public void EncodeDecodeUlongTest() { int slots = 32; EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = (ulong)slots * 2; parms.CoeffModulus = CoeffModulus.Create(64, new int[] { 40, 40, 40, 40 }); SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); CKKSEncoder encoder = new CKKSEncoder(context); Plaintext plain = new Plaintext(); List <Complex> result = new List <Complex>(); long value = 15; encoder.Encode(value, plain); encoder.Decode(plain, result); for (int i = 0; i < slots; i++) { double tmp = Math.Abs(value - result[i].Real); Assert.IsTrue(tmp < 0.5); } }
public void EncodeDecodeComplexTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS) { PolyModulusDegree = 64, CoeffModulus = CoeffModulus.Create(64, new int[] { 40, 40, 40, 40 }) }; SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); CKKSEncoder encoder = new CKKSEncoder(context); Plaintext plain = new Plaintext(); Complex value = new Complex(3.1415, 2.71828); encoder.Encode(value, scale: Math.Pow(2, 20), destination: plain); List <Complex> result = new List <Complex>(); encoder.Decode(plain, result); Assert.IsTrue(result.Count > 0); Assert.AreEqual(3.1415, result[0].Real, delta: 0.0001); Assert.AreEqual(2.71828, result[0].Imaginary, delta: 0.0001); }
public void EncodeDecodeVectorDoubleTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS) { PolyModulusDegree = 64, CoeffModulus = CoeffModulus.Create(64, new int[] { 30, 30 }) }; SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); CKKSEncoder encoder = new CKKSEncoder(context); Plaintext plain = new Plaintext(); double[] values = new double[] { 0.1, 2.3, 34.4 }; encoder.Encode(values, scale: Math.Pow(2, 20), destination: plain); List <double> result = new List <double>(); encoder.Decode(plain, result); Assert.IsNotNull(result); Assert.AreEqual(0.1, result[0], delta: 0.001); Assert.AreEqual(2.3, result[1], delta: 0.001); Assert.AreEqual(34.4, result[2], delta: 0.001); }
// private static void SinglePredict(Svc secureSvc, double[] feature, int i, CKKSEncoder encoder, Encryptor encryptor, Decryptor decryptor, Stopwatch innerProductStopwatch, Stopwatch degreeStopwatch, Stopwatch negateStopwatch, Stopwatch serverDecisionStopWatch, double scale, Result[] results) { double finalResult = 0; Console.WriteLine($"start {i} \n"); var plaintexts = new Plaintext(); var featuresCiphertexts = new Ciphertext(); encoder.Encode(feature, scale, plaintexts); encryptor.Encrypt(plaintexts, featuresCiphertexts); // Server side start var cyphetResult = secureSvc.Predict(featuresCiphertexts, true, true, innerProductStopwatch, degreeStopwatch, negateStopwatch, serverDecisionStopWatch); // Server side end //timePredictSum.Stop(); Plaintext plainResult = new Plaintext(); decryptor.Decrypt(cyphetResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); finalResult = result[0]; int estimation = finalResult > 0 ? 0 : 1; Console.WriteLine($"\n ************************************************"); Console.WriteLine($"SVC estimation{i} is : {estimation} , result : {finalResult}"); //file.WriteLine($"{i} , {estimation} , {finalResult} "); Console.WriteLine($"************************************************ \n"); results[i] = new Result(finalResult, estimation); //Console.WriteLine($"SecureSVC estimation{i} is : {estimation} , finalResult = {finalResult} , Time = {timePredictSum.ElapsedMilliseconds}"); }
public void EncodeDecodeDoubleTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = 64; List <SmallModulus> coeffModulus = new List <SmallModulus>(4); coeffModulus.Add(DefaultParams.SmallMods40Bit(0)); coeffModulus.Add(DefaultParams.SmallMods40Bit(1)); coeffModulus.Add(DefaultParams.SmallMods40Bit(2)); coeffModulus.Add(DefaultParams.SmallMods40Bit(3)); parms.CoeffModulus = coeffModulus; SEALContext context = SEALContext.Create(parms); int slots = 16; Plaintext plain = new Plaintext(); double delta = 1 << 16; List <Complex> result = new List <Complex>(); CKKSEncoder encoder = new CKKSEncoder(context); Assert.AreEqual(32ul, encoder.SlotCount); double value = 10d; encoder.Encode(value, delta, plain); encoder.Decode(plain, result); for (int i = 0; i < slots; i++) { double tmp = Math.Abs(value - result[i].Real); Assert.IsTrue(tmp < 0.5); } }
public void EncodeDecodeDoubleTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = 64; parms.CoeffModulus = CoeffModulus.Create(64, new int[] { 40, 40, 40, 40 }); SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); int slots = 16; Plaintext plain = new Plaintext(); double delta = 1 << 16; List <Complex> result = new List <Complex>(); CKKSEncoder encoder = new CKKSEncoder(context); Assert.AreEqual(32ul, encoder.SlotCount); double value = 10d; encoder.Encode(value, delta, plain); encoder.Decode(plain, result); for (int i = 0; i < slots; i++) { double tmp = Math.Abs(value - result[i].Real); Assert.IsTrue(tmp < 0.5); } }
public void EncodeDecodeComplexTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS) { PolyModulusDegree = 64, CoeffModulus = new List <SmallModulus>() { DefaultParams.SmallMods40Bit(0), DefaultParams.SmallMods40Bit(1), DefaultParams.SmallMods40Bit(2), DefaultParams.SmallMods40Bit(3) } }; SEALContext context = SEALContext.Create(parms); CKKSEncoder encoder = new CKKSEncoder(context); Plaintext plain = new Plaintext(); Complex value = new Complex(3.1415, 2.71828); encoder.Encode(value, scale: Math.Pow(2, 20), destination: plain); List <Complex> result = new List <Complex>(); encoder.Decode(plain, result); Assert.IsTrue(result.Count > 0); Assert.AreEqual(3.1415, result[0].Real, delta: 0.0001); Assert.AreEqual(2.71828, result[0].Imaginary, delta: 0.0001); }
public void EncodeDecodeUlongTest() { int slots = 32; EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = (ulong)slots * 2; List <SmallModulus> coeffModulus = new List <SmallModulus>(4); coeffModulus.Add(DefaultParams.SmallMods40Bit(0)); coeffModulus.Add(DefaultParams.SmallMods40Bit(1)); coeffModulus.Add(DefaultParams.SmallMods40Bit(2)); coeffModulus.Add(DefaultParams.SmallMods40Bit(3)); parms.CoeffModulus = coeffModulus; SEALContext context = SEALContext.Create(parms); CKKSEncoder encoder = new CKKSEncoder(context); Plaintext plain = new Plaintext(); List <Complex> result = new List <Complex>(); long value = 15; encoder.Encode(value, plain); encoder.Decode(plain, result); for (int i = 0; i < slots; i++) { double tmp = Math.Abs(value - result[i].Real); Assert.IsTrue(tmp < 0.5); } }
public void EncodeDecodeVectorDoubleTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS) { PolyModulusDegree = 64, CoeffModulus = new List <SmallModulus>() { DefaultParams.SmallMods30Bit(0), DefaultParams.SmallMods30Bit(1) } }; SEALContext context = SEALContext.Create(parms); CKKSEncoder encoder = new CKKSEncoder(context); Plaintext plain = new Plaintext(); double[] values = new double[] { 0.1, 2.3, 34.4 }; encoder.Encode(values, scale: Math.Pow(2, 20), destination: plain); List <double> result = new List <double>(); encoder.Decode(plain, result); Assert.IsNotNull(result); Assert.AreEqual(0.1, result[0], delta: 0.001); Assert.AreEqual(2.3, result[1], delta: 0.001); Assert.AreEqual(34.4, result[2], delta: 0.001); }
public double PrepareModel(Ciphertext cipherInput) { Plaintext decoded = new Plaintext(); List <double> doubleList = new List <double>(); // Decrypt CiphertextInput decryptor.Decrypt(cipherInput, decoded); // Decode plaintext encoder.Decode(decoded, doubleList); double decode; decode = doubleList.First(); // Return double value return(decode); }
//Function for printing the cipher text for debug public static List <double> PrintCyprherText(Decryptor decryptor, Ciphertext ciphertext, CKKSEncoder encoder, String name, bool print = false) { if (decryptor == null) { return(null); } Plaintext plainResult = new Plaintext(); decryptor.Decrypt(ciphertext, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); if (!PrintCipherText && !print) { return(result); } Console.WriteLine($"{name} TotalValue = {result[0]}"); return(result); }
public void EncodeDecodeVectorTest() { int slots = 32; EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = (ulong)slots * 2; List <SmallModulus> coeffModulus = new List <SmallModulus>(4); coeffModulus.Add(DefaultParams.SmallMods60Bit(0)); coeffModulus.Add(DefaultParams.SmallMods60Bit(1)); coeffModulus.Add(DefaultParams.SmallMods60Bit(2)); coeffModulus.Add(DefaultParams.SmallMods60Bit(3)); parms.CoeffModulus = coeffModulus; SEALContext context = SEALContext.Create(parms); CKKSEncoder encoder = new CKKSEncoder(context); List <Complex> values = new List <Complex>(slots); Random rnd = new Random(); int dataBound = 1 << 30; double delta = 1ul << 40; for (int i = 0; i < slots; i++) { values.Add(new Complex(rnd.Next() % dataBound, 0)); } Plaintext plain = new Plaintext(); encoder.Encode(values, delta, plain); List <Complex> result = new List <Complex>(); encoder.Decode(plain, result); for (int i = 0; i < slots; i++) { double tmp = Math.Abs(values[i].Real - result[i].Real); Assert.IsTrue(tmp < 0.5); } }
public static List <int> DecryptData( this List <Ciphertext> ctxs, Decryptor decryptor, CKKSEncoder encoder) { var result = new List <int>(); foreach (var ctx in ctxs) { using var ptx = new Plaintext(); decryptor.Decrypt(ctx, ptx); var data = new List <double>(); encoder.Decode(ptx, data); var integers = data.Select(d => (int)d); result.AddRange(integers); } return(result); }
public void EncodeDecodeVectorTest() { int slots = 32; EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = (ulong)slots * 2; parms.CoeffModulus = CoeffModulus.Create((ulong)slots * 2, new int[] { 60, 60, 60, 60 }); SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); CKKSEncoder encoder = new CKKSEncoder(context); List <Complex> values = new List <Complex>(slots); Random rnd = new Random(); int dataBound = 1 << 30; double delta = 1ul << 40; for (int i = 0; i < slots; i++) { values.Add(new Complex(rnd.Next() % dataBound, 0)); } Plaintext plain = new Plaintext(); encoder.Encode(values, delta, plain); List <Complex> result = new List <Complex>(); encoder.Decode(plain, result); for (int i = 0; i < slots; i++) { double tmp = Math.Abs(values[i].Real - result[i].Real); Assert.IsTrue(tmp < 0.5); } }
static async Task Main(string[] args) { Console.WriteLine("Secure Iris"); // Get Input from resource file or as args double[][] features; int numberOfFeatures = 4; int numOfRows = 0; // Option to load from args and not the whole dataset that is stored in resources if (args.Length >= numberOfFeatures) { numOfRows = args.Length / numberOfFeatures; // Features: features = new double[numOfRows][]; for (int i = 0; i < numOfRows; i++) { features[i] = new double[numberOfFeatures]; } for (int i = 0, l = args.Length; i < l; i++) { features[i / numberOfFeatures][i % numberOfFeatures] = Double.Parse(args[i]); } } else // load the whole dataset from resources { List <double[]> rows = new List <double[]>(); var bytes = Properties.Resources.iris; numOfRows = 0; features = SVCUtilities.SvcUtilities.LoadFeatures(bytes, numberOfFeatures, ref numOfRows); } Stopwatch clientStopwatch = new Stopwatch(); clientStopwatch.Start(); //svm algorithm parametrs calculated in python : training result double[][] vectors = new double[3][]; vectors[0] = new[] { 4.5, 2.3, 1.3, 0.3 }; vectors[1] = new[] { 5.1, 3.3, 1.7, 0.5 }; vectors[2] = new[] { 5.1, 2.5, 3.0, 1.1 }; double[][] coefficients = new double[1][]; coefficients[0] = new double[] { -0.07724840262003278, -0.6705185831514366, 0.7477669857714694 }; double[] intercepts = { 1.453766563649063 }; // SEAL parameters client side Console.WriteLine("SecureSVC : "); EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); ulong polyModulusDegree = 16384; int power = 40; double scale = Math.Pow(2.0, power); if (power >= 20 && power < 40) { parms.CoeffModulus = CoeffModulus.Create(polyModulusDegree, new int[] { 60, 20, 21, 22, 23, 24, 25, 26, 27, 60 }); } else if (power >= 40 && power < 60) { parms.CoeffModulus = CoeffModulus.Create(polyModulusDegree, new int[] { 60, 40, 40, 40, 40, 40, 40, 40, 60 }); } else if (power == 60) { polyModulusDegree = 32768; parms.CoeffModulus = CoeffModulus.Create(polyModulusDegree, new int[] { 60, 60, 60, 60, 60, 60, 60, 60, 60 }); } parms.PolyModulusDegree = polyModulusDegree; var context = new SEALContext(parms); // Key generation KeyGenerator keygen = new KeyGenerator(context); var publicKey = keygen.PublicKey; var secretKey = keygen.SecretKey; var relinKeys = keygen.RelinKeys(); var galoisKeys = keygen.GaloisKeys(); var encryptor = new Encryptor(context, publicKey); var decryptor = new Decryptor(context, secretKey); var encoder = new CKKSEncoder(context); clientStopwatch.Stop(); using (System.IO.StreamWriter file = new System.IO.StreamWriter( $@"{OutputDir}IrisLinearSecured_{IsParallel}_{DateTime.Now.Day}_{DateTime.Now.ToShortTimeString().ToString().Replace(":", "_")}.txt") ) { // Only CONCEPT demonstation how to parrallel all the computation on all machine cpu's // Though the parallel here is done on the client side , in "real life" this parallel mechanism // Should on the server side if (IsParallel) { int processorCount = Environment.ProcessorCount; Console.WriteLine("Number Of Logical Processors: {0}", processorCount); Svc[] machines = new Svc[processorCount]; Stopwatch[] innerProductStopwatchArr = new Stopwatch[processorCount]; Stopwatch[] negateStopwatchArr = new Stopwatch[processorCount]; Stopwatch[] degreeStopwatchArr = new Stopwatch[processorCount]; Stopwatch[] serverDecisionStopWatchArr = new Stopwatch[processorCount]; Result[] results = new Result[numOfRows]; Task[] tasks = new Task[processorCount]; for (int i = 0; i < processorCount; i++) { machines[i] = new Svc(vectors, coefficients, intercepts, "Linear", 0.25, 0.0, 3, 40, publicKey /*, secretKey*/, relinKeys, galoisKeys, 1, 4); innerProductStopwatchArr[i] = new Stopwatch(); negateStopwatchArr[i] = new Stopwatch(); degreeStopwatchArr[i] = new Stopwatch(); serverDecisionStopWatchArr[i] = new Stopwatch(); } Stopwatch totalTime = new Stopwatch(); totalTime.Start(); for (int i = 0; i < numOfRows;) { for (int j = 0; j < processorCount && i < numOfRows; j++) { var secureSvc = machines[i % processorCount]; var feature = features[i]; //Console.WriteLine($"\n\n $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"); List <object> l = new List <object>(); l.Add(secureSvc); //0 l.Add(feature); //1 l.Add(i); //2 l.Add(encoder); //3 l.Add(encryptor); //4 l.Add(decryptor); //5 l.Add(innerProductStopwatchArr[i % processorCount]); l.Add(degreeStopwatchArr[i % processorCount]); l.Add(negateStopwatchArr[i % processorCount]); l.Add(serverDecisionStopWatchArr[i % processorCount]); l.Add(results); tasks[j] = new TaskFactory().StartNew(new Action <object>((test) => { List <object> l2 = (List <object>)test; SinglePredict((Svc)l2[0], (double[])l2[1], (int)l2[2], (CKKSEncoder)l2[3], (Encryptor)l2[4], (Decryptor)l2[5], (Stopwatch)l2[6], (Stopwatch)l2[7], (Stopwatch)l2[8], (Stopwatch)l2[9], scale, (Result[])l2[10]); }), l); i++; } await Task.WhenAll(tasks); } totalTime.Stop(); for (int i = 0; i < numOfRows; i++) { var result = results[i]; file.WriteLine($"{i} , {result.Estimation} , {result.TotalValue} "); } double innerProductTime = 0; double degreeTime = 0; double negateTime = 0; double serverTime = 0; for (int i = 0; i < processorCount; i++) { innerProductTime = innerProductStopwatchArr[i].ElapsedMilliseconds; degreeTime = degreeStopwatchArr[i].ElapsedMilliseconds; negateTime = negateStopwatchArr[i].ElapsedMilliseconds; serverTime = serverDecisionStopWatchArr[i].ElapsedMilliseconds; } file.WriteLine($" Client time : {clientStopwatch.ElapsedMilliseconds} ms "); file.WriteLine($" Total time for {numOfRows} samples : {totalTime.ElapsedMilliseconds} ms "); file.WriteLine($" Avg time : {totalTime.ElapsedMilliseconds * 1000 / numOfRows} microSec "); file.WriteLine($" Inner Product time for {numOfRows} samples : {innerProductTime} ms "); file.WriteLine($" Inner Product Avg time : {innerProductTime * 1000 / numOfRows} microSec "); file.WriteLine($" Degree time for {numOfRows} samples : {degreeTime} ms "); file.WriteLine($" Degree Avg time : {degreeTime * 1000 / numOfRows} microSec "); file.WriteLine($" Negate time for {numOfRows} samples : {negateTime} ms "); file.WriteLine($" Negate Avg time : {negateTime * 1000 / numOfRows} microSec "); file.WriteLine($" Decision time for {numOfRows} samples : {serverTime} ms "); file.WriteLine($" Decision Avg time : {serverTime * 1000 / numOfRows} microSec "); } else { //Initiate Stopwatch for performance measure Stopwatch innerProductStopwatch = new Stopwatch(); Stopwatch negateStopwatch = new Stopwatch(); Stopwatch degreeStopwatch = new Stopwatch(); Stopwatch serverDecisionStopWatch = new Stopwatch(); int featureSizeWithSpace = numberOfFeatures; int batchSize = 200; if (batchSize > 1) { featureSizeWithSpace = numberOfFeatures * 2; } Svc clf = new Svc(vectors, coefficients, intercepts, "Linear", 0.25, 0.0, 3, 40, publicKey /*, secretKey*/, relinKeys, galoisKeys, batchSize, featureSizeWithSpace); Stopwatch totalTime = new Stopwatch(); totalTime.Start(); int start = 0; //double[] batchFeatures = new double[batchSize * featureSizeWithSpace]; for (int i = 0; i < numOfRows;) { start = i; double finalResult = -10000; double[][] batchRows = new double[batchSize][]; for (int j = 0; j < batchSize && i < numOfRows; j++) { batchRows[j] = features[i]; i++; } double[] batchFeatures = GetBatchFeatures(batchRows, batchSize, numberOfFeatures, featureSizeWithSpace); var plaintexts = new Plaintext(); var featuresCiphertexts = new Ciphertext(); encoder.Encode(batchFeatures, scale, plaintexts); encryptor.Encrypt(plaintexts, featuresCiphertexts); //Server side start var cypherResult = clf.Predict(featuresCiphertexts, true, true, innerProductStopwatch, degreeStopwatch, negateStopwatch, serverDecisionStopWatch); // Server side end Plaintext plainResult = new Plaintext(); decryptor.Decrypt(cypherResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); for (int j = 0; j < batchSize && start < numOfRows; j++) { finalResult = result[j * featureSizeWithSpace]; int estimation = finalResult > 0 ? 0 : 1; Console.WriteLine($"\n ************************************************"); Console.WriteLine($"SVC estimation{i} is : {estimation} , result : {finalResult}"); file.WriteLine($"{start} , {estimation} , {finalResult} "); Console.WriteLine($"************************************************ \n"); start++; } } totalTime.Stop(); file.WriteLine($" Client time : {clientStopwatch.ElapsedMilliseconds} ms "); file.WriteLine($" Total time for {numOfRows} samples : {totalTime.ElapsedMilliseconds} ms "); file.WriteLine($" Avg time : {totalTime.ElapsedMilliseconds * 1000 / numOfRows} microSec "); file.WriteLine($" Inner Product time for {numOfRows} samples : {innerProductStopwatch.ElapsedMilliseconds} ms "); file.WriteLine($" Inner Product Avg time : {innerProductStopwatch.ElapsedMilliseconds * 1000 / numOfRows} microSec "); file.WriteLine($" Degree time for {numOfRows} samples : {degreeStopwatch.ElapsedMilliseconds} ms "); file.WriteLine($" Degree Avg time : {degreeStopwatch.ElapsedMilliseconds * 1000 / numOfRows} microSec "); file.WriteLine($" Negate time for {numOfRows} samples : {negateStopwatch.ElapsedMilliseconds} ms "); file.WriteLine($" Negate Avg time : {negateStopwatch.ElapsedMilliseconds * 1000 / numOfRows} microSec "); file.WriteLine($" Decision time for {numOfRows} samples : {serverDecisionStopWatch.ElapsedMilliseconds} ms "); file.WriteLine($" Decision Avg time : {serverDecisionStopWatch.ElapsedMilliseconds * 1000 / numOfRows} microSec "); } } }
/* * In this example we show how serialization works in Microsoft SEAL. Specifically, * we present important concepts that enable the user to optimize the data size when * communicating ciphertexts and keys for outsourced computation. Unlike the previous * examples, we organize this one in a client-server style for maximal clarity. The * server selects encryption parameters, the client generates keys, the server does * the encrypted computation, and the client decrypts. */ private static void ExampleSerialization() { Utilities.PrintExampleBanner("Example: Serialization"); /* * We require ZLIB or Zstandard support for this example to be available. */ if (!Serialization.IsSupportedComprMode(ComprModeType.ZLIB) && !Serialization.IsSupportedComprMode(ComprModeType.ZSTD)) { Console.WriteLine("Neither ZLIB nor Zstandard support is enabled; this example is not available."); Console.WriteLine(); return; } /* * We start by briefly discussing the Serializable<T> generic class. This is * a wrapper class that can wrap any serializable class, which include: * * - EncryptionParameters * - Modulus * - Plaintext and Ciphertext * - SecretKey, PublicKey, RelinKeys, and GaloisKeys * * Serializable<T> provides minimal functionality needed to serialize the wrapped * object by simply forwarding the calls to corresponding functions of the wrapped * object of type T. The need for Serializable<T> comes from the fact that many * Microsoft SEAL objects consist of two parts, one of which is pseudorandom data * independent of the other part. Until the object is actually being used, the * pseudorandom part can be instead stored as a seed. We will call objects with * property `seedable'. * * For example, GaloisKeys can often be very large in size, but in reality half * of the data is pseudorandom and can be stored as a seed. Since GaloisKeys are * never used by the party that generates them, so it makes sense to expand the * seed at the point deserialization. On the other hand, we cannot allow the user * to accidentally try to use an unexpanded GaloisKeys object, which is prevented * at by ensuring it is always wrapped in a Serializable<GaloisKeys> and can only * be serialized. * * Only some Microsoft SEAL objects are seedable. Specifically, they are: * * - PublicKey, RelinKeys, and GaloisKeys * - Ciphertext in secret-key mode (from Encryptor.EncryptSymmetric or * Encryptor.EncryptZeroSymmetric) * * Importantly, ciphertexts in public-key mode are not seedable. Thus, it may * be beneficial to use Microsoft SEAL in secret-key mode whenever the public * key is not truly needed. * * There are a handful of functions that output Serializable<T> objects: * * - Encryptor.Encrypt (and variants) output Serializable<Ciphertext> * - KeyGenerator.Create... output Serializable<T> for different key types * * Note that Encryptor.Encrypt is included in the above list, yet it produces * ciphertexts in public-key mode that are not seedable. This is for the sake of * consistency in the API for public-key and secret-key encryption. Functions * that output Serializable<T> objects also have overloads that take a normal * object of type T as a destination parameter, overwriting it. These overloads * can be convenient for local testing where no serialization is needed and the * object needs to be used at the point of construction. Such an object can no * longer be transformed back to a seeded state. */ /* * To simulate client-server interaction, we set up a shared C# stream. In real * use-cases this can be a network stream, a filestream, or any shared resource. * * It is critical to note that all data serialized by Microsoft SEAL is in binary * form, so it is not meaningful to print the data as ASCII characters. Encodings * such as Base64 would increase the data size, which is already a bottleneck in * homomorphic encryption. Hence, serialization into text is not supported or * recommended. * * In this example we use a couple of shared MemoryStreams. */ using MemoryStream parmsStream = new MemoryStream(); using MemoryStream dataStream = new MemoryStream(); using MemoryStream skStream = new MemoryStream(); /* * The server first determines the computation and sets encryption parameters * accordingly. */ { ulong polyModulusDegree = 8192; using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 50, 30, 50 }); /* * Serialization of the encryption parameters to our shared stream is very * simple with the EncryptionParameters.Save function. */ long size = parms.Save(parmsStream); /* * Seek the parmsStream head back to beginning of the stream. */ parmsStream.Seek(0, SeekOrigin.Begin); /* * The return value of this function is the actual byte count of data written * to the stream. */ Utilities.PrintLine(); Console.WriteLine($"EncryptionParameters: wrote {size} bytes"); /* * Before moving on, we will take some time to discuss further options in * serialization. These will become particularly important when the user * needs to optimize communication and storage sizes. * * It is possible to enable or disable compression for serialization by * providing EncryptionParameters.Save with the desired compression mode as * in the following examples: * * long size = parms.Save(sharedStream, ComprModeType.None); * long size = parms.Save(sharedStream, ComprModeType.ZLIB); * long size = parms.Save(sharedStream, ComprModeType.ZSTD); * * If Microsoft SEAL is compiled with Zstandard or ZLIB support, the default * is to use one of them. If available, Zstandard is preferred over ZLIB due * to its speed. * * Compression can have a substantial impact on the serialized data size, * because ciphertext and key data consists of many uniformly random integers * modulo the CoeffModulus primes. Especially when using CKKS, the primes in * CoeffModulus can be relatively small compared to the 64-bit words used to * store the ciphertext and key data internally. Serialization writes full * 64-bit words to the destination buffer or stream, possibly leaving in many * zero bytes corresponding to the high-order bytes of the 64-bit words. One * convenient way to get rid of these zeros is to apply a general-purpose * compression algorithm on the encrypted data. The compression rate can be * significant (up to 50-60%) when using CKKS with small primes. */ /* * In many cases, when working with fixed size memory, it is necessary to know * ahead of time an upper bound on the serialized data size to allocate enough * memory. This information is returned by the EncryptionParameters.SaveSize * function. This function accepts the desired compression mode, or uses the * default option otherwise. * * In more detail, the output of EncryptionParameters.SaveSize is as follows: * * - Exact buffer size required for ComprModeType.None; * - Upper bound on the size required for ComprModeType.ZLIB or * ComprModeType.ZSTD. * * As we can see from the print-out, the sizes returned by these functions * are significantly larger than the compressed size written into the shared * stream in the beginning. This is normal: compression yielded a significant * improvement in the data size, however, it is impossible to know ahead of * time the exact size of the compressed data. If compression is not used, * then the size is exactly determined by the encryption parameters. */ Utilities.PrintLine(); Console.Write("EncryptionParameters: data size upper bound (ComprModeType.None): "); Console.WriteLine(parms.SaveSize(ComprModeType.None)); Console.Write(" "); Console.Write("EncryptionParameters: data size upper bound (compression): "); Console.WriteLine(parms.SaveSize(/* Serialization.ComprModeDefault */)); /* * As an example, we now serialize the encryption parameters to a fixed * size buffer. */ using MemoryStream buffer = new MemoryStream(new byte[parms.SaveSize()]); parms.Save(buffer); /* * To illustrate deserialization, we load back the encryption parameters * from our buffer into another instance of EncryptionParameters. First * we need to seek our stream back to the beginning. */ buffer.Seek(0, SeekOrigin.Begin); using EncryptionParameters parms2 = new EncryptionParameters(); parms2.Load(buffer); /* * We can check that the saved and loaded encryption parameters indeed match. */ Utilities.PrintLine(); Console.WriteLine($"EncryptionParameters: parms == parms2: {parms.Equals(parms2)}"); } /* * Client starts by loading the encryption parameters, sets up the SEALContext, * and creates the required keys. */ { using EncryptionParameters parms = new EncryptionParameters(); parms.Load(parmsStream); /* * Seek the parmsStream head back to beginning of the stream because we * will use the same stream to read the parameters repeatedly. */ parmsStream.Seek(0, SeekOrigin.Begin); using SEALContext context = new SEALContext(parms); using KeyGenerator keygen = new KeyGenerator(context); using SecretKey sk = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey pk); /* * We need to save the secret key so we can decrypt later. */ sk.Save(skStream); skStream.Seek(0, SeekOrigin.Begin); /* * As in previous examples, in this example we will encrypt in public-key * mode. If we want to send a public key over the network, we should instead * have created it as a seeded object as follows: * * Serializable<PublicKey> pk = keygen.CreatePublicKey(); * * In this example we will also use relinearization keys. These we will * absolutely want to create as seeded objects to minimize communication * cost, unlike in prior examples. */ using Serializable <RelinKeys> rlk = keygen.CreateRelinKeys(); /* * To demonstrate the significant space saving from this method, we will * create another set of relinearization keys, this time fully expanded. */ keygen.CreateRelinKeys(out RelinKeys rlkBig); /* * We serialize both relinearization keys to demonstrate the concrete size * difference. If compressed serialization is used, the compression rate * will be the same in both cases. We omit specifying the compression mode * to use the default, as determined by the Microsoft SEAL build system. */ long sizeRlk = rlk.Save(dataStream); long sizeRlkBig = rlkBig.Save(dataStream); Utilities.PrintLine(); Console.WriteLine($"Serializable<RelinKeys>: wrote {sizeRlk} bytes"); Console.Write(" "); Console.WriteLine($"RelinKeys: wrote {sizeRlkBig} bytes"); /* * Seek back in dataStream to where rlk data ended, i.e., sizeRlkBig bytes * backwards from current position. */ dataStream.Seek(-sizeRlkBig, SeekOrigin.Current); /* * Next set up the CKKSEncoder and Encryptor, and encrypt some numbers. */ double scale = Math.Pow(2.0, 30); CKKSEncoder encoder = new CKKSEncoder(context); using Plaintext plain1 = new Plaintext(), plain2 = new Plaintext(); encoder.Encode(2.3, scale, plain1); encoder.Encode(4.5, scale, plain2); using Encryptor encryptor = new Encryptor(context, pk); /* * The client will not compute on ciphertexts that it creates, so it can * just as well create Serializable<Ciphertext> objects. In fact, we do * not even need to name those objects and instead immediately call * Serializable<Ciphertext>.Save. */ long sizeEncrypted1 = encryptor.Encrypt(plain1).Save(dataStream); /* * As we discussed in the beginning of this example, ciphertexts can * be created in a seeded state in secret-key mode, providing a huge * reduction in the data size upon serialization. To do this, we need * to provide the Encryptor with the secret key in its constructor, or * at a later point with the Encryptor.SetSecretKey function, and use * the Encryptor.EncryptSymmetric function to encrypt. */ encryptor.SetSecretKey(sk); long sizeSymEncrypted2 = encryptor.EncryptSymmetric(plain2).Save(dataStream); /* * The size reduction is substantial. */ Utilities.PrintLine(); Console.WriteLine($"Serializable<Ciphertext> (public-key): wrote {sizeEncrypted1} bytes"); Console.Write(" "); Console.Write($"Serializable<Ciphertext> (seeded secret-key): "); Console.WriteLine($"wrote {sizeSymEncrypted2} bytes"); /* * Seek to the beginning of dataStream. */ dataStream.Seek(0, SeekOrigin.Begin); /* * We have seen how creating seeded objects can result in huge space * savings compared to creating unseeded objects. This is particularly * important when creating Galois keys, which can be very large. We have * seen how secret-key encryption can be used to achieve much smaller * ciphertext sizes when the public-key functionality is not needed. * * We would also like to draw attention to the fact there we could easily * serialize multiple Microsoft SEAL objects sequentially in a stream. Each * object writes its own size into the stream, so deserialization knows * exactly how many bytes to read. We will see this working below. */ } /* * The server can now compute on the encrypted data. We will recreate the * SEALContext and set up an Evaluator here. */ { using EncryptionParameters parms = new EncryptionParameters(); parms.Load(parmsStream); parmsStream.Seek(0, SeekOrigin.Begin); using SEALContext context = new SEALContext(parms); using Evaluator evaluator = new Evaluator(context); /* * Next we need to load relinearization keys and the ciphertexts from our * dataStream. */ using RelinKeys rlk = new RelinKeys(); using Ciphertext encrypted1 = new Ciphertext(), encrypted2 = new Ciphertext(); /* * Deserialization is as easy as serialization. */ rlk.Load(context, dataStream); encrypted1.Load(context, dataStream); encrypted2.Load(context, dataStream); /* * Compute the product, rescale, and relinearize. */ using Ciphertext encryptedProd = new Ciphertext(); evaluator.Multiply(encrypted1, encrypted2, encryptedProd); evaluator.RelinearizeInplace(encryptedProd, rlk); evaluator.RescaleToNextInplace(encryptedProd); /* * We use dataStream to communicate encryptedProd back to the client. * There is no way to save the encryptedProd as a seeded object: only * freshly encrypted secret-key ciphertexts can be seeded. Note how the * size of the result ciphertext is smaller than the size of a fresh * ciphertext because it is at a lower level due to the rescale operation. */ dataStream.Seek(0, SeekOrigin.Begin); long sizeEncryptedProd = encryptedProd.Save(dataStream); dataStream.Seek(0, SeekOrigin.Begin); Utilities.PrintLine(); Console.Write($"Ciphertext (secret-key): "); Console.WriteLine($"wrote {sizeEncryptedProd} bytes"); } /* * In the final step the client decrypts the result. */ { using EncryptionParameters parms = new EncryptionParameters(); parms.Load(parmsStream); parmsStream.Seek(0, SeekOrigin.Begin); using SEALContext context = new SEALContext(parms); /* * Load back the secret key from skStream. */ using SecretKey sk = new SecretKey(); sk.Load(context, skStream); using Decryptor decryptor = new Decryptor(context, sk); using CKKSEncoder encoder = new CKKSEncoder(context); using Ciphertext encryptedResult = new Ciphertext(); encryptedResult.Load(context, dataStream); using Plaintext plainResult = new Plaintext(); decryptor.Decrypt(encryptedResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); Utilities.PrintLine(); Console.WriteLine("Decrypt and decode PI * x ^ 3 + 0.4x + 1."); Console.WriteLine(" + Expected result:"); List <double> trueResult = new List <double>((int)encoder.SlotCount); for (ulong i = 0; i < encoder.SlotCount; i++) { trueResult.Add(2.3 * 4.5); } Utilities.PrintVector(trueResult, 3, 7); Console.WriteLine(" + Computed result ...... Correct."); Utilities.PrintVector(result, 3, 7); } /* * Finally, we give a little bit more explanation of the structure of data * serialized by Microsoft SEAL. Serialized data always starts with a 16-byte * SEALHeader struct, as defined in dotnet/src/Serialization.cs, and is * followed by the possibly compressed data for the object. * * A SEALHeader contains the following data: * * [offset 0] 2-byte magic number 0xA15E (Serialization.SEALMagic) * [offset 2] 1-byte indicating the header size in bytes (always 16) * [offset 3] 1-byte indicating the Microsoft SEAL major version number * [offset 4] 1-byte indicating the Microsoft SEAL minor version number * [offset 5] 1-byte indicating the compression mode type * [offset 6] 2-byte reserved field (unused) * [offset 8] 8-byte size in bytes of the serialized data, including the header * * Currently Microsoft SEAL supports only little-endian systems. * * As an example, we demonstrate the SEALHeader created by saving a plaintext. * Note that the SEALHeader is never compressed, so there is no need to specify * the compression mode. */ using Plaintext pt = new Plaintext("1x^2 + 3"); using MemoryStream stream = new MemoryStream(); long dataSize = pt.Save(stream); /* * Seek the stream head back to beginning of the stream. */ stream.Seek(0, SeekOrigin.Begin); /* * We can now load just the SEALHeader back from the stream as follows. */ Serialization.SEALHeader header = new Serialization.SEALHeader(); Serialization.LoadHeader(stream, header); /* * Now confirm that the size of data written to stream matches with what is * indicated by the SEALHeader. */ Utilities.PrintLine(); Console.WriteLine($"Size written to stream: {dataSize} bytes"); Console.Write(" "); Console.WriteLine($"Size indicated in SEALHeader: {header.Size} bytes"); Console.WriteLine(); }
public void TestLinearRegression_Encryption() { // these are the feature sets we will be encrypting and getting // the ML model results on. The plaintext versions of these values // should (in the encryped scheme) not be known by the evaluator double[][] testX = new double[6][]; testX[0] = new double[] { 43.45089541, 53.15091973, 53.07708932, 93.65456934, 65.23330105, 69.34856259, 62.91649012, 35.28814156, 108.1002775, 100.1735266 }; testX[1] = new double[] { 51.59952075, 99.48561775, 95.75948428, 126.6533636, 142.5235433, 90.97955769, 43.66586306, 85.31957886, 62.57644682, 66.12458533 }; testX[2] = new double[] { 94.77026243, 71.51229208, 85.33271407, 69.58347566, 107.8693045, 101.6701889, 89.88200921, 54.93440139, 105.5448532, 72.07947083 }; testX[3] = new double[] { 89.53820766, 100.199631, 86.19911875, 85.88717675, 33.92249944, 80.47113937, 65.34411148, 89.70004394, 75.00778202, 122.3514331 }; testX[4] = new double[] { 96.86101454, 97.54597612, 122.9960987, 86.1281547, 115.5539807, 107.888993, 65.51660154, 74.17007885, 48.04727402, 93.56952259 }; testX[5] = new double[] { 91.75121904, 121.2115065, 62.92763365, 99.4343452, 70.420912, 88.0580948, 71.82993308, 80.49171244, 87.11321454, 100.1459868 }; // setup the encryptor and other various components EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = 8192; parms.CoeffModulus = DefaultParams.CoeffModulus128(polyModulusDegree: 8192); SEALContext context = SEALContext.Create(parms); CKKSEncoder encoder = new CKKSEncoder(context); KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; RelinKeys relinKeys = keygen.RelinKeys(decompositionBitCount: DefaultParams.DBCmax); Encryptor encryptor = new Encryptor(context, publicKey); Evaluator evaluator = new Evaluator(context); Decryptor decryptor = new Decryptor(context, secretKey); double scale = Math.Pow(2.0, 30); List <List <Ciphertext> > featureCiphers = new List <List <Ciphertext> >(); for (int i = 0; i < testX.Length; i++) { List <Ciphertext> curFeatureCiphers = new List <Ciphertext>(); foreach (var featureVal in testX[i]) { List <double> featureVector = new double[] { featureVal }.ToList(); Plaintext plain = new Plaintext(); encoder.Encode(featureVal, scale, plain); Ciphertext encrypted = new Ciphertext(); encryptor.Encrypt(plain, encrypted); curFeatureCiphers.Add(encrypted); } featureCiphers.Add(curFeatureCiphers); } // This is the 'evaluator' section // this is not a part of the client and would, in a cloud based solution, be run on the server // the server should not know the values of the input features, but it will do math on them // likewise, the client would not know the various weights and parameters, but will receive a result double[] weights_model = { 0.0921533, 0.14545279, 0.11066622, 0.06119513, 0.10041948, 0.19091597, 0.07359407, 0.04503237, 0.10848583, 0.10092494 }; double intercept_model = -2.484390163425502; double[] weights_groundTruth = { 0.1, 0.15, 0.1, 0.05, 0.1, 0.2, 0.05, 0.05, 0.1, 0.1 }; // we need to encode the weights/parameters/intercepts/etc. used by the model using the same public key as the client List <Ciphertext> weightsCTs = new List <Ciphertext>(); List <Ciphertext> scoreCTs = new List <Ciphertext>(); foreach (var weight in weights_model) { List <double> weightVector = new double[] { weight }.ToList(); List <double> scoreVector = new double[] { 0.0 }.ToList(); Plaintext weightPT = new Plaintext(); encoder.Encode(weight, scale, weightPT); Ciphertext weightCT = new Ciphertext(); encryptor.Encrypt(weightPT, weightCT); weightsCTs.Add(weightCT); } // next, we run the actual model's computation // linear regression is a basic dot product for (int i = 0; i < featureCiphers.Count(); i++) { List <Ciphertext> multResultCTs = new List <Ciphertext>(); for (var j = 0; j < weightsCTs.Count; j++) { Ciphertext multResultCT = new Ciphertext(); evaluator.Multiply(weightsCTs[j], featureCiphers[i][j], multResultCT); multResultCTs.Add(multResultCT); } Ciphertext dotProductResult = new Ciphertext(); evaluator.AddMany(multResultCTs, dotProductResult); Plaintext scorePT = new Plaintext(); encoder.Encode(intercept_model, dotProductResult.Scale, scorePT); Ciphertext scoreCT = new Ciphertext(); encryptor.Encrypt(scorePT, scoreCT); evaluator.AddInplace(scoreCT, dotProductResult); scoreCTs.Add(scoreCT); //evaluator.AddInplace(scoreCTs[i], interceptCT); } // we now have the encrypted version of the ML model. The below section is the client's again // in it, we decrypt the results given by the server using the private key generated previously List <List <double> > predictions = new List <List <double> >(); for (int i = 0; i < scoreCTs.Count; i++) { Plaintext plainResult = new Plaintext(); var encryptedResult = scoreCTs[i]; decryptor.Decrypt(encryptedResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); predictions.Add(result); } // this is the output section. In practice, we would not have access to these values (as they represent the ground truth) // We are using them here merely to demonstrate that we can properly recover the proper ML model output double[] yGroundTruth = { 68.43881952, 87.75905253, 88.34053641, 83.87264322, 95.20322583, 89.61704108 }; double[] yTest = { 68.702934, 87.28860458, 88.40827187, 83.24001674, 94.53137951, 89.00229455 }; const double EPSILON = 1e-4; for (int i = 0; i < predictions.Count; i++) { var avgDecryption = predictions[i].Average(); var absDelta = Math.Abs(avgDecryption - yTest[i]); Assert.IsTrue(absDelta < EPSILON); } }
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 SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); keygen.CreateRelinKeys(out RelinKeys relinKeys); keygen.CreateGaloisKeys(out GaloisKeys galoisKeys); 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, galoisKeys, 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. */ }
public void EncryptZeroTest() { { SEALContext context = GlobalContext.BFVContext; KeyGenerator keyGen = new KeyGenerator(context); PublicKey publicKey = keyGen.PublicKey; SecretKey secretKey = keyGen.SecretKey; Encryptor encryptor = new Encryptor(context, publicKey, secretKey); Decryptor decryptor = new Decryptor(context, secretKey); Assert.IsNotNull(encryptor); Assert.IsNotNull(decryptor); Ciphertext cipher = new Ciphertext(); Plaintext plain = new Plaintext(); ParmsId nextParms = context.FirstContextData.NextContextData.ParmsId; { encryptor.EncryptZero(cipher); Assert.IsFalse(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); decryptor.Decrypt(cipher, plain); Assert.IsTrue(plain.IsZero); encryptor.EncryptZero(nextParms, cipher); Assert.IsFalse(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); Assert.AreEqual(cipher.ParmsId, nextParms); decryptor.Decrypt(cipher, plain); Assert.IsTrue(plain.IsZero); } { encryptor.EncryptZeroSymmetric(cipher); Assert.IsFalse(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); decryptor.Decrypt(cipher, plain); Assert.IsTrue(plain.IsZero); encryptor.EncryptZeroSymmetric(nextParms, cipher); Assert.IsFalse(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); Assert.AreEqual(cipher.ParmsId, nextParms); decryptor.Decrypt(cipher, plain); Assert.IsTrue(plain.IsZero); } using (MemoryStream stream = new MemoryStream()) { encryptor.EncryptZeroSymmetricSave(stream); stream.Seek(0, SeekOrigin.Begin); cipher.Load(context, stream); Assert.IsFalse(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); decryptor.Decrypt(cipher, plain); Assert.IsTrue(plain.IsZero); } using (MemoryStream stream = new MemoryStream()) { encryptor.EncryptZeroSymmetricSave(nextParms, stream); stream.Seek(0, SeekOrigin.Begin); cipher.Load(context, stream); Assert.IsFalse(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); Assert.AreEqual(cipher.ParmsId, nextParms); decryptor.Decrypt(cipher, plain); Assert.IsTrue(plain.IsZero); } } { SEALContext context = GlobalContext.CKKSContext; KeyGenerator keyGen = new KeyGenerator(context); PublicKey publicKey = keyGen.PublicKey; SecretKey secretKey = keyGen.SecretKey; Encryptor encryptor = new Encryptor(context, publicKey, secretKey); Decryptor decryptor = new Decryptor(context, secretKey); CKKSEncoder encoder = new CKKSEncoder(context); Assert.IsNotNull(encryptor); Assert.IsNotNull(decryptor); Ciphertext cipher = new Ciphertext(); Plaintext plain = new Plaintext(); ParmsId nextParms = context.FirstContextData.NextContextData.ParmsId; List <Complex> res = new List <Complex>(); { encryptor.EncryptZero(cipher); Assert.IsTrue(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); cipher.Scale = Math.Pow(2.0, 30); decryptor.Decrypt(cipher, plain); encoder.Decode(plain, res); foreach (Complex val in res) { Assert.AreEqual(val.Real, 0.0, 0.01); Assert.AreEqual(val.Imaginary, 0.0, 0.01); } encryptor.EncryptZero(nextParms, cipher); Assert.IsTrue(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); cipher.Scale = Math.Pow(2.0, 30); Assert.AreEqual(cipher.ParmsId, nextParms); decryptor.Decrypt(cipher, plain); Assert.AreEqual(plain.ParmsId, nextParms); encoder.Decode(plain, res); foreach (Complex val in res) { Assert.AreEqual(val.Real, 0.0, 0.01); Assert.AreEqual(val.Imaginary, 0.0, 0.01); } } { encryptor.EncryptZeroSymmetric(cipher); Assert.IsTrue(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); cipher.Scale = Math.Pow(2.0, 30); decryptor.Decrypt(cipher, plain); encoder.Decode(plain, res); foreach (Complex val in res) { Assert.AreEqual(val.Real, 0.0, 0.01); Assert.AreEqual(val.Imaginary, 0.0, 0.01); } encryptor.EncryptZeroSymmetric(nextParms, cipher); Assert.IsTrue(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); cipher.Scale = Math.Pow(2.0, 30); Assert.AreEqual(cipher.ParmsId, nextParms); decryptor.Decrypt(cipher, plain); Assert.AreEqual(plain.ParmsId, nextParms); encoder.Decode(plain, res); foreach (Complex val in res) { Assert.AreEqual(val.Real, 0.0, 0.01); Assert.AreEqual(val.Imaginary, 0.0, 0.01); } } using (MemoryStream stream = new MemoryStream()) { encryptor.EncryptZeroSymmetricSave(stream); stream.Seek(0, SeekOrigin.Begin); cipher.Load(context, stream); Assert.IsTrue(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); cipher.Scale = Math.Pow(2.0, 30); decryptor.Decrypt(cipher, plain); encoder.Decode(plain, res); foreach (Complex val in res) { Assert.AreEqual(val.Real, 0.0, 0.01); Assert.AreEqual(val.Imaginary, 0.0, 0.01); } } using (MemoryStream stream = new MemoryStream()) { encryptor.EncryptZeroSymmetricSave(nextParms, stream); stream.Seek(0, SeekOrigin.Begin); cipher.Load(context, stream); Assert.IsTrue(cipher.IsNTTForm); Assert.IsFalse(cipher.IsTransparent); Assert.AreEqual(cipher.Scale, 1.0, double.Epsilon); cipher.Scale = Math.Pow(2.0, 30); Assert.AreEqual(cipher.ParmsId, nextParms); decryptor.Decrypt(cipher, plain); Assert.AreEqual(plain.ParmsId, nextParms); encoder.Decode(plain, res); foreach (Complex val in res) { Assert.AreEqual(val.Real, 0.0, 0.01); Assert.AreEqual(val.Imaginary, 0.0, 0.01); } } } }
private static void CKKSPerformanceTest(SEALContext context) { Stopwatch timer; Utilities.PrintParameters(context); Console.WriteLine(); bool hasZLIB = Serialization.IsSupportedComprMode(ComprModeType.ZLIB); bool hasZSTD = Serialization.IsSupportedComprMode(ComprModeType.ZSTD); 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; keygen.CreatePublicKey(out PublicKey publicKey); Func <RelinKeys> GetRelinKeys = () => { if (context.UsingKeyswitching) { /* * Generate relinearization keys. */ Console.Write("Generating relinearization keys: "); timer = Stopwatch.StartNew(); keygen.CreateRelinKeys(out RelinKeys relinKeys); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(relinKeys); } 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(); keygen.CreateGaloisKeys(out GaloisKeys galoisKeys); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(galoisKeys); } 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(); Stopwatch timeSerializeSum = new Stopwatch(); Stopwatch timeSerializeZLIBSum = new Stopwatch(); Stopwatch timeSerializeZSTDSum = 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(); } /* * [Serialize Ciphertext] */ using MemoryStream stream = new MemoryStream(); timeSerializeSum.Start(); encrypted.Save(stream, ComprModeType.None); timeSerializeSum.Stop(); if (hasZLIB) { /* * [Serialize Ciphertext (ZLIB)] */ timeSerializeZLIBSum.Start(); encrypted.Save(stream, ComprModeType.ZLIB); timeSerializeZLIBSum.Stop(); } if (hasZSTD) { /* * [Serialize Ciphertext (Zstandard)] */ timeSerializeZSTDSum.Start(); encrypted.Save(stream, ComprModeType.ZSTD); timeSerializeZSTDSum.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); int avgSerializeSum = (int)(timeSerializeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSerializeZLIBSum = (int)(timeSerializeZLIBSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSerializeZSTDSum = (int)(timeSerializeZSTDSum.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.WriteLine($"Average serialize ciphertext: {avgSerializeSum} microseconds"); if (hasZLIB) { Console.WriteLine( $"Average compressed (ZLIB) serialize ciphertext: {avgSerializeZLIBSum} microseconds"); } if (hasZSTD) { Console.WriteLine( $"Average compressed (Zstandard) serialize ciphertext: {avgSerializeZSTDSum} microseconds"); } Console.Out.Flush(); }
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. */ }
public void TestNNBinary_Encryption() { double[][] testX = new double[5][]; testX[0] = new double[] { 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0 }; testX[1] = new double[] { 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 }; testX[2] = new double[] { 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 }; testX[3] = new double[] { 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0 }; testX[4] = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0 }; bool[] testYs = { true, false, true, false, false }; // setup the encryptor and other various components EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = 32768; parms.CoeffModulus = DefaultParams.CoeffModulus256(polyModulusDegree: 32768); SEALContext context = SEALContext.Create(parms); CKKSEncoder encoder = new CKKSEncoder(context); KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; RelinKeys relinKeys = keygen.RelinKeys(decompositionBitCount: DefaultParams.DBCmax); Encryptor encryptor = new Encryptor(context, publicKey); Evaluator evaluator = new Evaluator(context); Decryptor decryptor = new Decryptor(context, secretKey); double scale = Math.Pow(2.0, 30); // encrypt the input features List <List <Ciphertext> > featureCiphers = new List <List <Ciphertext> >(); for (int i = 0; i < testX.Length; i++) { List <Ciphertext> curFeatureCiphers = new List <Ciphertext>(); foreach (var featureVal in testX[i]) { List <double> featureVector = new double[] { featureVal }.ToList(); Plaintext plain = new Plaintext(); encoder.Encode(featureVal, scale, plain); Ciphertext encrypted = new Ciphertext(); encryptor.Encrypt(plain, encrypted); curFeatureCiphers.Add(encrypted); } featureCiphers.Add(curFeatureCiphers); } /* * * This represents the initial border between the client and the server * The only data that should cross this line is the public key computed above, and the cipher texts of the ML model features * * */ var weights = NNConstants.GetNNBinaryWeights(); var weightsEncoded = NNConstants.GetWeightsPlaintext(encoder, scale, weights); var biases = NNConstants.GetNNBinaryBiases(); List <Ciphertext[][]> TestFFOutputs = new List <Ciphertext[][]>(); List <Ciphertext[][]> TestActivationInputs = new List <Ciphertext[][]>(); for (int testI = 0; testI < testX.Length; testI++) { var FFOutputs = NNConstants.GetEmptyFFCipherVectors(weights); var ActivationInputs = NNConstants.GetEmptyFFCipherVectors(weights); for (int layer = 0; layer < weights.Length; layer++) { var numLayerInputs = weights[layer].Length; var numLayerOutputs = weights[layer][0].Length; for (int ffOutputIndex = 0; ffOutputIndex < numLayerOutputs; ffOutputIndex++) { List <Ciphertext> sumInputs = new List <Ciphertext>(); //double nodeOutput = 0.0; for (int inputIndex = 0; inputIndex < numLayerInputs; inputIndex++) { if (layer == 0) { Ciphertext multResult = new Ciphertext(); evaluator.MultiplyPlain(featureCiphers[testI][inputIndex], weightsEncoded[layer][inputIndex][ffOutputIndex], multResult); sumInputs.Add(multResult); } else { Ciphertext multResult = new Ciphertext(); bool success = false; int numTries = 0; while (!success && numTries < 4) { try { evaluator.MultiplyPlain(FFOutputs[layer - 1][inputIndex], weightsEncoded[layer][inputIndex][ffOutputIndex], multResult); success = true; } catch (Exception ex) { evaluator.RelinearizeInplace(FFOutputs[layer - 1][inputIndex], relinKeys); evaluator.RescaleToNextInplace(FFOutputs[layer - 1][inputIndex]); numTries++; if (numTries >= 4) { throw ex; } } } sumInputs.Add(multResult); } } Ciphertext activationInput = new Ciphertext(); evaluator.AddMany(sumInputs, activationInput); Plaintext biasPT = new Plaintext(); encoder.Encode(biases[layer][ffOutputIndex], activationInput.Scale, biasPT); evaluator.AddPlainInplace(activationInput, biasPT); Ciphertext activationOutput = new Ciphertext(); if (layer < (weights.Length - 1)) { ActivationInputs[layer][ffOutputIndex] = activationInput; bool success = false; int numTries = 0; while (!success && numTries < 4) { try { evaluator.Square(activationInput, activationOutput); success = true; } catch (Exception ex) { evaluator.RelinearizeInplace(activationInput, relinKeys); evaluator.RescaleToNextInplace(activationInput); numTries++; if (numTries >= 4) { throw ex; } } } } else { ActivationInputs[layer][ffOutputIndex] = activationInput; // note that normally, we would do a sigmoid on this - however, we cant do a sigmoid // in the encrypted space (we are limited to simple add/multiply // so we just copy it to the output - the client will use a >/< check to determine class activationOutput = activationInput; } FFOutputs[layer][ffOutputIndex] = activationOutput; } } TestFFOutputs.Add(FFOutputs); TestActivationInputs.Add(ActivationInputs); } /* * * This represents the next border between the client and the server * The only data that should cross this line is the encrypted output of running the ML model * In this case, the output decrypts to the input of a sigmoid - so the client will determine class membership * based on a > or < 0 boolean * * */ List <List <double> > predictions = new List <List <double> >(); for (int testI = 0; testI < TestFFOutputs.Count; testI++) { Plaintext plainResult = new Plaintext(); var encryptedResult = TestFFOutputs[testI][2][0]; decryptor.Decrypt(encryptedResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); predictions.Add(result); } for (int testI = 0; testI < TestFFOutputs.Count; testI++) { var avgResult = predictions[testI].Average(); bool prediction = false; if (avgResult >= 0) { prediction = true; } Assert.AreEqual(prediction, testYs[testI]); } }
static private void ExampleCKKSEncoder() { Utilities.PrintExampleBanner("Example: Encoders / CKKS Encoder"); /* * [CKKSEncoder] (For CKKS scheme only) * * In this example we demonstrate the Cheon-Kim-Kim-Song (CKKS) scheme for * computing on encrypted real or complex numbers. We start by creating * encryption parameters for the CKKS scheme. There are two important * differences compared to the BFV scheme: * * (1) CKKS does not use the PlainModulus encryption parameter; * (2) Selecting the CoeffModulus in a specific way can be very important * when using the CKKS scheme. We will explain this further in the file * `CKKS_Basics.cs'. In this example we use CoeffModulus.Create to * generate 5 40-bit prime numbers. */ 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 }); /* * We create the SEALContext as usual and print the parameters. */ using SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); Console.WriteLine(); /* * Keys are created the same way as for the BFV scheme. */ using KeyGenerator keygen = new KeyGenerator(context); using SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); keygen.CreateRelinKeys(out RelinKeys relinKeys); /* * We also set up an Encryptor, Evaluator, and Decryptor as usual. */ using Encryptor encryptor = new Encryptor(context, publicKey); using Evaluator evaluator = new Evaluator(context); using Decryptor decryptor = new Decryptor(context, secretKey); /* * To create CKKS plaintexts we need a special encoder: there is no other way * to create them. The BatchEncoder cannot be used with the * CKKS scheme. The CKKSEncoder encodes vectors of real or complex numbers into * Plaintext objects, which can subsequently be encrypted. At a high level this * looks a lot like what BatchEncoder does for the BFV scheme, but the theory * behind it is completely different. */ using CKKSEncoder encoder = new CKKSEncoder(context); /* * In CKKS the number of slots is PolyModulusDegree / 2 and each slot encodes * one real or complex number. This should be contrasted with BatchEncoder in * the BFV scheme, where the number of slots is equal to PolyModulusDegree * and they are arranged into a matrix with two rows. */ ulong slotCount = encoder.SlotCount; Console.WriteLine($"Number of slots: {slotCount}"); /* * We create a small vector to encode; the CKKSEncoder will implicitly pad it * with zeros to full size (PolyModulusDegree / 2) when encoding. */ double[] input = new double[] { 0.0, 1.1, 2.2, 3.3 }; Console.WriteLine("Input vector: "); Utilities.PrintVector(input); /* * Now we encode it with CKKSEncoder. The floating-point coefficients of `input' * will be scaled up by the parameter `scale'. This is necessary since even in * the CKKS scheme the plaintext elements are fundamentally polynomials with * integer coefficients. It is instructive to think of the scale as determining * the bit-precision of the encoding; naturally it will affect the precision of * the result. * * In CKKS the message is stored modulo CoeffModulus (in BFV it is stored modulo * PlainModulus), so the scaled message must not get too close to the total size * of CoeffModulus. In this case our CoeffModulus is quite large (200 bits) so * we have little to worry about in this regard. For this simple example a 30-bit * scale is more than enough. */ using Plaintext plain = new Plaintext(); double scale = Math.Pow(2.0, 30); Utilities.PrintLine(); Console.WriteLine("Encode input vector."); encoder.Encode(input, scale, plain); /* * We can instantly decode to check the correctness of encoding. */ List <double> output = new List <double>(); Console.WriteLine(" + Decode input vector ...... Correct."); encoder.Decode(plain, output); Utilities.PrintVector(output); /* * The vector is encrypted the same was as in BFV. */ using Ciphertext encrypted = new Ciphertext(); Utilities.PrintLine(); Console.WriteLine("Encrypt input vector, square, and relinearize."); encryptor.Encrypt(plain, encrypted); /* * Basic operations on the ciphertexts are still easy to do. Here we square * the ciphertext, decrypt, decode, and print the result. We note also that * decoding returns a vector of full size (PolyModulusDegree / 2); this is * because of the implicit zero-padding mentioned above. */ evaluator.SquareInplace(encrypted); evaluator.RelinearizeInplace(encrypted, relinKeys); /* * We notice that the scale in the result has increased. In fact, it is now * the square of the original scale: 2^60. */ Console.WriteLine(" + Scale in squared input: {0} ({1} bits)", encrypted.Scale, (int)Math.Ceiling(Math.Log(encrypted.Scale, newBase: 2))); Utilities.PrintLine(); Console.WriteLine("Decrypt and decode."); decryptor.Decrypt(encrypted, plain); encoder.Decode(plain, output); Console.WriteLine(" + Result vector ...... Correct."); Utilities.PrintVector(output); /* * The CKKS scheme allows the scale to be reduced between encrypted computations. * This is a fundamental and critical feature that makes CKKS very powerful and * flexible. We will discuss it in great detail in `3_Levels.cs' and later in * `4_CKKS_Basics.cs'. */ }
/* * In this example we show how serialization works in Microsoft SEAL. Specifically, * we present important concepts that enable the user to optimize the data size when * communicating ciphertexts and keys for outsourced computation. Unlike the previous * examples, we organize this one in a client-server style for maximal clarity. The * server selects encryption parameters, the client generates keys, the server does * the encrypted computation, and the client decrypts. */ private static void ExampleSerialization() { Utilities.PrintExampleBanner("Example: Serialization"); /* * We require ZLIB support for this example to be available. */ if (!Serialization.IsSupportedComprMode(ComprModeType.Deflate)) { Console.WriteLine("ZLIB support is not enabled; this example is not available."); Console.WriteLine(); return; } /* * To simulate client-server interaction, we set up a shared C# stream. In real * use-cases this can be a network stream, a filestream, or any shared resource. * * It is critical to note that all data serialized by Microsoft SEAL is in binary * form, so it is not meaningful to print the data as ASCII characters. Encodings * such as Base64 would increase the data size, which is already a bottleneck in * homomorphic encryption. Hence, serialization into text is not supported or * recommended. * * In this example we use a couple of shared MemoryStreams. */ MemoryStream parmsStream = new MemoryStream(); MemoryStream dataStream = new MemoryStream(); MemoryStream skStream = new MemoryStream(); /* * The server first determines the computation and sets encryption parameters * accordingly. */ { ulong polyModulusDegree = 8192; using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 50, 20, 50 }); /* * Serialization of the encryption parameters to our shared stream is very * simple with the EncryptionParameters.Save function. */ long size = parms.Save(parmsStream); /* * Seek the parmsStream head back to beginning of the stream. */ parmsStream.Seek(0, SeekOrigin.Begin); /* * The return value of this function is the actual byte count of data written * to the stream. */ Utilities.PrintLine(); Console.WriteLine($"EncryptionParameters: wrote {size} bytes"); /* * Before moving on, we will take some time to discuss further options in * serialization. These will become particularly important when the user * needs to optimize communication and storage sizes. */ /* * It is possible to enable or disable ZLIB ("deflate") compression for * serialization by providing EncryptionParameters.Save with the desired * compression mode as in the following examples: * * long size = parms.Save(sharedStream, ComprModeType.None); * long size = parms.Save(sharedStream, ComprModeType.Deflate); * * If Microsoft SEAL is compiled with ZLIB support, the default is to use * ComprModeType.Deflate, so to instead disable compression one would use * the first version of the two. */ /* * In many cases, when working with fixed size memory, it is necessary * to know ahead of time an upper bound on the serialized data size to * allocate enough memory. This information is returned by the * EncryptionParameters.SaveSize function. This function accepts the * desired compression mode, with ComprModeType.Deflate being the default * when Microsoft SEAL is compiled with ZLIB support. * * In more detail, the output of EncryptionParameters.SaveSize is as follows: * * - Exact buffer size required for ComprModeType.None; * - Upper bound on the size required for ComprModeType.Deflate. * * As we can see from the print-out, the sizes returned by these functions * are significantly larger than the compressed size written into the shared * stream in the beginning. This is normal: compression yielded a significant * improvement in the data size, yet it is hard to estimate the size of the * compressed data. */ Utilities.PrintLine(); Console.Write("EncryptionParameters: data size upper bound (ComprModeType.None): "); Console.WriteLine(parms.SaveSize(ComprModeType.None)); Console.Write(" "); Console.Write("EncryptionParameters: data size upper bound (ComprModeType.Deflate): "); Console.WriteLine(parms.SaveSize(ComprModeType.Deflate)); /* * As an example, we now serialize the encryption parameters to a fixed * size buffer. */ MemoryStream buffer = new MemoryStream(new byte[parms.SaveSize()]); parms.Save(buffer); /* * To illustrate deserialization, we load back the encryption parameters * from our buffer into another instance of EncryptionParameters. First * we need to seek our stream back to the beginning. */ buffer.Seek(0, SeekOrigin.Begin); using EncryptionParameters parms2 = new EncryptionParameters(); parms2.Load(buffer); /* * We can check that the saved and loaded encryption parameters indeed match. */ Utilities.PrintLine(); Console.WriteLine($"EncryptionParameters: parms == parms2: {parms.Equals(parms2)}"); } /* * Client starts by loading the encryption parameters, sets up the SEALContext, * and creates the required keys. */ { using EncryptionParameters parms = new EncryptionParameters(); parms.Load(parmsStream); /* * Seek the parmsStream head back to beginning of the stream because we * will use the same stream to read the parameters repeatedly. */ parmsStream.Seek(0, SeekOrigin.Begin); using SEALContext context = new SEALContext(parms); using KeyGenerator keygen = new KeyGenerator(context); using SecretKey sk = keygen.SecretKey; using PublicKey pk = keygen.PublicKey; /* * We need to save the secret key so we can decrypt later. */ sk.Save(skStream); skStream.Seek(0, SeekOrigin.Begin); /* * In this example we will also use relinearization keys. For realinearization * and Galois keys the KeyGenerator.RelinKeys and KeyGenerator.GaloisKeys * functions return special Serializable<T> objects. These objects are meant * to be serialized and never used locally. On the other hand, for local use * of RelinKeys and GaloisKeys, the functions KeyGenerator.RelinKeysLocal * and KeyGenerator.GaloisKeysLocal can be used to create the RelinKeys * and GaloisKeys objects directly. The difference is that the Serializable<T> * objects contain a partly seeded version of the RelinKeys (or GaloisKeys) * that will result in a significantly smaller size when serialized. Using * this method has no impact on security. Such seeded RelinKeys (GaloisKeys) * must be expanded before being used in computations; this is automatically * done by deserialization. */ using Serializable <RelinKeys> rlk = keygen.RelinKeys(); /* * Before continuing, we demonstrate the significant space saving from this * method. */ long sizeRlk = rlk.Save(dataStream); using RelinKeys rlkLocal = keygen.RelinKeysLocal(); long sizeRlkLocal = rlkLocal.Save(dataStream); /* * Now compare the serialized sizes of rlk and rlkLocal. */ Utilities.PrintLine(); Console.WriteLine($"Serializable<RelinKeys>: wrote {sizeRlk} bytes"); Console.Write(" "); Console.WriteLine($"RelinKeys (local): wrote {sizeRlkLocal} bytes"); /* * Seek back in dataStream to where rlk data ended, i.e., sizeRlkLocal * bytes backwards from current position. */ dataStream.Seek(-sizeRlkLocal, SeekOrigin.Current); /* * Next set up the CKKSEncoder and Encryptor, and encrypt some numbers. */ double scale = Math.Pow(2.0, 20); CKKSEncoder encoder = new CKKSEncoder(context); using Plaintext plain1 = new Plaintext(), plain2 = new Plaintext(); encoder.Encode(2.3, scale, plain1); encoder.Encode(4.5, scale, plain2); using Encryptor encryptor = new Encryptor(context, pk); using Ciphertext encrypted1 = new Ciphertext(), encrypted2 = new Ciphertext(); encryptor.Encrypt(plain1, encrypted1); encryptor.Encrypt(plain2, encrypted2); /* * Now, we could serialize both encrypted1 and encrypted2 to dataStream * using Ciphertext.Save. However, for this example, we demonstrate another * size-saving trick that can come in handy. * * As you noticed, we set up the Encryptor using the public key. Clearly this * indicates that the CKKS scheme is a public-key encryption scheme. However, * both BFV and CKKS can operate also in a symmetric-key mode. This can be * beneficial when the public-key functionality is not exactly needed, like * in simple outsourced computation scenarios. The benefit is that in these * cases it is possible to produce ciphertexts that are partly seeded, hence * significantly smaller. Such ciphertexts must be expanded before being used * in computations; this is automatically done by deserialization. * * To use symmetric-key encryption, we need to set up the Encryptor with the * secret key instead. */ using Encryptor symEncryptor = new Encryptor(context, sk); using Serializable <Ciphertext> symEncrypted1 = symEncryptor.EncryptSymmetric(plain1); using Serializable <Ciphertext> symEncrypted2 = symEncryptor.EncryptSymmetric(plain2); /* * Before continuing, we demonstrate the significant space saving from this * method. */ long sizeSymEncrypted1 = symEncrypted1.Save(dataStream); long sizeEncrypted1 = encrypted1.Save(dataStream); /* * Now compare the serialized sizes of encrypted1 and symEncrypted1. */ Utilities.PrintLine(); Console.Write("Serializable<Ciphertext> (symmetric-key): "); Console.WriteLine($"wrote {sizeSymEncrypted1} bytes"); Console.Write(" "); Console.WriteLine($"Ciphertext (public-key): wrote {sizeEncrypted1} bytes"); /* * Seek back in dataStream to where symEncrypted1 data ended, i.e., * sizeEncrypted1 bytes backwards from current position and write * symEncrypted2 right after symEncrypted1. */ dataStream.Seek(-sizeEncrypted1, SeekOrigin.Current); symEncrypted2.Save(dataStream); dataStream.Seek(0, SeekOrigin.Begin); /* * We have seen how using KeyGenerator.RelinKeys (KeyGenerator.GaloisKeys) * can result in huge space savings over the local variants when the objects * are not needed for local use. We have seen how symmetric-key encryption * can be used to achieve much smaller ciphertext sizes when the public-key * functionality is not needed. * * We would also like to draw attention to the fact there we could easily * serialize multiple Microsoft SEAL objects sequentially in a stream. Each * object writes its own size into the stream, so deserialization knows * exactly how many bytes to read. We will see this working next. * * Finally, we would like to point out that none of these methods provide any * space savings unless Microsoft SEAL is compiled with ZLIB support, or when * serialized with ComprModeType.None. */ } /* * The server can now compute on the encrypted data. We will recreate the * SEALContext and set up an Evaluator here. */ { using EncryptionParameters parms = new EncryptionParameters(); parms.Load(parmsStream); parmsStream.Seek(0, SeekOrigin.Begin); using SEALContext context = new SEALContext(parms); using Evaluator evaluator = new Evaluator(context); /* * Next we need to load relinearization keys and the ciphertexts from our * dataStream. */ using RelinKeys rlk = new RelinKeys(); using Ciphertext encrypted1 = new Ciphertext(), encrypted2 = new Ciphertext(); /* * Deserialization is as easy as serialization. */ rlk.Load(context, dataStream); encrypted1.Load(context, dataStream); encrypted2.Load(context, dataStream); /* * Compute the product, rescale, and relinearize. */ using Ciphertext encryptedProd = new Ciphertext(); evaluator.Multiply(encrypted1, encrypted2, encryptedProd); evaluator.RelinearizeInplace(encryptedProd, rlk); evaluator.RescaleToNextInplace(encryptedProd); /* * We use dataStream to communicate encryptedProd back to the client. There * is no way to save the encryptedProd as Serializable<Ciphertext> even * though it is still a symmetric-key encryption: only freshly encrypted * ciphertexts can be seeded. Note how the size of the result ciphertext is * smaller than the size of a fresh ciphertext because it is at a lower level * due to the rescale operation. */ dataStream.Seek(0, SeekOrigin.Begin); long sizeEncryptedProd = encryptedProd.Save(dataStream); dataStream.Seek(0, SeekOrigin.Begin); Utilities.PrintLine(); Console.Write($"Ciphertext (symmetric-key): "); Console.WriteLine($"wrote {sizeEncryptedProd} bytes"); } /* * In the final step the client decrypts the result. */ { using EncryptionParameters parms = new EncryptionParameters(); parms.Load(parmsStream); parmsStream.Seek(0, SeekOrigin.Begin); using SEALContext context = new SEALContext(parms); /* * Load back the secret key from skStream. */ using SecretKey sk = new SecretKey(); sk.Load(context, skStream); using Decryptor decryptor = new Decryptor(context, sk); using CKKSEncoder encoder = new CKKSEncoder(context); using Ciphertext encryptedResult = new Ciphertext(); encryptedResult.Load(context, dataStream); using Plaintext plainResult = new Plaintext(); decryptor.Decrypt(encryptedResult, plainResult); List <double> result = new List <double>(); encoder.Decode(plainResult, result); Utilities.PrintLine(); Console.WriteLine("Result: "); Utilities.PrintVector(result, 3, 7); } /* * Finally, we give a little bit more explanation of the structure of data * serialized by Microsoft SEAL. Serialized data always starts with a 16-byte * SEALHeader struct, as defined in dotnet/src/Serialization.cs, and is * followed by the possibly compressed data for the object. * * A SEALHeader contains the following data: * * [offset 0] 2-byte magic number 0xA15E (Serialization.SEALMagic) * [offset 2] 1-byte indicating the header size in bytes (always 16) * [offset 3] 1-byte indicating the Microsoft SEAL major version number * [offset 4] 1-byte indicating the Microsoft SEAL minor version number * [offset 5] 1-byte indicating the compression mode type * [offset 6] 2-byte reserved field (unused) * [offset 8] 8-byte size in bytes of the serialized data, including the header * * Currently Microsoft SEAL supports only little-endian systems. * * As an example, we demonstrate the SEALHeader created by saving a plaintext. * Note that the SEALHeader is never compressed, so there is no need to specify * the compression mode. */ using Plaintext pt = new Plaintext("1x^2 + 3"); MemoryStream stream = new MemoryStream(); long dataSize = pt.Save(stream); /* * Seek the stream head back to beginning of the stream. */ stream.Seek(0, SeekOrigin.Begin); /* * We can now load just the SEALHeader back from the stream as follows. */ Serialization.SEALHeader header = new Serialization.SEALHeader(); Serialization.LoadHeader(stream, header); /* * Now confirm that the size of data written to stream matches with what is * indicated by the SEALHeader. */ Utilities.PrintLine(); Console.WriteLine($"Size written to stream: {dataSize} bytes"); Console.Write(" "); Console.WriteLine($"Size indicated in SEALHeader: {header.Size} bytes"); Console.WriteLine(); }