Example #1
0
        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);
            }
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        //
        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}");
        }
Example #5
0
        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);
            }
        }
Example #6
0
        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);
            }
        }
Example #7
0
        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);
        }
Example #8
0
        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);
            }
        }
Example #9
0
        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);
        }
Example #11
0
        //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);
        }
Example #12
0
        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);
            }
        }
Example #13
0
        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);
        }
Example #14
0
        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);
            }
        }
Example #15
0
        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 ");
                }
            }
        }
Example #16
0
        /*
         * 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);
            }
        }
Example #18
0
        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);
                    }
                }
            }
        }
Example #20
0
        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();
        }
Example #21
0
        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.
             */
        }
Example #22
0
        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]);
            }
        }
Example #23
0
        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'.
             */
        }
Example #24
0
        /*
         * 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();
        }