public void CreateRelinKeysTest() { RelinKeys keys = new RelinKeys(); Assert.IsNotNull(keys); Assert.AreEqual(0ul, keys.Size); }
public void GetKeyTest() { SEALContext context = GlobalContext.Context; KeyGenerator keygen = new KeyGenerator(context); RelinKeys relinKeys = keygen.RelinKeys(decompositionBitCount: 60, count: 3); Assert.IsFalse(relinKeys.HasKey(0)); Assert.IsFalse(relinKeys.HasKey(1)); Assert.IsTrue(relinKeys.HasKey(2)); Assert.IsTrue(relinKeys.HasKey(3)); Assert.IsTrue(relinKeys.HasKey(4)); Assert.IsFalse(relinKeys.HasKey(5)); Assert.ThrowsException <ArgumentOutOfRangeException>(() => relinKeys.Key(1)); List <Ciphertext> key1 = new List <Ciphertext>(relinKeys.Key(2)); Assert.AreEqual(2, key1.Count); Assert.AreEqual(2ul, key1[0].CoeffModCount); Assert.AreEqual(2ul, key1[1].CoeffModCount); List <Ciphertext> key2 = new List <Ciphertext>(relinKeys.Key(3)); Assert.AreEqual(2, key2.Count); Assert.AreEqual(2ul, key2[0].CoeffModCount); Assert.AreEqual(2ul, key2[1].CoeffModCount); List <Ciphertext> key3 = new List <Ciphertext>(relinKeys.Key(4)); Assert.AreEqual(2, key3.Count); Assert.AreEqual(2ul, key3[0].CoeffModCount); Assert.AreEqual(2ul, key3[1].CoeffModCount); }
/// <summary> /// Constructor for MainWindow /// </summary> public MainWindow() { // Initialize in background thread Task.Run(() => { KeyGenerator keygen = new KeyGenerator(GlobalProperties.Context); encryptor_ = new Encryptor(GlobalProperties.Context, keygen.PublicKey); decryptor_ = new Decryptor(GlobalProperties.Context, keygen.SecretKey); encoder_ = new BatchEncoder(GlobalProperties.Context); rlk_ = keygen.RelinKeys(decompositionBitCount: GlobalProperties.RelinKeysDBC); List <int> rotCounts = new List <int>(); for (int i = (int)encoder_.SlotCount / GlobalProperties.MatrixSizeMax; i < (int)encoder_.SlotCount / 2; i *= 2) { rotCounts.Add(i); } rotCounts.Add(0); galk_ = keygen.GaloisKeys(decompositionBitCount: GlobalProperties.GaloisKeysDBC, steps: rotCounts); // Choose the Session ID RandomizeSID(); }); InitializeComponent(); }
public void SaveLoadTest() { SEALContext context = GlobalContext.Context; KeyGenerator keygen = new KeyGenerator(context); RelinKeys keys = keygen.RelinKeys(decompositionBitCount: 30, count: 2); Assert.IsNotNull(keys); Assert.AreEqual(30, keys.DecompositionBitCount); Assert.AreEqual(2ul, keys.Size); RelinKeys other = new RelinKeys(); MemoryPoolHandle handle = other.Pool; Assert.AreEqual(0, other.DecompositionBitCount); Assert.AreEqual(0ul, other.Size); ulong alloced = handle.AllocByteCount; using (MemoryStream ms = new MemoryStream()) { keys.Save(ms); ms.Seek(offset: 0, loc: SeekOrigin.Begin); other.Load(context, ms); } Assert.AreEqual(30, other.DecompositionBitCount); Assert.AreEqual(2ul, other.Size); Assert.IsTrue(other.IsMetadataValidFor(context)); Assert.IsTrue(handle.AllocByteCount > 0ul); List <IEnumerable <Ciphertext> > keysData = new List <IEnumerable <Ciphertext> >(keys.Data); List <IEnumerable <Ciphertext> > otherData = new List <IEnumerable <Ciphertext> >(other.Data); Assert.AreEqual(keysData.Count, otherData.Count); for (int i = 0; i < keysData.Count; i++) { List <Ciphertext> keysCiphers = new List <Ciphertext>(keysData[i]); List <Ciphertext> otherCiphers = new List <Ciphertext>(otherData[i]); Assert.AreEqual(keysCiphers.Count, otherCiphers.Count); for (int j = 0; j < keysCiphers.Count; j++) { Ciphertext keysCipher = keysCiphers[j]; Ciphertext otherCipher = otherCiphers[j]; Assert.AreEqual(keysCipher.Size, otherCipher.Size); Assert.AreEqual(keysCipher.PolyModulusDegree, otherCipher.PolyModulusDegree); Assert.AreEqual(keysCipher.CoeffModCount, otherCipher.CoeffModCount); ulong coeffCount = keysCipher.Size * keysCipher.PolyModulusDegree * keysCipher.CoeffModCount; for (ulong k = 0; k < coeffCount; k++) { Assert.AreEqual(keysCipher[k], otherCipher[k]); } } } }
public void CreateRelinKeysTest() { RelinKeys keys = new RelinKeys(); Assert.IsNotNull(keys); Assert.AreEqual(0ul, keys.Size); Assert.AreEqual(0, keys.DecompositionBitCount); }
public SecureComputationController(RelinKeys Keys) { hydrationComputation = new HydrationComputation(); sleepComputation = new SleepComputation(); bmiComputation = new BMIComputation(Keys); salaryComputation = new SalaryComputation(); breaksComputation = new BreaksComputation(Keys); }
public void SeededKeyTest() { EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV) { PolyModulusDegree = 128, PlainModulus = new Modulus(1 << 6), CoeffModulus = CoeffModulus.Create(128, new int[] { 40, 40, 40 }) }; SEALContext context = new SEALContext(parms, expandModChain: false, secLevel: SecLevelType.None); KeyGenerator keygen = new KeyGenerator(context); RelinKeys relinKeys = new RelinKeys(); using (MemoryStream stream = new MemoryStream()) { keygen.CreateRelinKeys().Save(stream); stream.Seek(0, SeekOrigin.Begin); relinKeys.Load(context, stream); } keygen.CreatePublicKey(out PublicKey publicKey); Encryptor encryptor = new Encryptor(context, publicKey); Decryptor decryptor = new Decryptor(context, keygen.SecretKey); Evaluator evaluator = new Evaluator(context); Ciphertext encrypted1 = new Ciphertext(context); Ciphertext encrypted2 = new Ciphertext(context); Plaintext plain1 = new Plaintext(); Plaintext plain2 = new Plaintext(); plain1.Set(0); encryptor.Encrypt(plain1, encrypted1); evaluator.SquareInplace(encrypted1); evaluator.RelinearizeInplace(encrypted1, relinKeys); decryptor.Decrypt(encrypted1, plain2); Assert.AreEqual(1ul, plain2.CoeffCount); Assert.AreEqual(0ul, plain2[0]); plain1.Set("1x^10 + 2"); encryptor.Encrypt(plain1, encrypted1); evaluator.SquareInplace(encrypted1); evaluator.RelinearizeInplace(encrypted1, relinKeys); evaluator.SquareInplace(encrypted1); evaluator.Relinearize(encrypted1, relinKeys, encrypted2); decryptor.Decrypt(encrypted2, plain2); // {1x^40 + 8x^30 + 18x^20 + 20x^10 + 10} Assert.AreEqual(41ul, plain2.CoeffCount); Assert.AreEqual(16ul, plain2[0]); Assert.AreEqual(32ul, plain2[10]); Assert.AreEqual(24ul, plain2[20]); Assert.AreEqual(8ul, plain2[30]); Assert.AreEqual(1ul, plain2[40]); }
/// <summary> /// Convert a RelinKeys object to a Base64 string /// </summary> /// <param name="rlk">RelinKeys to convert</param> /// <returns>Base64 string representing the RelinKeys</returns> public static string RelinKeysToBase64(RelinKeys rlk) { using (MemoryStream ms = new MemoryStream()) { rlk.Save(ms); byte[] bytes = ms.ToArray(); return(Convert.ToBase64String(bytes)); } }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("Processing request: PublicKeysUpload"); string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); string sid = data?.sid; string keyType = data?.type; if (sid == null || sid.Equals(string.Empty)) { return(new BadRequestObjectResult("sid not present in request")); } if (keyType?.Equals("GaloisKeys") == true) { if (GlobalProperties.GaloisKeys.ContainsKey(sid)) { return(new BadRequestObjectResult($"{keyType} for given sid already present")); } string keystr = data?.key; if (keystr == null) { return(new BadRequestObjectResult($"{keyType} not present in request")); } GaloisKeys galk = Utilities.Base64ToGaloisKeys(keystr, GlobalProperties.Context); GlobalProperties.GaloisKeys.Add(sid, galk); } else if (keyType?.Equals("RelinKeys") == true) { if (GlobalProperties.RelinKeys.ContainsKey(sid)) { return(new BadRequestObjectResult($"{keyType} for given sid already present")); } string keystr = data?.key; if (keystr == null) { return(new BadRequestObjectResult($"{keyType} not present in request")); } RelinKeys rlk = Utilities.Base64ToRelinKeys(keystr, GlobalProperties.Context); GlobalProperties.RelinKeys.Add(sid, rlk); } else { return(new BadRequestObjectResult("Bad key type")); } return(new OkResult()); }
public void SaveLoadTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); keygen.CreateRelinKeys(out RelinKeys keys); Assert.IsNotNull(keys); Assert.AreEqual(1ul, keys.Size); RelinKeys other = new RelinKeys(); MemoryPoolHandle handle = other.Pool; Assert.AreEqual(0ul, other.Size); ulong alloced = handle.AllocByteCount; using (MemoryStream ms = new MemoryStream()) { keys.Save(ms); ms.Seek(offset: 0, loc: SeekOrigin.Begin); other.Load(context, ms); } Assert.AreEqual(1ul, other.Size); Assert.IsTrue(ValCheck.IsValidFor(other, context)); Assert.IsTrue(handle.AllocByteCount > 0ul); List <IEnumerable <PublicKey> > keysData = new List <IEnumerable <PublicKey> >(keys.Data); List <IEnumerable <PublicKey> > otherData = new List <IEnumerable <PublicKey> >(other.Data); Assert.AreEqual(keysData.Count, otherData.Count); for (int i = 0; i < keysData.Count; i++) { List <PublicKey> keysCiphers = new List <PublicKey>(keysData[i]); List <PublicKey> otherCiphers = new List <PublicKey>(otherData[i]); Assert.AreEqual(keysCiphers.Count, otherCiphers.Count); for (int j = 0; j < keysCiphers.Count; j++) { PublicKey keysCipher = keysCiphers[j]; PublicKey otherCipher = otherCiphers[j]; Assert.AreEqual(keysCipher.Data.Size, otherCipher.Data.Size); Assert.AreEqual(keysCipher.Data.PolyModulusDegree, otherCipher.Data.PolyModulusDegree); Assert.AreEqual(keysCipher.Data.CoeffModulusSize, otherCipher.Data.CoeffModulusSize); ulong coeffCount = keysCipher.Data.Size * keysCipher.Data.PolyModulusDegree * keysCipher.Data.CoeffModulusSize; for (ulong k = 0; k < coeffCount; k++) { Assert.AreEqual(keysCipher.Data[k], otherCipher.Data[k]); } } } }
/// <summary> /// Convert a Base64 string to a RelinKeys object /// </summary> /// <param name="b64">Base 64 string</param> /// <param name="context">SEALContext to verify resulting RelinKeys is valid for the SEALContext</param> /// <returns>Decoded RelinKeys</returns> public static RelinKeys Base64ToRelinKeys(string b64, SEALContext context) { RelinKeys result = new RelinKeys(); byte[] bytes = Convert.FromBase64String(b64); using (MemoryStream ms = new MemoryStream(bytes)) { result.Load(context, ms); } return(result); }
public BreaksComputation(RelinKeys Keys) { parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 60, 40, 40, 60 }); //parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20); context = new SEALContext(parms); evaluator = new Evaluator(context); encoder = new CKKSEncoder(context); SetConstants(); keys = Keys; }
//private static bool _firstTime = true; //private static Decryptor _decryptor; public SecureSvc(int nRows, double[][] vectors, double[][] coefficients, double[] intercepts, String kernel, double gamma, double coef0, ulong degree , int power) { //this._nRows = nRows; this._vectors = vectors; this._coefficients = coefficients; this._intercepts = intercepts; this._kernel = Enum.Parse<Kernel>(kernel, true); this._gamma = gamma; this._coef0 = coef0; this._degree = degree; this._power = power; EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); ulong polyModulusDegree = 16384; 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; _context = new SEALContext(parms); KeyGenerator keygen = new KeyGenerator(_context); _publicKey = keygen.PublicKey; _secretKey = keygen.SecretKey; _relinKeys = keygen.RelinKeys(); _galoisKeys = keygen.GaloisKeys(); _encryptor = new Encryptor(_context, _publicKey); _evaluator = new Evaluator(_context); _decryptor = new Decryptor(_context, _secretKey); _encoder = new CKKSEncoder(_context); }
public void GetKeyTest() { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); RelinKeys relinKeys = keygen.RelinKeys(); Assert.IsTrue(relinKeys.HasKey(2)); Assert.IsFalse(relinKeys.HasKey(3)); Assert.ThrowsException <ArgumentException>(() => relinKeys.Key(0)); Assert.ThrowsException <ArgumentException>(() => relinKeys.Key(1)); List <PublicKey> key1 = new List <PublicKey>(relinKeys.Key(2)); Assert.AreEqual(4, key1.Count); Assert.AreEqual(5ul, key1[0].Data.CoeffModCount); }
public CKKSEncryptor() { //Set scheme Primes and encryption parameters. parms = new EncryptionParameters(SchemeType.CKKS); parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 60, 40, 40, 60 }); scale = Math.Pow(2.0, 40); context = new SEALContext(parms); keygen = new KeyGenerator(context); //Generate private and public key. publicKey = keygen.PublicKey; secretKey = keygen.SecretKey; encryptor = new Encryptor(context, publicKey); encoder = new CKKSEncoder(context); KeysRelin = keygen.RelinKeys(); }
public void ExceptionsTest() { RelinKeys keys = new RelinKeys(); SEALContext context = GlobalContext.BFVContext; Utilities.AssertThrows <ArgumentNullException>(() => keys = new RelinKeys(null)); Utilities.AssertThrows <ArgumentNullException>(() => keys.Set(null)); Utilities.AssertThrows <ArgumentNullException>(() => ValCheck.IsValidFor(keys, null)); Utilities.AssertThrows <ArgumentNullException>(() => keys.Save(null)); Utilities.AssertThrows <ArgumentNullException>(() => keys.Load(context, null)); Utilities.AssertThrows <ArgumentNullException>(() => keys.Load(null, new MemoryStream())); Utilities.AssertThrows <EndOfStreamException>(() => keys.Load(context, new MemoryStream())); Utilities.AssertThrows <ArgumentNullException>(() => keys.UnsafeLoad(null, new MemoryStream())); Utilities.AssertThrows <ArgumentNullException>(() => keys.UnsafeLoad(context, null)); }
public void CreateNonEmptyRelinKeysTest() { { SEALContext context = GlobalContext.BFVContext; KeyGenerator keygen = new KeyGenerator(context); keygen.CreateRelinKeys(out RelinKeys keys); Assert.IsNotNull(keys); Assert.AreEqual(1ul, keys.Size); RelinKeys copy = new RelinKeys(keys); Assert.IsNotNull(copy); Assert.AreEqual(1ul, copy.Size); RelinKeys copy2 = new RelinKeys(); copy2.Set(keys); Assert.IsNotNull(copy2); Assert.AreEqual(1ul, copy2.Size); } { SEALContext context = GlobalContext.BGVContext; KeyGenerator keygen = new KeyGenerator(context); keygen.CreateRelinKeys(out RelinKeys keys); Assert.IsNotNull(keys); Assert.AreEqual(1ul, keys.Size); RelinKeys copy = new RelinKeys(keys); Assert.IsNotNull(copy); Assert.AreEqual(1ul, copy.Size); RelinKeys copy2 = new RelinKeys(); copy2.Set(keys); Assert.IsNotNull(copy2); Assert.AreEqual(1ul, copy2.Size); } }
public void ExceptionsTest() { RelinKeys keys = new RelinKeys(); SEALContext context = GlobalContext.Context; Assert.ThrowsException <ArgumentNullException>(() => keys = new RelinKeys(null)); Assert.ThrowsException <ArgumentNullException>(() => keys.Set(null)); Assert.ThrowsException <ArgumentNullException>(() => keys.IsValidFor(null)); Assert.ThrowsException <ArgumentNullException>(() => keys.IsMetadataValidFor(null)); Assert.ThrowsException <ArgumentNullException>(() => keys.Save(null)); Assert.ThrowsException <ArgumentNullException>(() => keys.Load(context, null)); Assert.ThrowsException <ArgumentNullException>(() => keys.Load(null, new MemoryStream())); Assert.ThrowsException <ArgumentException>(() => keys.Load(context, new MemoryStream())); Assert.ThrowsException <ArgumentNullException>(() => keys.UnsafeLoad(null)); }
public void CreateNonEmptyRelinKeysTest() { SEALContext context = GlobalContext.Context; KeyGenerator keygen = new KeyGenerator(context); RelinKeys keys = keygen.RelinKeys(decompositionBitCount: 30); Assert.IsNotNull(keys); Assert.AreEqual(30, keys.DecompositionBitCount); Assert.AreEqual(1ul, keys.Size); RelinKeys copy = new RelinKeys(keys); Assert.IsNotNull(copy); Assert.AreEqual(30, copy.DecompositionBitCount); Assert.AreEqual(1ul, copy.Size); RelinKeys copy2 = new RelinKeys(); copy2.Set(keys); Assert.IsNotNull(copy2); Assert.AreEqual(30, copy2.DecompositionBitCount); Assert.AreEqual(1ul, copy2.Size); }
//constructor for debugging , because it enables to pass secretkey which will not happen in real life // special parameters : // batchsize - number of batches sample in one ciphertext ,if the client batches mulitple samples in the ciphertet . // featureSize - number of features in sample private Svc(double[][] vectors, double[][] coefficients, double[] intercepts, String kernel, double gamma, double coef0, ulong degree, int power, PublicKey publicKey, SecretKey secretKey, RelinKeys relinKeys, GaloisKeys galoisKeys, int batchSize, int featureSize) { this._vectors = vectors; this._coefficients = coefficients; this._intercepts = intercepts; this._kernel = (Kernel)System.Enum.Parse(typeof(Kernel), kernel); this._gamma = gamma; this._coef0 = coef0; this._degree = degree; this._power = power; //Use the ckks SCheme EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); // polyModulusDegree and CoeffModulus used for general SVM algorithm and depends on the polynomial kernel degree // ( and the percision constraint ) . // My implementation can be used up to dgree = 4 , but it can be easly refactored with some oprimizations to higher // polynomial degree ulong polyModulusDegree = 16384; 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; _context = new SEALContext(parms); _publicKey = publicKey; _secretKey = secretKey; _relinKeys = relinKeys; _galoisKeys = galoisKeys; _evaluator = new Evaluator(_context); if (_secretKey != null) { _decryptor = new Decryptor(_context, _secretKey); //FOR DEBUG ONLY ( not used in real server) } _encoder = new CKKSEncoder(_context); Stopwatch serverInitStopwatch = new Stopwatch(); serverInitStopwatch.Start(); _numOfrowsCount = _vectors.Length; //Number of Support Vectors _numOfcolumnsCount = _vectors[0].Length; //Number of features in every Support vector _scale = Math.Pow(2.0, _power); // vars for batch rotations _svPlaintexts = new Plaintext[_numOfrowsCount]; //Encode support vectors _sums = new Ciphertext[_numOfrowsCount]; if (UseBatchInnerProduct) { double[] batchVectors = new double[batchSize * featureSize]; for (int i = 0; i < _numOfrowsCount; i++) { for (int k = 0; k < batchSize * featureSize; k++) { var index0 = k % featureSize; batchVectors[k] = index0 < _numOfcolumnsCount ? _vectors[i][index0] : 0; } _svPlaintexts[i] = new Plaintext(); _encoder.Encode(batchVectors, _scale, _svPlaintexts[i]); SVCUtilities.SvcUtilities.PrintScale(_svPlaintexts[i], "batch supportVectorsPlaintext" + i); _sums[i] = new Ciphertext(); } } else { ///////////////////////////////////////////////////////////////////////// // // vars for simple inner product // // Handle SV _svPlaintextsArr = new Plaintext[_numOfrowsCount, _numOfcolumnsCount]; //Encode SV for (int i = 0; i < _numOfrowsCount; i++) { for (int j = 0; j < _numOfcolumnsCount; j++) { _svPlaintextsArr[i, j] = new Plaintext(); _encoder.Encode(_vectors[i][j] != 0 ? _vectors[i][j] : Zero, _scale, _svPlaintextsArr[i, j]); SvcUtilities.PrintScale(_svPlaintextsArr[i, j], $"supportVectorsPlaintext[{i}][{j}]"); } } // Prepare sum of inner product _innerProdSums = new Ciphertext[_numOfcolumnsCount]; for (int i = 0; i < _numOfcolumnsCount; i++) { _innerProdSums[i] = new Ciphertext(); } ////////////////////////////////////////////////////////////// } // Allocate memory for svm secure calculation _kernels = new Ciphertext[_numOfrowsCount]; _decisionsArr = new Ciphertext[_numOfrowsCount]; _coefArr = new Plaintext[_numOfrowsCount]; for (int i = 0; i < _numOfrowsCount; i++) { _kernels[i] = new Ciphertext(); _decisionsArr[i] = new Ciphertext(); _coefArr[i] = new Plaintext(); } _gamaPlaintext = new Plaintext(); _encoder.Encode(_gamma != 0 ? _gamma : Zero, _scale, _gamaPlaintext); serverInitStopwatch.Stop(); Console.WriteLine($"server Init elapsed {serverInitStopwatch.ElapsedMilliseconds} ms"); }
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. */ 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. */ SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); Console.WriteLine(); /* * Keys are created the same way as for the BFV scheme. */ KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; RelinKeys relinKeys = keygen.RelinKeys(); /* * We also set up an Encryptor, Evaluator, and Decryptor as usual. */ Encryptor encryptor = new Encryptor(context, publicKey); Evaluator evaluator = new Evaluator(context); Decryptor decryptor = new Decryptor(context, secretKey); /* * To create CKKS plaintexts we need a special encoder: there is no other way * to create them. The IntegerEncoder and 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. */ 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. */ 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. */ 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'. */ }
private static void ExampleBatchEncoder() { Utilities.PrintExampleBanner("Example: Encoders / Batch Encoder"); /* * [BatchEncoder] (For BFV scheme only) * * Let N denote the PolyModulusDegree and T denote the PlainModulus. Batching * allows the BFV plaintext polynomials to be viewed as 2-by-(N/2) matrices, with * each element an integer modulo T. In the matrix view, encrypted operations act * element-wise on encrypted matrices, allowing the user to obtain speeds-ups of * several orders of magnitude in fully vectorizable computations. Thus, in all * but the simplest computations, batching should be the preferred method to use * with BFV, and when used properly will result in implementations outperforming * anything done with the IntegerEncoder. */ EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree); /* * To enable batching, we need to set the plain_modulus to be a prime number * congruent to 1 modulo 2*PolyModulusDegree. Microsoft SEAL provides a helper * method for finding such a prime. In this example we create a 20-bit prime * that supports batching. */ parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20); SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); Console.WriteLine(); /* * We can verify that batching is indeed enabled by looking at the encryption * parameter qualifiers created by SEALContext. */ var qualifiers = context.FirstContextData.Qualifiers; Console.WriteLine($"Batching enabled: {qualifiers.UsingBatching}"); KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; RelinKeys relinKeys = keygen.RelinKeys(); Encryptor encryptor = new Encryptor(context, publicKey); Evaluator evaluator = new Evaluator(context); Decryptor decryptor = new Decryptor(context, secretKey); /* * Batching is done through an instance of the BatchEncoder class. */ BatchEncoder batchEncoder = new BatchEncoder(context); /* * The total number of batching `slots' equals the PolyModulusDegree, N, and * these slots are organized into 2-by-(N/2) matrices that can be encrypted and * computed on. Each slot contains an integer modulo PlainModulus. */ ulong slotCount = batchEncoder.SlotCount; ulong rowSize = slotCount / 2; Console.WriteLine($"Plaintext matrix row size: {rowSize}"); /* * The matrix plaintext is simply given to BatchEncoder as a flattened vector * of numbers. The first `rowSize' many numbers form the first row, and the * rest form the second row. Here we create the following matrix: * * [ 0, 1, 2, 3, 0, 0, ..., 0 ] * [ 4, 5, 6, 7, 0, 0, ..., 0 ] */ ulong[] podMatrix = new ulong[slotCount]; podMatrix[0] = 0; podMatrix[1] = 1; podMatrix[2] = 2; podMatrix[3] = 3; podMatrix[rowSize] = 4; podMatrix[rowSize + 1] = 5; podMatrix[rowSize + 2] = 6; podMatrix[rowSize + 3] = 7; Console.WriteLine("Input plaintext matrix:"); Utilities.PrintMatrix(podMatrix, (int)rowSize); /* * First we use BatchEncoder to encode the matrix into a plaintext polynomial. */ Plaintext plainMatrix = new Plaintext(); Utilities.PrintLine(); Console.WriteLine("Encode plaintext matrix:"); batchEncoder.Encode(podMatrix, plainMatrix); /* * We can instantly decode to verify correctness of the encoding. Note that no * encryption or decryption has yet taken place. */ List <ulong> podResult = new List <ulong>(); Console.WriteLine(" + Decode plaintext matrix ...... Correct."); batchEncoder.Decode(plainMatrix, podResult); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Next we encrypt the encoded plaintext. */ Ciphertext encryptedMatrix = new Ciphertext(); Utilities.PrintLine(); Console.WriteLine("Encrypt plainMatrix to encryptedMatrix."); encryptor.Encrypt(plainMatrix, encryptedMatrix); Console.WriteLine(" + Noise budget in encryptedMatrix: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); /* * Operating on the ciphertext results in homomorphic operations being performed * simultaneously in all 8192 slots (matrix elements). To illustrate this, we * form another plaintext matrix * * [ 1, 2, 1, 2, 1, 2, ..., 2 ] * [ 1, 2, 1, 2, 1, 2, ..., 2 ] * * and encode it into a plaintext. */ ulong[] podMatrix2 = new ulong[slotCount]; for (ulong i = 0; i < slotCount; i++) { podMatrix2[i] = (i % 2) + 1; } Plaintext plainMatrix2 = new Plaintext(); batchEncoder.Encode(podMatrix2, plainMatrix2); Console.WriteLine(); Console.WriteLine("Second input plaintext matrix:"); Utilities.PrintMatrix(podMatrix2, (int)rowSize); /* * We now add the second (plaintext) matrix to the encrypted matrix, and square * the sum. */ Utilities.PrintLine(); Console.WriteLine("Sum, square, and relinearize."); evaluator.AddPlainInplace(encryptedMatrix, plainMatrix2); evaluator.SquareInplace(encryptedMatrix); evaluator.RelinearizeInplace(encryptedMatrix, relinKeys); /* * How much noise budget do we have left? */ Console.WriteLine(" + Noise budget in result: {0} bits", decryptor.InvariantNoiseBudget(encryptedMatrix)); /* * We decrypt and decompose the plaintext to recover the result as a matrix. */ Plaintext plainResult = new Plaintext(); Utilities.PrintLine(); Console.WriteLine("Decrypt and decode result."); decryptor.Decrypt(encryptedMatrix, plainResult); batchEncoder.Decode(plainResult, podResult); Console.WriteLine(" + Result plaintext matrix ...... Correct."); Utilities.PrintMatrix(podResult, (int)rowSize); /* * Batching allows us to efficiently use the full plaintext polynomial when the * desired encrypted computation is highly parallelizable. However, it has not * solved the other problem mentioned in the beginning of this file: each slot * holds only an integer modulo plain_modulus, and unless plain_modulus is very * large, we can quickly encounter data type overflow and get unexpected results * when integer computations are desired. Note that overflow cannot be detected * in encrypted form. The CKKS scheme (and the CKKSEncoder) addresses the data * type overflow issue, but at the cost of yielding only approximate results. */ }
private static void CKKSPerformanceTest(SEALContext context) { Stopwatch timer; Utilities.PrintParameters(context); Console.WriteLine(); bool hasZLIB = Serialization.IsSupportedComprMode(ComprModeType.ZLIB); bool hasZSTD = Serialization.IsSupportedComprMode(ComprModeType.ZSTD); using EncryptionParameters parms = context.FirstContextData.Parms; ulong polyModulusDegree = parms.PolyModulusDegree; Console.Write("Generating secret/public keys: "); using KeyGenerator keygen = new KeyGenerator(context); Console.WriteLine("Done"); using SecretKey secretKey = keygen.SecretKey; keygen.CreatePublicKey(out PublicKey publicKey); Func <RelinKeys> GetRelinKeys = () => { if (context.UsingKeyswitching) { /* * Generate relinearization keys. */ Console.Write("Generating relinearization keys: "); timer = Stopwatch.StartNew(); keygen.CreateRelinKeys(out RelinKeys relinKeys); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(relinKeys); } else { return(null); } }; Func <GaloisKeys> GetGaloisKeys = () => { if (context.UsingKeyswitching) { if (!context.KeyContextData.Qualifiers.UsingBatching) { Console.WriteLine("Given encryption parameters do not support batching."); return(null); } /* * Generate Galois keys. In larger examples the Galois keys can use a lot of * memory, which can be a problem in constrained systems. The user should * try some of the larger runs of the test and observe their effect on the * memory pool allocation size. The key generation can also take a long time, * as can be observed from the print-out. */ Console.Write($"Generating Galois keys: "); timer = Stopwatch.StartNew(); keygen.CreateGaloisKeys(out GaloisKeys galoisKeys); int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000); Console.WriteLine($"Done [{micros} microseconds]"); return(galoisKeys); } else { return(null); } }; using RelinKeys relinKeys = GetRelinKeys(); using GaloisKeys galKeys = GetGaloisKeys(); using Encryptor encryptor = new Encryptor(context, publicKey); using Decryptor decryptor = new Decryptor(context, secretKey); using Evaluator evaluator = new Evaluator(context); using CKKSEncoder ckksEncoder = new CKKSEncoder(context); Stopwatch timeEncodeSum = new Stopwatch(); Stopwatch timeDecodeSum = new Stopwatch(); Stopwatch timeEncryptSum = new Stopwatch(); Stopwatch timeDecryptSum = new Stopwatch(); Stopwatch timeAddSum = new Stopwatch(); Stopwatch timeMultiplySum = new Stopwatch(); Stopwatch timeMultiplyPlainSum = new Stopwatch(); Stopwatch timeSquareSum = new Stopwatch(); Stopwatch timeRelinearizeSum = new Stopwatch(); Stopwatch timeRescaleSum = new Stopwatch(); Stopwatch timeRotateOneStepSum = new Stopwatch(); Stopwatch timeRotateRandomSum = new Stopwatch(); Stopwatch timeConjugateSum = new Stopwatch(); Stopwatch timeSerializeSum = new Stopwatch(); Stopwatch timeSerializeZLIBSum = new Stopwatch(); Stopwatch timeSerializeZSTDSum = new Stopwatch(); Random rnd = new Random(); /* * How many times to run the test? */ int count = 10; /* * Populate a vector of floating-point values to batch. */ ulong slotCount = ckksEncoder.SlotCount; double[] podValues = new double[slotCount]; for (ulong i = 0; i < slotCount; i++) { podValues[i] = 1.001 * i; } Console.Write("Running tests "); for (int i = 0; i < count; i++) { /* * [Encoding] * For scale we use the square root of the last CoeffModulus prime * from parms. */ double scale = Math.Sqrt(parms.CoeffModulus.Last().Value); using Plaintext plain = new Plaintext(parms.PolyModulusDegree * (ulong)parms.CoeffModulus.Count(), 0); timeEncodeSum.Start(); ckksEncoder.Encode(podValues, scale, plain); timeEncodeSum.Stop(); /* * [Decoding] */ List <double> podList = new List <double>((int)slotCount); timeDecodeSum.Start(); ckksEncoder.Decode(plain, podList); timeDecodeSum.Stop(); /* * [Encryption] */ using Ciphertext encrypted = new Ciphertext(context); timeEncryptSum.Start(); encryptor.Encrypt(plain, encrypted); timeEncryptSum.Stop(); /* * [Decryption] */ using Plaintext plain2 = new Plaintext(polyModulusDegree, 0); timeDecryptSum.Start(); decryptor.Decrypt(encrypted, plain2); timeDecryptSum.Stop(); /* * [Add] */ using Ciphertext encrypted1 = new Ciphertext(context); ckksEncoder.Encode(i + 1, plain); encryptor.Encrypt(plain, encrypted1); using Ciphertext encrypted2 = new Ciphertext(context); ckksEncoder.Encode(i + 1, plain2); encryptor.Encrypt(plain2, encrypted2); timeAddSum.Start(); evaluator.AddInplace(encrypted1, encrypted2); evaluator.AddInplace(encrypted2, encrypted2); evaluator.AddInplace(encrypted1, encrypted2); timeAddSum.Stop(); /* * [Multiply] */ encrypted1.Reserve(3); timeMultiplySum.Start(); evaluator.MultiplyInplace(encrypted1, encrypted2); timeMultiplySum.Stop(); /* * [Multiply Plain] */ timeMultiplyPlainSum.Start(); evaluator.MultiplyPlainInplace(encrypted2, plain); timeMultiplyPlainSum.Stop(); /* * [Square] */ timeSquareSum.Start(); evaluator.SquareInplace(encrypted2); timeSquareSum.Stop(); if (context.UsingKeyswitching) { /* * [Relinearize] */ timeRelinearizeSum.Start(); evaluator.RelinearizeInplace(encrypted1, relinKeys); timeRelinearizeSum.Stop(); /* * [Rescale] */ timeRescaleSum.Start(); evaluator.RescaleToNextInplace(encrypted1); timeRescaleSum.Stop(); /* * [Rotate Vector] */ timeRotateOneStepSum.Start(); evaluator.RotateVectorInplace(encrypted, 1, galKeys); evaluator.RotateVectorInplace(encrypted, -1, galKeys); timeRotateOneStepSum.Stop(); /* * [Rotate Vector Random] */ // ckksEncoder.SlotCount is always a power of 2. int randomRotation = rnd.Next() & ((int)ckksEncoder.SlotCount - 1); timeRotateRandomSum.Start(); evaluator.RotateVectorInplace(encrypted, randomRotation, galKeys); timeRotateRandomSum.Stop(); /* * [Complex Conjugate] */ timeConjugateSum.Start(); evaluator.ComplexConjugateInplace(encrypted, galKeys); timeConjugateSum.Stop(); } /* * [Serialize Ciphertext] */ using MemoryStream stream = new MemoryStream(); timeSerializeSum.Start(); encrypted.Save(stream, ComprModeType.None); timeSerializeSum.Stop(); if (hasZLIB) { /* * [Serialize Ciphertext (ZLIB)] */ timeSerializeZLIBSum.Start(); encrypted.Save(stream, ComprModeType.ZLIB); timeSerializeZLIBSum.Stop(); } if (hasZSTD) { /* * [Serialize Ciphertext (Zstandard)] */ timeSerializeZSTDSum.Start(); encrypted.Save(stream, ComprModeType.ZSTD); timeSerializeZSTDSum.Stop(); } /* * Print a dot to indicate progress. */ Console.Write("."); Console.Out.Flush(); } Console.WriteLine(" Done"); Console.WriteLine(); Console.Out.Flush(); int avgEncode = (int)(timeEncodeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgDecode = (int)(timeDecodeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count)); int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count); int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRescale = (int)(timeRescaleSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRotateOneStep = (int)(timeRotateOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count)); int avgRotateRandom = (int)(timeRotateRandomSum.Elapsed.TotalMilliseconds * 1000 / count); int avgConjugate = (int)(timeConjugateSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSerializeSum = (int)(timeSerializeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSerializeZLIBSum = (int)(timeSerializeZLIBSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSerializeZSTDSum = (int)(timeSerializeZSTDSum.Elapsed.TotalMilliseconds * 1000 / count); Console.WriteLine($"Average encode: {avgEncode} microseconds"); Console.WriteLine($"Average decode: {avgDecode} microseconds"); Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds"); Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds"); Console.WriteLine($"Average add: {avgAdd} microseconds"); Console.WriteLine($"Average multiply: {avgMultiply} microseconds"); Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds"); Console.WriteLine($"Average square: {avgSquare} microseconds"); if (context.UsingKeyswitching) { Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds"); Console.WriteLine($"Average rescale: {avgRescale} microseconds"); Console.WriteLine($"Average rotate vector one step: {avgRotateOneStep} microseconds"); Console.WriteLine($"Average rotate vector random: {avgRotateRandom} microseconds"); Console.WriteLine($"Average complex conjugate: {avgConjugate} microseconds"); } Console.WriteLine($"Average serialize ciphertext: {avgSerializeSum} microseconds"); if (hasZLIB) { Console.WriteLine( $"Average compressed (ZLIB) serialize ciphertext: {avgSerializeZLIBSum} microseconds"); } if (hasZSTD) { Console.WriteLine( $"Average compressed (Zstandard) serialize ciphertext: {avgSerializeZSTDSum} microseconds"); } Console.Out.Flush(); }
private static void BFVPerformanceTest(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; using Modulus plainModulus = parms.PlainModulus; ulong polyModulusDegree = parms.PolyModulusDegree; Console.Write("Generating secret/public keys: "); using KeyGenerator keygen = new KeyGenerator(context); Console.WriteLine("Done"); using SecretKey secretKey = keygen.SecretKey; 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 BatchEncoder batchEncoder = new BatchEncoder(context); /* * These will hold the total times used by each operation. */ Stopwatch timeBatchSum = new Stopwatch(); Stopwatch timeUnbatchSum = new Stopwatch(); Stopwatch timeEncryptSum = new Stopwatch(); Stopwatch timeDecryptSum = new Stopwatch(); Stopwatch timeAddSum = new Stopwatch(); Stopwatch timeMultiplySum = new Stopwatch(); Stopwatch timeMultiplyPlainSum = new Stopwatch(); Stopwatch timeSquareSum = new Stopwatch(); Stopwatch timeRelinearizeSum = new Stopwatch(); Stopwatch timeRotateRowsOneStepSum = new Stopwatch(); Stopwatch timeRotateRowsRandomSum = new Stopwatch(); Stopwatch timeRotateColumnsSum = new Stopwatch(); Stopwatch timeSerializeSum = new Stopwatch(); Stopwatch timeSerializeZLIBSum = new Stopwatch(); Stopwatch timeSerializeZSTDSum = new Stopwatch(); /* * How many times to run the test? */ int count = 10; /* * Populate a vector of values to batch. */ ulong slotCount = batchEncoder.SlotCount; ulong[] podValues = new ulong[slotCount]; Random rnd = new Random(); for (ulong i = 0; i < batchEncoder.SlotCount; i++) { podValues[i] = plainModulus.Reduce((ulong)rnd.Next()); } Console.Write("Running tests "); for (int i = 0; i < count; i++) { /* * [Batching] * There is nothing unusual here. We batch our random plaintext matrix * into the polynomial. Note how the plaintext we create is of the exactly * right size so unnecessary reallocations are avoided. */ using Plaintext plain = new Plaintext(parms.PolyModulusDegree, 0); timeBatchSum.Start(); batchEncoder.Encode(podValues, plain); timeBatchSum.Stop(); /* * [Unbatching] * We unbatch what we just batched. */ List <ulong> podList = new List <ulong>((int)slotCount); timeUnbatchSum.Start(); batchEncoder.Decode(plain, podList); timeUnbatchSum.Stop(); if (!podList.SequenceEqual(podValues)) { throw new InvalidOperationException("Batch/unbatch failed. Something is wrong."); } /* * [Encryption] * We make sure our ciphertext is already allocated and large enough * to hold the encryption with these encryption parameters. We encrypt * our random batched matrix here. */ using Ciphertext encrypted = new Ciphertext(context); timeEncryptSum.Start(); encryptor.Encrypt(plain, encrypted); timeEncryptSum.Stop(); /* * [Decryption] * We decrypt what we just encrypted. */ using Plaintext plain2 = new Plaintext(polyModulusDegree, 0); timeDecryptSum.Start(); decryptor.Decrypt(encrypted, plain2); timeDecryptSum.Stop(); if (!plain2.Equals(plain)) { throw new InvalidOperationException("Encrypt/decrypt failed. Something is wrong."); } /* * [Add] * We create two ciphertexts and perform a few additions with them. */ using Plaintext plain1 = new Plaintext(parms.PolyModulusDegree, 0); for (ulong j = 0; j < batchEncoder.SlotCount; j++) { podValues[j] = j; } batchEncoder.Encode(podValues, plain1); for (ulong j = 0; j < batchEncoder.SlotCount; j++) { podValues[j] = j + 1; } batchEncoder.Encode(podValues, plain2); using Ciphertext encrypted1 = new Ciphertext(context); encryptor.Encrypt(plain1, encrypted1); using Ciphertext encrypted2 = new Ciphertext(context); encryptor.Encrypt(plain2, encrypted2); timeAddSum.Start(); evaluator.AddInplace(encrypted1, encrypted1); evaluator.AddInplace(encrypted2, encrypted2); evaluator.AddInplace(encrypted1, encrypted2); timeAddSum.Stop(); /* * [Multiply] * We multiply two ciphertexts. Since the size of the result will be 3, * and will overwrite the first argument, we reserve first enough memory * to avoid reallocating during multiplication. */ encrypted1.Reserve(3); timeMultiplySum.Start(); evaluator.MultiplyInplace(encrypted1, encrypted2); timeMultiplySum.Stop(); /* * [Multiply Plain] * We multiply a ciphertext with a random plaintext. Recall that * MultiplyPlain does not change the size of the ciphertext so we use * encrypted2 here. */ timeMultiplyPlainSum.Start(); evaluator.MultiplyPlainInplace(encrypted2, plain); timeMultiplyPlainSum.Stop(); /* * [Square] * We continue to use encrypted2. Now we square it; this should be * faster than generic homomorphic multiplication. */ timeSquareSum.Start(); evaluator.SquareInplace(encrypted2); timeSquareSum.Stop(); if (context.UsingKeyswitching) { /* * [Relinearize] * Time to get back to encrypted1. We now relinearize it back * to size 2. Since the allocation is currently big enough to * contain a ciphertext of size 3, no costly reallocations are * needed in the process. */ timeRelinearizeSum.Start(); evaluator.RelinearizeInplace(encrypted1, relinKeys); timeRelinearizeSum.Stop(); /* * [Rotate Rows One Step] * We rotate matrix rows by one step left and measure the time. */ timeRotateRowsOneStepSum.Start(); evaluator.RotateRowsInplace(encrypted, 1, galKeys); evaluator.RotateRowsInplace(encrypted, -1, galKeys); timeRotateRowsOneStepSum.Stop(); /* * [Rotate Rows Random] * We rotate matrix rows by a random number of steps. This is much more * expensive than rotating by just one step. */ int rowSize = (int)batchEncoder.SlotCount / 2; // rowSize is always a power of 2. int randomRotation = rnd.Next() & (rowSize - 1); timeRotateRowsRandomSum.Start(); evaluator.RotateRowsInplace(encrypted, randomRotation, galKeys); timeRotateRowsRandomSum.Stop(); /* * [Rotate Columns] * Nothing surprising here. */ timeRotateColumnsSum.Start(); evaluator.RotateColumnsInplace(encrypted, galKeys); timeRotateColumnsSum.Stop(); } /* * [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 avgBatch = (int)(timeBatchSum.Elapsed.TotalMilliseconds * 1000 / count); int avgUnbatch = (int)(timeUnbatchSum.Elapsed.TotalMilliseconds * 1000 / count); int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count); int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count)); int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count); int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count); int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRotateRowsOneStep = (int)(timeRotateRowsOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count)); int avgRotateRowsRandom = (int)(timeRotateRowsRandomSum.Elapsed.TotalMilliseconds * 1000 / count); int avgRotateColumns = (int)(timeRotateColumnsSum.Elapsed.TotalMilliseconds * 1000 / count); 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 batch: {avgBatch} microseconds"); Console.WriteLine($"Average unbatch: {avgUnbatch} microseconds"); Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds"); Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds"); Console.WriteLine($"Average add: {avgAdd} microseconds"); Console.WriteLine($"Average multiply: {avgMultiply} microseconds"); Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds"); Console.WriteLine($"Average square: {avgSquare} microseconds"); if (context.UsingKeyswitching) { Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds"); Console.WriteLine($"Average rotate rows one step: {avgRotateRowsOneStep} microseconds"); Console.WriteLine($"Average rotate rows random: {avgRotateRowsRandom} microseconds"); Console.WriteLine($"Average rotate columns: {avgRotateColumns} microseconds"); } Console.WriteLine($"Average serialize ciphertext: {avgSerializeSum} microseconds"); if (hasZLIB) { Console.WriteLine( $"Average compressed (ZLIB) serialize ciphertext: {avgSerializeZLIBSum} microseconds"); } if (hasZSTD) { Console.WriteLine( $"Average compressed (Zstandard) serialize ciphertext: {avgSerializeZSTDSum} microseconds"); } Console.Out.Flush(); }
private static void ExampleLevels() { Utilities.PrintExampleBanner("Example: Levels"); /* * In this examples we describe the concept of `levels' in BFV and CKKS and the * related objects that represent them in Microsoft SEAL. * * In Microsoft SEAL a set of encryption parameters (excluding the random number * generator) is identified uniquely by a SHA-3 hash of the parameters. This * hash is called the `ParmsId' and can be easily accessed and printed at any * time. The hash will change as soon as any of the parameters is changed. * * When a SEALContext is created from a given EncryptionParameters instance, * Microsoft SEAL automatically creates a so-called `modulus switching chain', * which is a chain of other encryption parameters derived from the original set. * The parameters in the modulus switching chain are the same as the original * parameters with the exception that size of the coefficient modulus is * decreasing going down the chain. More precisely, each parameter set in the * chain attempts to remove the last coefficient modulus prime from the * previous set; this continues until the parameter set is no longer valid * (e.g., PlainModulus is larger than the remaining CoeffModulus). It is easy * to walk through the chain and access all the parameter sets. Additionally, * each parameter set in the chain has a `chain index' that indicates its * position in the chain so that the last set has index 0. We say that a set * of encryption parameters, or an object carrying those encryption parameters, * is at a higher level in the chain than another set of parameters if its the * chain index is bigger, i.e., it is earlier in the chain. * * Each set of parameters in the chain involves unique pre-computations performed * when the SEALContext is created, and stored in a SEALContext.ContextData * object. The chain is basically a linked list of SEALContext.ContextData * objects, and can easily be accessed through the SEALContext at any time. Each * node can be identified by the ParmsId of its specific encryption parameters * (PolyModulusDegree remains the same but CoeffModulus varies). */ EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; /* * In this example we use a custom CoeffModulus, consisting of 5 primes of * sizes 50, 30, 30, 50, and 50 bits. Note that this is still OK according to * the explanation in `1_BFV_Basics.cs'. Indeed, * * CoeffModulus.MaxBitCount(polyModulusDegree) * * returns 218 (less than 50+30+30+50+50=210). * * Due to the modulus switching chain, the order of the 5 primes is significant. * The last prime has a special meaning and we call it the `special prime'. Thus, * the first parameter set in the modulus switching chain is the only one that * involves the special prime. All key objects, such as SecretKey, are created * at this highest level. All data objects, such as Ciphertext, can be only at * lower levels. The special modulus should be as large as the largest of the * other primes in the CoeffModulus, although this is not a strict requirement. * * special prime +---------+ | | v | CoeffModulus: { 50, 30, 30, 50, 50 } +---+ Level 4 (all keys; `key level') | | | CoeffModulus: { 50, 30, 30, 50 } +---+ Level 3 (highest `data level') | | | CoeffModulus: { 50, 30, 30 } +---+ Level 2 | | | CoeffModulus: { 50, 30 } +---+ Level 1 | | | CoeffModulus: { 50 } +---+ Level 0 (lowest level) */ parms.CoeffModulus = CoeffModulus.Create( polyModulusDegree, new int[] { 50, 30, 30, 50, 50 }); /* * In this example the PlainModulus does not play much of a role; we choose * some reasonable value. */ parms.PlainModulus = new SmallModulus(1 << 20); SEALContext context = new SEALContext(parms); Utilities.PrintParameters(context); /* * There are convenience method for accessing the SEALContext.ContextData for * some of the most important levels: * * SEALContext.KeyContextData: access to key level ContextData * SEALContext.FirstContextData: access to highest data level ContextData * SEALContext.LastContextData: access to lowest level ContextData * * We iterate over the chain and print the ParmsId for each set of parameters. */ Console.WriteLine(); Utilities.PrintLine(); Console.WriteLine("Print the modulus switching chain."); /* * First print the key level parameter information. */ SEALContext.ContextData contextData = context.KeyContextData; Console.WriteLine("----> Level (chain index): {0} ...... KeyContextData", contextData.ChainIndex); Console.WriteLine($" ParmsId: {contextData.ParmsId}"); Console.Write(" CoeffModulus primes: "); foreach (SmallModulus prime in contextData.Parms.CoeffModulus) { Console.Write($"{Utilities.ULongToString(prime.Value)} "); } Console.WriteLine(); Console.WriteLine("\\"); Console.Write(" \\--> "); /* * Next iterate over the remaining (data) levels. */ contextData = context.FirstContextData; while (null != contextData) { Console.Write($"Level (chain index): {contextData.ChainIndex}"); if (contextData.ParmsId.Equals(context.FirstParmsId)) { Console.WriteLine(" ...... FirstContextData"); } else if (contextData.ParmsId.Equals(context.LastParmsId)) { Console.WriteLine(" ...... LastContextData"); } else { Console.WriteLine(); } Console.WriteLine($" ParmsId: {contextData.ParmsId}"); Console.Write(" CoeffModulus primes: "); foreach (SmallModulus prime in contextData.Parms.CoeffModulus) { Console.Write($"{Utilities.ULongToString(prime.Value)} "); } Console.WriteLine(); Console.WriteLine("\\"); Console.Write(" \\--> "); /* * Step forward in the chain. */ contextData = contextData.NextContextData; } Console.WriteLine("End of chain reached"); Console.WriteLine(); /* * We create some keys and check that indeed they appear at the highest level. */ KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; RelinKeys relinKeys = keygen.RelinKeys(); GaloisKeys galoisKeys = keygen.GaloisKeys(); Utilities.PrintLine(); Console.WriteLine("Print the parameter IDs of generated elements."); Console.WriteLine($" + publicKey: {publicKey.ParmsId}"); Console.WriteLine($" + secretKey: {secretKey.ParmsId}"); Console.WriteLine($" + relinKeys: {relinKeys.ParmsId}"); Console.WriteLine($" + galoisKeys: {galoisKeys.ParmsId}"); Encryptor encryptor = new Encryptor(context, publicKey); Evaluator evaluator = new Evaluator(context); Decryptor decryptor = new Decryptor(context, secretKey); /* * In the BFV scheme plaintexts do not carry a ParmsId, but ciphertexts do. Note * how the freshly encrypted ciphertext is at the highest data level. */ Plaintext plain = new Plaintext("1x^3 + 2x^2 + 3x^1 + 4"); Ciphertext encrypted = new Ciphertext(); encryptor.Encrypt(plain, encrypted); Console.WriteLine($" + plain: {plain.ParmsId} (not set in BFV)"); Console.WriteLine($" + encrypted: {encrypted.ParmsId}"); Console.WriteLine(); /* * `Modulus switching' is a technique of changing the ciphertext parameters down * in the chain. The function Evaluator.ModSwitchToNext always switches to the * next level down the chain, whereas Evaluator.ModSwitchTo switches to a parameter * set down the chain corresponding to a given ParmsId. However, it is impossible * to switch up in the chain. */ Utilities.PrintLine(); Console.WriteLine("Perform modulus switching on encrypted and print."); contextData = context.FirstContextData; Console.Write("----> "); while (null != contextData.NextContextData) { Console.WriteLine($"Level (chain index): {contextData.ChainIndex}"); Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}"); Console.WriteLine(" Noise budget at this level: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); Console.WriteLine("\\"); Console.Write(" \\--> "); evaluator.ModSwitchToNextInplace(encrypted); contextData = contextData.NextContextData; } Console.WriteLine($"Level (chain index): {contextData.ChainIndex}"); Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}"); Console.WriteLine(" Noise budget at this level: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); Console.WriteLine("\\"); Console.Write(" \\--> "); Console.WriteLine("End of chain reached"); Console.WriteLine(); /* * At this point it is hard to see any benefit in doing this: we lost a huge * amount of noise budget (i.e., computational power) at each switch and seemed * to get nothing in return. Decryption still works. */ Utilities.PrintLine(); Console.WriteLine("Decrypt still works after modulus switching."); decryptor.Decrypt(encrypted, plain); Console.WriteLine($" + Decryption of encrypted: {plain} ...... Correct."); Console.WriteLine(); /* * However, there is a hidden benefit: the size of the ciphertext depends * linearly on the number of primes in the coefficient modulus. Thus, if there * is no need or intention to perform any further computations on a given * ciphertext, we might as well switch it down to the smallest (last) set of * parameters in the chain before sending it back to the secret key holder for * decryption. * * Also the lost noise budget is actually not as issue at all, if we do things * right, as we will see below. * * First we recreate the original ciphertext and perform some computations. */ Console.WriteLine("Computation is more efficient with modulus switching."); Utilities.PrintLine(); Console.WriteLine("Compute the fourth power."); encryptor.Encrypt(plain, encrypted); Console.WriteLine(" + Noise budget before squaring: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); evaluator.SquareInplace(encrypted); evaluator.RelinearizeInplace(encrypted, relinKeys); Console.WriteLine(" + Noise budget after squaring: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); /* * From the print-out we see that the noise budget after these computations is * just slightly below the level we would have in a fresh ciphertext after one * modulus switch (135 bits). Surprisingly, in this case modulus switching has * no effect at all on the noise budget. */ evaluator.ModSwitchToNextInplace(encrypted); Console.WriteLine(" + Noise budget after modulus switching: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); /* * This means that there is no harm at all in dropping some of the coefficient * modulus after doing enough computations. In some cases one might want to * switch to a lower level slightly earlier, actually sacrificing some of the * noise budget in the process, to gain computational performance from having * smaller parameters. We see from the print-out that the next modulus switch * should be done ideally when the noise budget is down to around 81 bits. */ evaluator.SquareInplace(encrypted); evaluator.RelinearizeInplace(encrypted, relinKeys); Console.WriteLine(" + Noise budget after squaring: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); evaluator.ModSwitchToNextInplace(encrypted); Console.WriteLine(" + Noise budget after modulus switching: {0} bits", decryptor.InvariantNoiseBudget(encrypted)); /* * At this point the ciphertext still decrypts correctly, has very small size, * and the computation was as efficient as possible. Note that the decryptor * can be used to decrypt a ciphertext at any level in the modulus switching * chain. */ decryptor.Decrypt(encrypted, plain); Console.WriteLine(" + Decryption of fourth power (hexadecimal) ...... Correct."); Console.WriteLine($" {plain}"); Console.WriteLine(); /* * In BFV modulus switching is not necessary and in some cases the user might * not want to create the modulus switching chain, except for the highest two * levels. This can be done by passing a bool `false' to SEALContext constructor. */ context = new SEALContext(parms, expandModChain: false); /* * We can check that indeed the modulus switching chain has been created only * for the highest two levels (key level and highest data level). The following * loop should execute only once. */ Console.WriteLine("Optionally disable modulus switching chain expansion."); Utilities.PrintLine(); Console.WriteLine("Print the modulus switching chain."); Console.Write("----> "); for (contextData = context.KeyContextData; null != contextData; contextData = contextData.NextContextData) { Console.WriteLine($"Level (chain index): {contextData.ChainIndex}"); Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}"); Console.Write(" CoeffModulus primes: "); foreach (SmallModulus prime in contextData.Parms.CoeffModulus) { Console.Write($"{Utilities.ULongToString(prime.Value)} "); } Console.WriteLine(); Console.WriteLine("\\"); Console.Write(" \\--> "); } Console.WriteLine("End of chain reached"); Console.WriteLine(); /* * It is very important to understand how this example works since in the CKKS * scheme modulus switching has a much more fundamental purpose and the next * examples will be difficult to understand unless these basic properties are * totally clear. */ }
private static void 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. */ }
private static void ExampleBFVBasics() { Utilities.PrintExampleBanner("Example: BFV Basics"); /* * In this example, we demonstrate performing simple computations (a polynomial * evaluation) on encrypted integers using the BFV encryption scheme. * * The first task is to set up an instance of the EncryptionParameters class. * It is critical to understand how the different parameters behave, how they * affect the encryption scheme, performance, and the security level. There are * three encryption parameters that are necessary to set: * * - PolyModulusDegree (degree of polynomial modulus); * - CoeffModulus ([ciphertext] coefficient modulus); * - PlainModulus (plaintext modulus; only for the BFV scheme). * * The BFV scheme cannot perform arbitrary computations on encrypted data. * Instead, each ciphertext has a specific quantity called the `invariant noise * budget' -- or `noise budget' for short -- measured in bits. The noise budget * in a freshly encrypted ciphertext (initial noise budget) is determined by * the encryption parameters. Homomorphic operations consume the noise budget * at a rate also determined by the encryption parameters. In BFV the two basic * operations allowed on encrypted data are additions and multiplications, of * which additions can generally be thought of as being nearly free in terms of * noise budget consumption compared to multiplications. Since noise budget * consumption compounds in sequential multiplications, the most significant * factor in choosing appropriate encryption parameters is the multiplicative * depth of the arithmetic circuit that the user wants to evaluate on encrypted * data. Once the noise budget of a ciphertext reaches zero it becomes too * corrupted to be decrypted. Thus, it is essential to choose the parameters to * be large enough to support the desired computation; otherwise the result is * impossible to make sense of even with the secret key. */ EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); /* * The first parameter we set is the degree of the `polynomial modulus'. This * must be a positive power of 2, representing the degree of a power-of-two * cyclotomic polynomial; it is not necessary to understand what this means. * * Larger PolyModulusDegree makes ciphertext sizes larger and all operations * slower, but enables more complicated encrypted computations. Recommended * values are 1024, 2048, 4096, 8192, 16384, 32768, but it is also possible * to go beyond this range. * * In this example we use a relatively small polynomial modulus. Anything * smaller than this will enable only very restricted encrypted computations. */ ulong polyModulusDegree = 4096; parms.PolyModulusDegree = polyModulusDegree; /* * Next we set the [ciphertext] `coefficient modulus' (CoeffModulus). This * parameter is a large integer, which is a product of distinct prime numbers, * numbers, each represented by an instance of the SmallModulus class. The * bit-length of CoeffModulus means the sum of the bit-lengths of its prime * factors. * * A larger CoeffModulus implies a larger noise budget, hence more encrypted * computation capabilities. However, an upper bound for the total bit-length * of the CoeffModulus is determined by the PolyModulusDegree, as follows: * +----------------------------------------------------+ | PolyModulusDegree | max CoeffModulus bit-length | +---------------------+------------------------------+ | 1024 | 27 | | 2048 | 54 | | 4096 | 109 | | 8192 | 218 | | 16384 | 438 | | 32768 | 881 | +---------------------+------------------------------+ | | These numbers can also be found in native/src/seal/util/hestdparms.h encoded | in the function SEAL_HE_STD_PARMS_128_TC, and can also be obtained from the | function | | CoeffModulus.MaxBitCount(polyModulusDegree). | | For example, if PolyModulusDegree is 4096, the coeff_modulus could consist | of three 36-bit primes (108 bits). | | Microsoft SEAL comes with helper functions for selecting the CoeffModulus. | For new users the easiest way is to simply use | | CoeffModulus.BFVDefault(polyModulusDegree), | | which returns IEnumerable<SmallModulus> consisting of a generally good choice | for the given PolyModulusDegree. */ parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree); /* * The plaintext modulus can be any positive integer, even though here we take * it to be a power of two. In fact, in many cases one might instead want it * to be a prime number; we will see this in later examples. The plaintext * modulus determines the size of the plaintext data type and the consumption * of noise budget in multiplications. Thus, it is essential to try to keep the * plaintext data type as small as possible for best performance. The noise * budget in a freshly encrypted ciphertext is * * ~ log2(CoeffModulus/PlainModulus) (bits) * * and the noise budget consumption in a homomorphic multiplication is of the * form log2(PlainModulus) + (other terms). * * The plaintext modulus is specific to the BFV scheme, and cannot be set when * using the CKKS scheme. */ parms.PlainModulus = new SmallModulus(1024); /* * Now that all parameters are set, we are ready to construct a SEALContext * object. This is a heavy class that checks the validity and properties of the * parameters we just set. */ SEALContext context = new SEALContext(parms); /* * Print the parameters that we have chosen. */ Utilities.PrintLine(); Console.WriteLine("Set encryption parameters and print"); Utilities.PrintParameters(context); Console.WriteLine(); Console.WriteLine("~~~~~~ A naive way to calculate 4(x^2+1)(x+1)^2. ~~~~~~"); /* * The encryption schemes in Microsoft SEAL are public key encryption schemes. * For users unfamiliar with this terminology, a public key encryption scheme * has a separate public key for encrypting data, and a separate secret key for * decrypting data. This way multiple parties can encrypt data using the same * shared public key, but only the proper recipient of the data can decrypt it * with the secret key. * * We are now ready to generate the secret and public keys. For this purpose * we need an instance of the KeyGenerator class. Constructing a KeyGenerator * automatically generates the public and secret key, which can immediately be * read to local variables. */ KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; /* * To be able to encrypt we need to construct an instance of Encryptor. Note * that the Encryptor only requires the public key, as expected. */ Encryptor encryptor = new Encryptor(context, publicKey); /* * Computations on the ciphertexts are performed with the Evaluator class. In * a real use-case the Evaluator would not be constructed by the same party * that holds the secret key. */ Evaluator evaluator = new Evaluator(context); /* * We will of course want to decrypt our results to verify that everything worked, * so we need to also construct an instance of Decryptor. Note that the Decryptor * requires the secret key. */ Decryptor decryptor = new Decryptor(context, secretKey); /* * As an example, we evaluate the degree 4 polynomial * * 4x^4 + 8x^3 + 8x^2 + 8x + 4 * * over an encrypted x = 6. The coefficients of the polynomial can be considered * as plaintext inputs, as we will see below. The computation is done modulo the * plain_modulus 1024. * * While this examples is simple and easy to understand, it does not have much * practical value. In later examples we will demonstrate how to compute more * efficiently on encrypted integers and real or complex numbers. * * Plaintexts in the BFV scheme are polynomials of degree less than the degree * of the polynomial modulus, and coefficients integers modulo the plaintext * modulus. For readers with background in ring theory, the plaintext space is * the polynomial quotient ring Z_T[X]/(X^N + 1), where N is PolyModulusDegree * and T is PlainModulus. * * To get started, we create a plaintext containing the constant 6. For the * plaintext element we use a constructor that takes the desired polynomial as * a string with coefficients represented as hexadecimal numbers. */ Utilities.PrintLine(); int x = 6; Plaintext xPlain = new Plaintext(x.ToString()); Console.WriteLine($"Express x = {x} as a plaintext polynomial 0x{xPlain}."); /* * We then encrypt the plaintext, producing a ciphertext. */ Utilities.PrintLine(); Ciphertext xEncrypted = new Ciphertext(); Console.WriteLine("Encrypt xPlain to xEncrypted."); encryptor.Encrypt(xPlain, xEncrypted); /* * In Microsoft SEAL, a valid ciphertext consists of two or more polynomials * whose coefficients are integers modulo the product of the primes in the * coeff_modulus. The number of polynomials in a ciphertext is called its `size' * and is given by Ciphertext.Size. A freshly encrypted ciphertext always has * size 2. */ Console.WriteLine($" + size of freshly encrypted x: {xEncrypted.Size}"); /* * There is plenty of noise budget left in this freshly encrypted ciphertext. */ Console.WriteLine(" + noise budget in freshly encrypted x: {0} bits", decryptor.InvariantNoiseBudget(xEncrypted)); /* * We decrypt the ciphertext and print the resulting plaintext in order to * demonstrate correctness of the encryption. */ Plaintext xDecrypted = new Plaintext(); Console.Write(" + decryption of encrypted_x: "); decryptor.Decrypt(xEncrypted, xDecrypted); Console.WriteLine($"0x{xDecrypted} ...... Correct."); /* * When using Microsoft SEAL, it is typically advantageous to compute in a way * that minimizes the longest chain of sequential multiplications. In other * words, encrypted computations are best evaluated in a way that minimizes * the multiplicative depth of the computation, because the total noise budget * consumption is proportional to the multiplicative depth. For example, for * our example computation it is advantageous to factorize the polynomial as * * 4x^4 + 8x^3 + 8x^2 + 8x + 4 = 4(x + 1)^2 * (x^2 + 1) * * to obtain a simple depth 2 representation. Thus, we compute (x + 1)^2 and * (x^2 + 1) separately, before multiplying them, and multiplying by 4. * * First, we compute x^2 and add a plaintext "1". We can clearly see from the * print-out that multiplication has consumed a lot of noise budget. The user * can vary the plain_modulus parameter to see its effect on the rate of noise * budget consumption. */ Utilities.PrintLine(); Console.WriteLine("Compute xSqPlusOne (x^2+1)."); Ciphertext xSqPlusOne = new Ciphertext(); evaluator.Square(xEncrypted, xSqPlusOne); Plaintext plainOne = new Plaintext("1"); evaluator.AddPlainInplace(xSqPlusOne, plainOne); /* * Encrypted multiplication results in the output ciphertext growing in size. * More precisely, if the input ciphertexts have size M and N, then the output * ciphertext after homomorphic multiplication will have size M+N-1. In this * case we perform a squaring, and observe both size growth and noise budget * consumption. */ Console.WriteLine($" + size of xSqPlusOne: {xSqPlusOne.Size}"); Console.WriteLine(" + noise budget in xSqPlusOne: {0} bits", decryptor.InvariantNoiseBudget(xSqPlusOne)); /* * Even though the size has grown, decryption works as usual as long as noise * budget has not reached 0. */ Plaintext decryptedResult = new Plaintext(); Console.Write(" + decryption of xSqPlusOne: "); decryptor.Decrypt(xSqPlusOne, decryptedResult); Console.WriteLine($"0x{decryptedResult} ...... Correct."); /* * Next, we compute (x + 1)^2. */ Utilities.PrintLine(); Console.WriteLine("Compute xPlusOneSq ((x+1)^2)."); Ciphertext xPlusOneSq = new Ciphertext(); evaluator.AddPlain(xEncrypted, plainOne, xPlusOneSq); evaluator.SquareInplace(xPlusOneSq); Console.WriteLine($" + size of xPlusOneSq: {xPlusOneSq.Size}"); Console.WriteLine(" + noise budget in xPlusOneSq: {0} bits", decryptor.InvariantNoiseBudget(xPlusOneSq)); Console.Write(" + decryption of xPlusOneSq: "); decryptor.Decrypt(xPlusOneSq, decryptedResult); Console.WriteLine($"0x{decryptedResult} ...... Correct."); /* * Finally, we multiply (x^2 + 1) * (x + 1)^2 * 4. */ Utilities.PrintLine(); Console.WriteLine("Compute encryptedResult (4(x^2+1)(x+1)^2)."); Ciphertext encryptedResult = new Ciphertext(); Plaintext plainFour = new Plaintext("4"); evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour); evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult); Console.WriteLine($" + size of encrypted_result: {encryptedResult.Size}"); Console.WriteLine(" + noise budget in encrypted_result: {0} bits", decryptor.InvariantNoiseBudget(encryptedResult)); Console.WriteLine("NOTE: Decryption can be incorrect if noise budget is zero."); Console.WriteLine(); Console.WriteLine("~~~~~~ A better way to calculate 4(x^2+1)(x+1)^2. ~~~~~~"); /* * Noise budget has reached 0, which means that decryption cannot be expected * to give the correct result. This is because both ciphertexts xSqPlusOne and * xPlusOneSq consist of 3 polynomials due to the previous squaring operations, * and homomorphic operations on large ciphertexts consume much more noise budget * than computations on small ciphertexts. Computing on smaller ciphertexts is * also computationally significantly cheaper. * * `Relinearization' is an operation that reduces the size of a ciphertext after * multiplication back to the initial size, 2. Thus, relinearizing one or both * input ciphertexts before the next multiplication can have a huge positive * impact on both noise growth and performance, even though relinearization has * a significant computational cost itself. It is only possible to relinearize * size 3 ciphertexts down to size 2, so often the user would want to relinearize * after each multiplication to keep the ciphertext sizes at 2. * * Relinearization requires special `relinearization keys', which can be thought * of as a kind of public key. Relinearization keys can easily be created with * the KeyGenerator. * * Relinearization is used similarly in both the BFV and the CKKS schemes, but * in this example we continue using BFV. We repeat our computation from before, * but this time relinearize after every multiplication. * * We use KeyGenerator.RelinKeys() to create relinearization keys. */ Utilities.PrintLine(); Console.WriteLine("Generate relinearization keys."); RelinKeys relinKeys = keygen.RelinKeys(); /* * We now repeat the computation relinearizing after each multiplication. */ Utilities.PrintLine(); Console.WriteLine("Compute and relinearize xSquared (x^2),"); Console.WriteLine(new string(' ', 13) + "then compute xSqPlusOne (x^2+1)"); Ciphertext xSquared = new Ciphertext(); evaluator.Square(xEncrypted, xSquared); Console.WriteLine($" + size of xSquared: {xSquared.Size}"); evaluator.RelinearizeInplace(xSquared, relinKeys); Console.WriteLine(" + size of xSquared (after relinearization): {0}", xSquared.Size); evaluator.AddPlain(xSquared, plainOne, xSqPlusOne); Console.WriteLine(" + noise budget in xSqPlusOne: {0} bits", decryptor.InvariantNoiseBudget(xSqPlusOne)); Console.Write(" + decryption of xSqPlusOne: "); decryptor.Decrypt(xSqPlusOne, decryptedResult); Console.WriteLine($"0x{decryptedResult} ...... Correct."); Utilities.PrintLine(); Ciphertext xPlusOne = new Ciphertext(); Console.WriteLine("Compute xPlusOne (x+1),"); Console.WriteLine(new string(' ', 13) + "then compute and relinearize xPlusOneSq ((x+1)^2)."); evaluator.AddPlain(xEncrypted, plainOne, xPlusOne); evaluator.Square(xPlusOne, xPlusOneSq); Console.WriteLine($" + size of xPlusOneSq: {xPlusOneSq.Size}"); evaluator.RelinearizeInplace(xPlusOneSq, relinKeys); Console.WriteLine(" + noise budget in xPlusOneSq: {0} bits", decryptor.InvariantNoiseBudget(xPlusOneSq)); Console.Write(" + decryption of xPlusOneSq: "); decryptor.Decrypt(xPlusOneSq, decryptedResult); Console.WriteLine($"0x{decryptedResult} ...... Correct."); Utilities.PrintLine(); Console.WriteLine("Compute and relinearize encryptedResult (4(x^2+1)(x+1)^2)."); evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour); evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult); Console.WriteLine($" + size of encryptedResult: {encryptedResult.Size}"); evaluator.RelinearizeInplace(encryptedResult, relinKeys); Console.WriteLine(" + size of encryptedResult (after relinearization): {0}", encryptedResult.Size); Console.WriteLine(" + noise budget in encryptedResult: {0} bits", decryptor.InvariantNoiseBudget(encryptedResult)); Console.WriteLine(); Console.WriteLine("NOTE: Notice the increase in remaining noise budget."); /* * Relinearization clearly improved our noise consumption. We have still plenty * of noise budget left, so we can expect the correct answer when decrypting. */ Utilities.PrintLine(); Console.WriteLine("Decrypt encrypted_result (4(x^2+1)(x+1)^2)."); decryptor.Decrypt(encryptedResult, decryptedResult); Console.WriteLine(" + decryption of 4(x^2+1)(x+1)^2 = 0x{0} ...... Correct.", decryptedResult); /* * For x=6, 4(x^2+1)(x+1)^2 = 7252. Since the plaintext modulus is set to 1024, * this result is computed in integers modulo 1024. Therefore the expected output * should be 7252 % 1024 == 84, or 0x54 in hexadecimal. */ }
public Svc(double[][] vectors, double[][] coefficients, double[] intercepts, String kernel, double gamma, double coef0, ulong degree, int power, PublicKey publicKey, RelinKeys relinKeys, GaloisKeys galoisKeys, int batchSize, int featureSize) : this(vectors, coefficients, intercepts, kernel, gamma, coef0, degree, power, publicKey, null, relinKeys, galoisKeys, batchSize, featureSize) { }
public int Predict(double[] features, int power, bool useRelinearizeInplace, bool useReScale, Stopwatch timePredictSum) { EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS); if (power < 60) { ulong polyModulusDegree = 8192; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create(polyModulusDegree, new int[] { 60, 40, 40, 60 }); } else { ulong polyModulusDegree = 16384; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.Create(polyModulusDegree, new int[] { 60, 60, 60, 60, 60, 60 }); } // double scale = Math.Pow(2.0, power); SEALContext context = new SEALContext(parms); Console.WriteLine(); KeyGenerator keygen = new KeyGenerator(context); PublicKey publicKey = keygen.PublicKey; SecretKey secretKey = keygen.SecretKey; RelinKeys relinKeys = keygen.RelinKeys(); Encryptor encryptor = new Encryptor(context, publicKey); Evaluator evaluator = new Evaluator(context); Decryptor decryptor = new Decryptor(context, secretKey); CKKSEncoder encoder = new CKKSEncoder(context); ulong slotCount = encoder.SlotCount; Console.WriteLine($"Number of slots: {slotCount}"); timePredictSum.Start(); var featuresLength = features.Length; var plaintexts = new Plaintext[featuresLength]; var featuresCiphertexts = new Ciphertext[featuresLength]; //Encode and encrypt features for (int i = 0; i < featuresLength; i++) { plaintexts[i] = new Plaintext(); encoder.Encode(features[i], scale, plaintexts[i]); SvcUtilities.PrintScale(plaintexts[i], "featurePlaintext" + i); featuresCiphertexts[i] = new Ciphertext(); encryptor.Encrypt(plaintexts[i], featuresCiphertexts[i]); SvcUtilities.PrintScale(featuresCiphertexts[i], "featurefEncrypted" + i); } // Handle SV var numOfrows = _vectors.Length; var numOfcolumns = _vectors[0].Length; var svPlaintexts = new Plaintext[numOfrows, numOfcolumns]; //Encode SV for (int i = 0; i < numOfrows; i++) { for (int j = 0; j < numOfcolumns; j++) { svPlaintexts[i, j] = new Plaintext(); encoder.Encode(_vectors[i][j], scale, svPlaintexts[i, j]); SvcUtilities.PrintScale(svPlaintexts[i, j], "supportVectorsPlaintext" + i + j); } } // Prepare sum of inner product var sums = new Ciphertext[numOfcolumns]; for (int i = 0; i < numOfcolumns; i++) { sums[i] = new Ciphertext(); } var kernels = new Ciphertext[numOfrows]; var decisionsArr = new Ciphertext[numOfrows]; var coefArr = new Plaintext [numOfrows]; for (int i = 0; i < numOfrows; i++) { kernels[i] = new Ciphertext(); decisionsArr[i] = new Ciphertext(); coefArr[i] = new Plaintext(); } // Level 1 for (int i = 0; i < numOfrows; i++) { var ciphertexts = new List <Ciphertext>(); //inner product for (int j = 0; j < numOfcolumns; j++) { evaluator.MultiplyPlain(featuresCiphertexts[j], svPlaintexts[i, j], sums[j]); if (useRelinearizeInplace) { evaluator.RelinearizeInplace(sums[j], relinKeys); } if (useReScale) { evaluator.RescaleToNextInplace(sums[j]); } SvcUtilities.PrintScale(sums[j], "tSum" + j); } evaluator.AddMany(sums, kernels[i]); evaluator.NegateInplace(kernels[i]); SvcUtilities.PrintScale(kernels[i], "kernel" + i); SvcUtilities.PrintCyprherText(decryptor, kernels[i], encoder, "kernel" + i); } // Encode coefficients : ParmsId! , scale! double scale2 = Math.Pow(2.0, power); if (useReScale) { scale2 = kernels[0].Scale; } for (int i = 0; i < numOfrows; i++) { encoder.Encode(_coefficients[0][i], scale2, coefArr[i]); SvcUtilities.PrintScale(coefArr[i], "coefPlainText+i"); } if (useReScale) { for (int i = 0; i < numOfrows; i++) { ParmsId lastParmsId = kernels[i].ParmsId; evaluator.ModSwitchToInplace(coefArr[i], lastParmsId); } } // Level 2 // Calculate decisionArr for (int i = 0; i < numOfrows; i++) { evaluator.MultiplyPlain(kernels[i], coefArr[i], decisionsArr[i]); if (useRelinearizeInplace) { evaluator.RelinearizeInplace(decisionsArr[i], relinKeys); } if (useReScale) { evaluator.RescaleToNextInplace(decisionsArr[i]); } SvcUtilities.PrintScale(decisionsArr[i], "decision" + i); SvcUtilities.PrintCyprherText(decryptor, decisionsArr[i], encoder, "decision" + i); } // Calculate decisionTotal Ciphertext decisionTotal = new Ciphertext(); //================================================================= evaluator.AddMany(decisionsArr, decisionTotal); //================================================================= SvcUtilities.PrintScale(decisionTotal, "decisionTotal"); SvcUtilities.PrintCyprherText(decryptor, decisionTotal, encoder, "decision total"); // Encode intercepts : ParmsId! , scale! Plaintext interceptsPlainText = new Plaintext(); double scale3 = Math.Pow(2.0, power * 3); if (useReScale) { scale3 = decisionTotal.Scale; } encoder.Encode(_intercepts[0], scale3, interceptsPlainText); if (useReScale) { ParmsId lastParmsId = decisionTotal.ParmsId; evaluator.ModSwitchToInplace(interceptsPlainText, lastParmsId); } SvcUtilities.PrintScale(interceptsPlainText, "interceptsPlainText"); SvcUtilities.PrintScale(decisionTotal, "decisionTotal"); //// Calculate finalTotal Ciphertext finalTotal = new Ciphertext(); //================================================================= evaluator.AddPlainInplace(decisionTotal, interceptsPlainText); //================================================================= timePredictSum.Stop(); SvcUtilities.PrintScale(decisionTotal, "decisionTotal"); //Level 3 List <double> result = SvcUtilities.PrintCyprherText(decryptor, decisionTotal, encoder, "finalTotal"); using (System.IO.StreamWriter file = new System.IO.StreamWriter( $@"{OutputDir}IrisLinear_IrisSecureSVC_total_{power}_{useRelinearizeInplace}_{useReScale}.txt", !_firstTime) ) { _firstTime = false; file.WriteLine($"{result[0]}"); } if (result[0] > 0) { return(0); } return(1); }
public void calculateDistance(string encryptedRiderXFile, string encryptedRiderYFile, string encryptedDriverXFile, string encryptedDriverYFile, string distanceFile) { using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV); ulong polyModulusDegree = 4096; parms.PolyModulusDegree = polyModulusDegree; parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree); parms.PlainModulus = new Modulus(1024); using SEALContext context = new SEALContext(parms); using KeyGenerator keygen = new KeyGenerator(context); using PublicKey riderPub = new PublicKey(); using Ciphertext xRiderEncrypted = new Ciphertext(); using (var sr = new StreamReader(encryptedRiderXFile)) { xRiderEncrypted.Load(context, sr.BaseStream); } using Ciphertext yRiderEncrypted = new Ciphertext(); using (var sr = new StreamReader(encryptedRiderYFile)) { yRiderEncrypted.Load(context, sr.BaseStream); } using Ciphertext xDriverEncrypted = new Ciphertext(); using (var sr = new StreamReader(encryptedDriverXFile)) { xDriverEncrypted.Load(context, sr.BaseStream); } using Ciphertext yDriverEncrypted = new Ciphertext(); using (var sr = new StreamReader(encryptedDriverYFile)) { yDriverEncrypted.Load(context, sr.BaseStream); } //Console.WriteLine("Calculate ( (x2-x1)^2 + (y2-y1)^2 )"); using Evaluator evaluator = new Evaluator(context); using IntegerEncoder encoder = new IntegerEncoder(context); using RelinKeys relinKeys = keygen.RelinKeysLocal(); using Ciphertext X2MinusX1 = new Ciphertext(); evaluator.Sub(xDriverEncrypted, xRiderEncrypted, X2MinusX1); // (x2-x1) using Ciphertext Y2MinusY1 = new Ciphertext(); evaluator.Sub(yDriverEncrypted, yRiderEncrypted, Y2MinusY1); // (y2-y1) using Ciphertext X2MinusX1Squared = new Ciphertext(); evaluator.Square(X2MinusX1, X2MinusX1Squared); // (x2-x1)^2 //evaluator.RelinearizeInplace(X2MinusX1Squared, relinKeys); using Ciphertext Y2MinusY1Squared = new Ciphertext(); evaluator.Square(Y2MinusY1, Y2MinusY1Squared); // (x2-x1)^2 //evaluator.RelinearizeInplace(Y2MinusY1Squared, relinKeys); using Ciphertext X2MinusX1SquaredPlusY2MinusY1Squared = new Ciphertext(); evaluator.Add(X2MinusX1Squared, Y2MinusY1Squared, X2MinusX1SquaredPlusY2MinusY1Squared); // (x2-x1)^2 + (y2-y1)^2 // using Ciphertext SquareRootOfX2MinusX1SquaredPlusY2MinusY1Squared = new Ciphertext(); // decimal vIn = 0.5M; // ulong vOut = Convert.ToUInt64(vIn); // evaluator.Exponentiate(X2MinusX1SquaredPlusY2MinusY1Squared, vOut,relinKeys,SquareRootOfX2MinusX1SquaredPlusY2MinusY1Squared); var fileStream = File.Create(distanceFile); X2MinusX1SquaredPlusY2MinusY1Squared.Save(fileStream); fileStream.Close(); }