Commit d8df7d47 authored by Hoang Gia NGUYEN's avatar Hoang Gia NGUYEN
Browse files

testing

parent c6c9d2f5

Too many changes to show.

To preserve performance only 269 of 269+ files are displayed.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace SEALNetTest
{
[TestClass]
public class IntegerEncoderTests
{
[TestMethod]
public void CreateTest()
{
IntegerEncoder encoder = new IntegerEncoder(GlobalContext.BFVContext);
Assert.IsNotNull(encoder);
Assert.AreEqual(65537ul, encoder.PlainModulus.Value);
}
[TestMethod]
public void EncodeTest()
{
IntegerEncoder encoder = new IntegerEncoder(GlobalContext.BFVContext);
Plaintext plain = encoder.Encode(10);
Assert.IsNotNull(plain);
Assert.AreEqual(4ul, plain.CoeffCount);
Assert.AreEqual(0ul, plain[0]);
Assert.AreEqual(1ul, plain[1]);
Assert.AreEqual(0ul, plain[2]);
Assert.AreEqual(1ul, plain[3]);
plain = encoder.Encode(13u);
Assert.AreEqual(4ul, plain.CoeffCount);
Assert.AreEqual(1ul, plain[0]);
Assert.AreEqual(0ul, plain[1]);
Assert.AreEqual(1ul, plain[2]);
Assert.AreEqual(1ul, plain[3]);
plain = encoder.Encode(20L);
Assert.AreEqual(5ul, plain.CoeffCount);
Assert.AreEqual(0ul, plain[0]);
Assert.AreEqual(0ul, plain[1]);
Assert.AreEqual(1ul, plain[2]);
Assert.AreEqual(0ul, plain[3]);
Assert.AreEqual(1ul, plain[4]);
plain = encoder.Encode(15ul);
Assert.AreEqual(4ul, plain.CoeffCount);
Assert.AreEqual(1ul, plain[0]);
Assert.AreEqual(1ul, plain[1]);
Assert.AreEqual(1ul, plain[2]);
Assert.AreEqual(1ul, plain[3]);
BigUInt bui = new BigUInt("AB");
plain = encoder.Encode(bui);
Assert.AreEqual(8ul, plain.CoeffCount);
Assert.AreEqual(1ul, plain[0]);
Assert.AreEqual(1ul, plain[1]);
Assert.AreEqual(0ul, plain[2]);
Assert.AreEqual(1ul, plain[3]);
Assert.AreEqual(0ul, plain[4]);
Assert.AreEqual(1ul, plain[5]);
Assert.AreEqual(0ul, plain[6]);
Assert.AreEqual(1ul, plain[7]);
Plaintext plain2 = new Plaintext();
encoder.Encode(10, plain2);
Assert.AreEqual(4ul, plain2.CoeffCount);
Assert.AreEqual(0ul, plain2[0]);
Assert.AreEqual(1ul, plain2[1]);
Assert.AreEqual(0ul, plain2[2]);
Assert.AreEqual(1ul, plain2[3]);
encoder.Encode(13u, plain2);
Assert.AreEqual(4ul, plain2.CoeffCount);
Assert.AreEqual(1ul, plain2[0]);
Assert.AreEqual(0ul, plain2[1]);
Assert.AreEqual(1ul, plain2[2]);
Assert.AreEqual(1ul, plain2[3]);
encoder.Encode(20L, plain2);
Assert.AreEqual(5ul, plain2.CoeffCount);
Assert.AreEqual(0ul, plain2[0]);
Assert.AreEqual(0ul, plain2[1]);
Assert.AreEqual(1ul, plain2[2]);
Assert.AreEqual(0ul, plain2[3]);
Assert.AreEqual(1ul, plain2[4]);
encoder.Encode(15ul, plain2);
Assert.AreEqual(4ul, plain2.CoeffCount);
Assert.AreEqual(1ul, plain2[0]);
Assert.AreEqual(1ul, plain2[1]);
Assert.AreEqual(1ul, plain2[2]);
Assert.AreEqual(1ul, plain2[3]);
encoder.Encode(bui, plain2);
Assert.AreEqual(8ul, plain2.CoeffCount);
Assert.AreEqual(1ul, plain2[0]);
Assert.AreEqual(1ul, plain2[1]);
Assert.AreEqual(0ul, plain2[2]);
Assert.AreEqual(1ul, plain2[3]);
Assert.AreEqual(0ul, plain2[4]);
Assert.AreEqual(1ul, plain2[5]);
Assert.AreEqual(0ul, plain2[6]);
Assert.AreEqual(1ul, plain2[7]);
}
[TestMethod]
public void DecodeTest()
{
IntegerEncoder encoder = new IntegerEncoder(GlobalContext.BFVContext);
Plaintext plain = new Plaintext("0x^5 + 1x^4 + 1x^3 + 1x^1 + 0");
Assert.AreEqual(6ul, plain.CoeffCount);
ulong resultU64 = encoder.DecodeUInt64(plain);
Assert.AreEqual(26UL, resultU64);
long resultI64 = encoder.DecodeInt64(plain);
Assert.AreEqual(26L, resultI64);
uint resultU32 = encoder.DecodeUInt32(plain);
Assert.AreEqual(26U, resultU32);
int resultI32 = encoder.DecodeInt32(plain);
Assert.AreEqual(26, resultI32);
BigUInt bui = encoder.DecodeBigUInt(plain);
Assert.IsNotNull(bui);
Assert.AreEqual(0, bui.CompareTo(26ul));
}
[TestMethod]
public void ExceptionsTest()
{
SEALContext context = GlobalContext.BFVContext;
SEALContext context_null = null;
IntegerEncoder enc = new IntegerEncoder(context);
BigUInt bui_null = null;
BigUInt bui = new BigUInt(5ul);
Plaintext plain = new Plaintext();
Utilities.AssertThrows<ArgumentNullException>(() => enc = new IntegerEncoder(context_null));
Utilities.AssertThrows<ArgumentException>(() => enc = new IntegerEncoder(GlobalContext.CKKSContext));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(1ul, null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(1L, null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(1, null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(1u, null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(bui_null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(bui, null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.Encode(bui_null, plain));
Utilities.AssertThrows<ArgumentNullException>(() => enc.DecodeUInt32(null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.DecodeUInt64(null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.DecodeInt32(null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.DecodeInt64(null));
Utilities.AssertThrows<ArgumentNullException>(() => enc.DecodeBigUInt(null));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
namespace SEALNetTest
{
[TestClass]
public class KeyGeneratorTests
{
[TestMethod]
public void CreateTest()
{
SEALContext context = GlobalContext.BFVContext;
KeyGenerator keygen = new KeyGenerator(context);
Assert.IsNotNull(keygen);
PublicKey pubKey = keygen.PublicKey;
SecretKey secKey = keygen.SecretKey;
Assert.IsNotNull(pubKey);
Assert.IsNotNull(secKey);
Ciphertext cipher = pubKey.Data;
Assert.IsNotNull(cipher);
Plaintext plain = secKey.Data;
Assert.IsNotNull(plain);
Assert.AreEqual(40960ul, plain.CoeffCount);
}
[TestMethod]
public void Create2Test()
{
SEALContext context = GlobalContext.BFVContext;
KeyGenerator keygen1 = new KeyGenerator(context);
Encryptor encryptor1 = new Encryptor(context, keygen1.PublicKey);
Decryptor decryptor1 = new Decryptor(context, keygen1.SecretKey);
Ciphertext cipher = new Ciphertext();
Plaintext plain = new Plaintext("2x^1 + 5");
Plaintext plain2 = new Plaintext();
encryptor1.Encrypt(plain, cipher);
decryptor1.Decrypt(cipher, plain2);
Assert.AreNotSame(plain, plain2);
Assert.AreEqual(plain, plain2);
KeyGenerator keygen2 = new KeyGenerator(context, keygen1.SecretKey);
Encryptor encryptor2 = new Encryptor(context, keygen2.PublicKey);
Decryptor decryptor2 = new Decryptor(context, keygen2.SecretKey);
Plaintext plain3 = new Plaintext();
decryptor2.Decrypt(cipher, plain3);
Assert.AreNotSame(plain, plain3);
Assert.AreEqual(plain, plain3);
}
[TestMethod]
public void KeyCopyTest()
{
SEALContext context = GlobalContext.BFVContext;
PublicKey pk = null;
SecretKey sk = null;
using (KeyGenerator keygen = new KeyGenerator(context))
{
pk = keygen.PublicKey;
sk = keygen.SecretKey;
}
ParmsId parmsIdPK = pk.ParmsId;
ParmsId parmsIdSK = sk.ParmsId;
Assert.AreEqual(parmsIdPK, parmsIdSK);
Assert.AreEqual(parmsIdPK, context.KeyParmsId);
}
[TestMethod]
public void ExceptionsTest()
{
SEALContext context = GlobalContext.BFVContext;
KeyGenerator keygen = new KeyGenerator(context);
SecretKey secret = new SecretKey();
List<uint> elts = new List<uint> { 16385 };
List<uint> elts_null = null;
List<int> steps = new List<int> { 4096 };
List<int> steps_null = null;
Utilities.AssertThrows<ArgumentNullException>(() => keygen = new KeyGenerator(null));
Utilities.AssertThrows<ArgumentNullException>(() => keygen = new KeyGenerator(context, null));
Utilities.AssertThrows<ArgumentNullException>(() => keygen = new KeyGenerator(null, keygen.SecretKey));
Utilities.AssertThrows<ArgumentException>(() => keygen = new KeyGenerator(context, secret));
Utilities.AssertThrows<ArgumentNullException>(() => keygen.GaloisKeys(elts_null));
Utilities.AssertThrows<ArgumentException>(() => keygen.GaloisKeys(elts));
Utilities.AssertThrows<ArgumentNullException>(() => keygen.GaloisKeys(steps_null));
Utilities.AssertThrows<ArgumentException>(() => keygen.GaloisKeys(steps));
EncryptionParameters smallParms = new EncryptionParameters(SchemeType.CKKS);
smallParms.PolyModulusDegree = 128;
smallParms.CoeffModulus = CoeffModulus.Create(smallParms.PolyModulusDegree, new int[] { 60 });
context = new SEALContext(smallParms, true, SecLevelType.None);
keygen = new KeyGenerator(context);
Utilities.AssertThrows<InvalidOperationException>(() => keygen.RelinKeys());
Utilities.AssertThrows<InvalidOperationException>(() => keygen.GaloisKeys());
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SEALNetTest
{
[TestClass]
public class MemoryManagerTests
{
[TestMethod]
public void SwitchProfileTest()
{
MemoryPoolHandle handle = MemoryManager.GetPool(MMProfOpt.ForceNew);
MMProfFixed fixedProfile = new MMProfFixed(handle);
MMProf oldProfile = MemoryManager.SwitchProfile(fixedProfile);
Assert.IsInstanceOfType(oldProfile, typeof(MMProfGlobal));
MMProfNew newProfile = new MMProfNew();
oldProfile = MemoryManager.SwitchProfile(newProfile);
Assert.IsInstanceOfType(oldProfile, typeof(MMProfFixed));
MMProfGlobal globalProfile = new MMProfGlobal();
oldProfile = MemoryManager.SwitchProfile(globalProfile);
Assert.IsInstanceOfType(oldProfile, typeof(MMProfNew));
MemoryPoolHandle globalHandle = globalProfile.GetPool();
Assert.IsNotNull(globalHandle);
Assert.IsTrue(globalHandle.IsInitialized);
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace SEALNetTest
{
[TestClass]
public class MemoryPoolHandleTests
{
[TestMethod]
public void CreateTest()
{
MemoryPoolHandle handle = MemoryManager.GetPool();
Assert.IsNotNull(handle);
Assert.IsTrue(handle.IsInitialized);
MemoryPoolHandle handle2 = new MemoryPoolHandle(handle);
Assert.IsTrue(handle2.IsInitialized);
Assert.AreEqual(handle.PoolCount, handle2.PoolCount);
Assert.AreEqual(handle.AllocByteCount, handle2.AllocByteCount);
MemoryPoolHandle handle5 = new MemoryPoolHandle();
handle5.Set(handle);
Assert.IsTrue(handle5.IsInitialized);
Assert.AreEqual(handle.PoolCount, handle5.PoolCount);
Assert.AreEqual(handle.AllocByteCount, handle5.AllocByteCount);
MemoryPoolHandle handle3 = MemoryManager.GetPool(MMProfOpt.ForceNew, clearOnDestruction: true);
Assert.IsNotNull(handle3);
Assert.AreEqual(0ul, handle3.PoolCount);
Assert.AreEqual(0ul, handle3.AllocByteCount);
MemoryPoolHandle handle4 = MemoryManager.GetPool(MMProfOpt.ForceThreadLocal);
Assert.IsNotNull(handle4);
Assert.AreEqual(0ul, handle4.PoolCount);
Assert.AreEqual(0ul, handle4.AllocByteCount);
}
[TestMethod]
public void EqualsTest()
{
MemoryPoolHandle handle1 = MemoryManager.GetPool(MMProfOpt.ForceNew);
MemoryPoolHandle handle2 = MemoryManager.GetPool(MMProfOpt.Default);
MemoryPoolHandle handle3 = MemoryManager.GetPool();
Assert.IsNotNull(handle1);
Assert.IsNotNull(handle2);
Assert.IsNotNull(handle3);
Assert.AreNotEqual(handle1, handle2);
Assert.AreNotEqual(handle1, handle3);
Assert.AreEqual(handle2, handle3);
Assert.AreNotEqual(handle1.GetHashCode(), handle2.GetHashCode());
Assert.IsFalse(handle3.Equals(null));
}
[TestMethod]
public void StaticMethodsTest()
{
MemoryPoolHandle handle1 = MemoryPoolHandle.Global();
Assert.IsNotNull(handle1);
MemoryPoolHandle handle2 = MemoryPoolHandle.New(clearOnDestruction: true);
Assert.IsNotNull(handle2);
MemoryPoolHandle handle3 = MemoryPoolHandle.ThreadLocal();
Assert.IsNotNull(handle3);
}
[TestMethod]
public void UseCountTest()
{
MemoryPoolHandle pool = MemoryPoolHandle.New();
Assert.AreEqual(1L, pool.UseCount);
Plaintext plain = new Plaintext(pool);
Assert.AreEqual(2L, pool.UseCount);
Plaintext plain2 = new Plaintext(pool);
Assert.AreEqual(3L, pool.UseCount);
plain.Dispose();
plain2.Dispose();
Assert.AreEqual(1L, pool.UseCount);
}
[TestMethod]
public void ExceptionsTest()
{
MemoryPoolHandle handle = new MemoryPoolHandle();
Utilities.AssertThrows<ArgumentNullException>(() => handle = new MemoryPoolHandle(null));
Utilities.AssertThrows<ArgumentNullException>(() => handle.Set(null));
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SEALNetTest
{
[TestClass]
public class ModulusTests
{
[TestMethod]
public void EmptyConstructorTest()
{
Modulus sm = new Modulus();
Assert.IsNotNull(sm);
Assert.IsTrue(sm.IsZero);
Assert.AreEqual(0ul, sm.Value);
Assert.AreEqual(0, sm.BitCount);
Assert.AreEqual(1ul, sm.UInt64Count);
Assert.IsFalse(sm.IsPrime);
}
[TestMethod]
public void ValueConstructorTest()
{
Modulus sm = new Modulus(5);
Assert.IsNotNull(sm);
Assert.IsFalse(sm.IsZero);
Assert.AreEqual(5ul, sm.Value);
Assert.AreEqual(3, sm.BitCount);
Assert.IsTrue(sm.IsPrime);
// Value is exactly 61 bits
Modulus sm2 = new Modulus(0x1FFFFFFFFFFFFFFFul);
Assert.IsNotNull(sm2);
Assert.IsFalse(sm2.IsZero);
Assert.AreEqual(0x1FFFFFFFFFFFFFFFul, sm2.Value);
Assert.AreEqual(61, sm2.BitCount);
Assert.AreEqual(1ul, sm2.UInt64Count);
Assert.IsTrue(sm2.IsPrime);
Modulus sm3 = new Modulus(0xF00000F000079ul);
Assert.IsNotNull(sm3);
Assert.IsFalse(sm3.IsZero);
Assert.AreEqual(0xF00000F000079ul, sm3.Value);
Assert.AreEqual(52, sm3.BitCount);
Assert.AreEqual(1ul, sm3.UInt64Count);
Assert.IsTrue(sm3.IsPrime);
}
[TestMethod]
public void CopyConstructorTest()
{
Modulus sm = new Modulus(10);
Modulus copy = new Modulus(sm);
Assert.IsNotNull(copy);
Assert.IsFalse(copy.IsZero);
Assert.AreEqual(10ul, copy.Value);
}
[TestMethod]
public void ExplicitCreateTest()
{
Modulus sm = (Modulus)34ul;
Assert.IsNotNull(sm);
Assert.AreEqual(34ul, sm.Value);
}
[TestMethod]
public void ConstructorFail1Test()
{
// Should fail if value is 1
Utilities.AssertThrows<ArgumentException>(() => { Modulus sm = new Modulus(1); });
}
[TestMethod]
public void ConstructorFail2Test()
{
// Should fail if value is larger than 61 bits
Utilities.AssertThrows<ArgumentException>(() => { Modulus sm = new Modulus(0x2000000000000000ul); });
}
[TestMethod]
public void SetTest()
{
Modulus sm1 = new Modulus(456);
Modulus sm2 = new Modulus();
Assert.IsFalse(sm1.IsZero);
Assert.AreEqual(456ul, sm1.Value);
Assert.IsFalse(sm1.IsPrime);
Assert.IsTrue(sm2.IsZero);
Assert.AreEqual(0ul, sm2.Value);
Assert.IsFalse(sm2.IsPrime);
sm2.Set(sm1);
Assert.IsFalse(sm2.IsZero);
Assert.AreEqual(456ul, sm2.Value);
Assert.IsFalse(sm2.IsPrime);
sm2.Set(value: 65537ul);
Assert.IsFalse(sm2.IsZero);
Assert.AreEqual(65537ul, sm2.Value);
Assert.IsTrue(sm2.IsPrime);
}
[TestMethod]
public void SetFail1Test()
{
// Should faile if set to 1
Modulus sm = new Modulus();
Utilities.AssertThrows<ArgumentException>(() => sm.Set(1));
}
[TestMethod]
public void SetFail2Test()
{
// Should fail if set to bigger than 61 bits
Modulus sm = new Modulus();
Utilities.AssertThrows<ArgumentException>(() => sm.Set(0x2000000000000000ul));
}
[TestMethod]
public void ConstRatioTest()
{
Modulus sm = new Modulus();
sm.Set(0x1234567890ABCDEFul);
Tuple<ulong, ulong, ulong> ratio = sm.ConstRatio;
Assert.IsNotNull(ratio);
Assert.AreNotEqual(0ul, ratio.Item1);
Assert.AreNotEqual(0ul, ratio.Item2);
Assert.AreNotEqual(0ul, ratio.Item3);
sm.Set(0xF00000F000079ul);
ratio = sm.ConstRatio;
Assert.IsNotNull(ratio);
Assert.AreEqual(1224979096621368355ul, ratio.Item1);
Assert.AreEqual(4369ul, ratio.Item2);
Assert.AreEqual(1144844808538997ul, ratio.Item3);
}
[TestMethod]
public void EqualsTest()
{
Modulus sm1 = new Modulus(0x12345ul);
Modulus sm2 = new Modulus(0x12345ul);
Assert.AreEqual(sm1, sm2);
Assert.AreEqual(sm1.GetHashCode(), sm2.GetHashCode());
Assert.IsTrue(sm1.Equals(0x12345ul));
Assert.IsFalse(sm1.Equals(0x1234ul));
Assert.IsFalse(sm1.Equals(null));
}
[TestMethod]
public void CompareToTest()
{
Modulus sminv = null;
Modulus sm0 = new Modulus();
Modulus sm2 = new Modulus(2);
Modulus sm5 = new Modulus(5);
Modulus smbig = new Modulus(0xFFFFFFF);
Assert.AreEqual(1, sm0.CompareTo(sminv));
Assert.AreEqual(0, sm0.CompareTo(sm0));
Assert.AreEqual(-1, sm2.CompareTo(sm5));
Assert.AreEqual(-1, sm2.CompareTo(smbig));
Assert.AreEqual(1, sm2.CompareTo(sminv));
Assert.AreEqual(0, sm5.CompareTo(sm5));
Assert.AreEqual(0, smbig.CompareTo(smbig));
Assert.AreEqual(1, smbig.CompareTo(sm0));
Assert.AreEqual(1, smbig.CompareTo(sm5));
Assert.AreEqual(1, smbig.CompareTo(sminv));
Assert.AreEqual(-1, sm5.CompareTo(6));
Assert.AreEqual(0, sm5.CompareTo(5));
Assert.AreEqual(1, sm5.CompareTo(4));
Assert.AreEqual(1, sm5.CompareTo(0));
}
[TestMethod]
public void SaveLoadTest()
{
Modulus sm1 = new Modulus(65537ul);
Modulus sm2 = new Modulus();
Assert.AreNotSame(sm1, sm2);
Assert.AreNotEqual(sm1, sm2);
Assert.AreNotEqual(sm1.IsPrime, sm2.IsPrime);
using (MemoryStream stream = new MemoryStream())
{
sm1.Save(stream);
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
sm2.Load(stream);
}
Assert.AreNotSame(sm1, sm2);
Assert.AreEqual(sm1, sm2);
Assert.AreEqual(sm1.BitCount, sm2.BitCount);
Assert.AreEqual(sm1.UInt64Count, sm2.UInt64Count);
Assert.AreEqual(sm1.ConstRatio.Item1, sm2.ConstRatio.Item1);
Assert.AreEqual(sm1.ConstRatio.Item2, sm2.ConstRatio.Item2);
Assert.AreEqual(sm1.ConstRatio.Item3, sm2.ConstRatio.Item3);
Assert.AreEqual(sm1.IsPrime, sm2.IsPrime);
}
[TestMethod]
public void CreateTest()
{
List<Modulus> cm = (List<Modulus>)CoeffModulus.Create(2, new int[]{ });
Assert.AreEqual(0, cm.Count);
cm = (List<Modulus>)CoeffModulus.Create(2, new int[] { 3 });
Assert.AreEqual(1, cm.Count);
Assert.AreEqual(5ul, cm[0].Value);
cm = (List<Modulus>)CoeffModulus.Create(2, new int[] { 3, 4 });
Assert.AreEqual(2, cm.Count);
Assert.AreEqual(5ul, cm[0].Value);
Assert.AreEqual(13ul, cm[1].Value);
cm = (List<Modulus>)CoeffModulus.Create(2, new int[] { 3, 5, 4, 5 });
Assert.AreEqual(4, cm.Count);
Assert.AreEqual(5ul, cm[0].Value);
Assert.AreEqual(17ul, cm[1].Value);
Assert.AreEqual(13ul, cm[2].Value);
Assert.AreEqual(29ul, cm[3].Value);
cm = (List<Modulus>)CoeffModulus.Create(32, new int[] { 30, 40, 30, 30, 40 });
Assert.AreEqual(5, cm.Count);
Assert.AreEqual(30, (int)(Math.Log(cm[0].Value, 2)) + 1);
Assert.AreEqual(40, (int)(Math.Log(cm[1].Value, 2)) + 1);
Assert.AreEqual(30, (int)(Math.Log(cm[2].Value, 2)) + 1);
Assert.AreEqual(30, (int)(Math.Log(cm[3].Value, 2)) + 1);
Assert.AreEqual(40, (int)(Math.Log(cm[4].Value, 2)) + 1);
Assert.AreEqual(1ul, cm[0].Value % 64);
Assert.AreEqual(1ul, cm[1].Value % 64);
Assert.AreEqual(1ul, cm[2].Value % 64);
Assert.AreEqual(1ul, cm[3].Value % 64);
Assert.AreEqual(1ul, cm[4].Value % 64);
}
[TestMethod]
public void ExceptionsTest()
{
Modulus sm = new Modulus(0x12345ul);
Utilities.AssertThrows<ArgumentNullException>(() => sm = new Modulus(null));
Utilities.AssertThrows<ArgumentNullException>(() => sm.Set(null));
Utilities.AssertThrows<ArgumentNullException>(() => sm.Save(null));
Utilities.AssertThrows<ArgumentNullException>(() => sm.Load(null));
Utilities.AssertThrows<EndOfStreamException>(() => sm.Load(new MemoryStream()));
// Too small polyModulusDegree
Utilities.AssertThrows<ArgumentException>(() => CoeffModulus.Create(1, new int[] { 2 }));
// Too large polyModulusDegree
Utilities.AssertThrows<ArgumentException>(() => CoeffModulus.Create(262144, new int[] { 30 }));
// Invalid polyModulusDegree
Utilities.AssertThrows<ArgumentException>(() => CoeffModulus.Create(1023, new int[] { 20 }));
// Invalid bitSize
Utilities.AssertThrows<ArgumentException>(() => CoeffModulus.Create(2048, new int[] { 0 }));
Utilities.AssertThrows<ArgumentException>(() => CoeffModulus.Create(2048, new int[] { -30 }));
Utilities.AssertThrows<ArgumentException>(() => CoeffModulus.Create(2048, new int[] { 30, -30 }));
// Too small primes requested
Utilities.AssertThrows<InvalidOperationException>(() => CoeffModulus.Create(2, new int[] { 2 }));
Utilities.AssertThrows<InvalidOperationException>(() => CoeffModulus.Create(2, new int[] { 3, 3, 3 }));
Utilities.AssertThrows<InvalidOperationException>(() => CoeffModulus.Create(1024, new int[] { 8 }));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.Research.SEAL.Tools;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace SEALNetTest
{
[TestClass]
public class NativeObjectTests
{
[TestMethod]
public void IsDisposedTest()
{
Ciphertext cipher = new Ciphertext();
Assert.IsNotNull(cipher);
Assert.AreEqual(0ul, cipher.Size);
Assert.AreEqual(0ul, cipher.PolyModulusDegree);
Assert.AreEqual(0ul, cipher.CoeffModulusSize);
// After disposing object, accessing any field should fail.
cipher.Dispose();
Utilities.AssertThrows<ObjectDisposedException>(() => cipher.Size);
Utilities.AssertThrows<ObjectDisposedException>(() => cipher.PolyModulusDegree);
Utilities.AssertThrows<ObjectDisposedException>(() => cipher.CoeffModulusSize);
Utilities.AssertThrows<ObjectDisposedException>(() => cipher.IsTransparent);
Utilities.AssertThrows<ObjectDisposedException>(() => cipher.IsNTTForm);
}
}
}
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace SEALNetTest
{
[TestClass]
public class ParmsIdTests
{
[TestMethod]
public void ParamIDConstructorTest()
{
ParmsId id = new ParmsId();
Assert.AreEqual(0ul, id.Block[0]);
Assert.AreEqual(0ul, id.Block[1]);
Assert.AreEqual(0ul, id.Block[2]);
Assert.AreEqual(0ul, id.Block[3]);
id.Block[0] = 5;
id.Block[1] = 4;
id.Block[2] = 3;
id.Block[3] = 2;
ParmsId id2 = new ParmsId(id);
id.Block[1] = 7;
Assert.AreEqual(5ul, id2.Block[0]);
Assert.AreEqual(4ul, id2.Block[1]);
Assert.AreEqual(3ul, id2.Block[2]);
Assert.AreEqual(2ul, id2.Block[3]);
Assert.AreEqual(7ul, id.Block[1]);
Assert.IsFalse(id2.Equals(null));
Assert.AreNotEqual(id.GetHashCode(), id2.GetHashCode());
}
[TestMethod]
public void ToStringTest()
{
ParmsId id = new ParmsId();
id.Block[0] = 1;
id.Block[1] = 2;
id.Block[2] = 3;
id.Block[3] = 4;
Assert.AreEqual("0000000000000001 0000000000000002 0000000000000003 0000000000000004", id.ToString());
}
[TestMethod]
public void OperatorsTest()
{
ParmsId id = new ParmsId();
id.Block[0] = 1;
id.Block[1] = 2;
id.Block[2] = 3;
id.Block[3] = 4;
ParmsId id2 = new ParmsId(id);
ParmsId id3 = new ParmsId(id);
id3.Block[0] = 2;
Assert.IsTrue(id == id2);
Assert.IsFalse(id == id3);
ParmsId id_null1 = null;
ParmsId id_null2 = null;
Assert.IsFalse(id_null1 != id_null2);
Assert.IsTrue(id_null1 != id);
}
[TestMethod]
public void ExceptionsTest()
{
ParmsId id = new ParmsId();
ParmsId id_null = null;
Utilities.AssertThrows<ArgumentNullException>(() => id = new ParmsId(id_null));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
namespace SEALNetTest
{
[TestClass]
public class PlaintextTests
{
[TestMethod]
public void CreateTest()
{
Plaintext plain = new Plaintext();
Assert.IsNotNull(plain);
Assert.AreEqual(0ul, plain.CoeffCount);
Plaintext plain2 = new Plaintext(capacity: 20, coeffCount: 10);
Assert.IsNotNull(plain2);
Assert.AreEqual(20ul, plain2.Capacity);
Assert.AreEqual(10ul, plain2.CoeffCount);
Plaintext plain3 = new Plaintext();
plain3.Set(plain2);
Assert.IsNotNull(plain3);
Assert.AreEqual(10ul, plain3.Capacity);
Assert.AreEqual(10ul, plain3.CoeffCount);
}
[TestMethod]
public void CreateWithHexTest()
{
Plaintext plain = new Plaintext("6x^5 + 5x^4 + 3x^2 + 2x^1 + 1");
Assert.IsNotNull(plain);
Assert.AreEqual(6ul, plain.CoeffCount);
Assert.AreEqual(5ul, plain.NonZeroCoeffCount);
Assert.AreEqual(1ul, plain[0]);
Assert.AreEqual(2ul, plain[1]);
Assert.AreEqual(3ul, plain[2]);
Assert.AreEqual(0ul, plain[3]);
Assert.AreEqual(5ul, plain[4]);
Assert.AreEqual(6ul, plain[5]);
Plaintext plain2 = new Plaintext("6x^5 + 5x^4 + 3x^2 + 2x^1");
Assert.IsNotNull(plain);
Assert.AreEqual(6ul, plain2.CoeffCount);
Assert.AreEqual(4ul, plain2.NonZeroCoeffCount);
Assert.AreEqual(0ul, plain2[0]);
Assert.AreEqual(2ul, plain2[1]);
Assert.AreEqual(3ul, plain2[2]);
Assert.AreEqual(0ul, plain2[3]);
Assert.AreEqual(5ul, plain2[4]);
Assert.AreEqual(6ul, plain2[5]);
}
[TestMethod]
public void CopyTest()
{
Plaintext plain = new Plaintext("6x^5 + 5x^4 + 3x^2 + 2x^1 + 1");
Assert.IsFalse(plain.IsNTTForm);
Plaintext plain2 = new Plaintext(plain);
Assert.AreEqual(plain, plain2);
Assert.IsFalse(plain2.IsNTTForm);
Assert.AreEqual(plain.ParmsId, plain2.ParmsId);
SEALContext context = GlobalContext.BFVContext;
Evaluator evaluator = new Evaluator(context);
evaluator.TransformToNTTInplace(plain, context.FirstParmsId);
Assert.IsTrue(plain.IsNTTForm);
Assert.IsFalse(plain2.IsNTTForm);
Assert.AreNotEqual(plain.ParmsId, plain2.ParmsId);
Assert.AreEqual(plain.ParmsId, context.FirstParmsId);
Plaintext plain3 = new Plaintext(plain);
Assert.AreEqual(plain3, plain);
Assert.IsTrue(plain3.IsNTTForm);
Assert.AreEqual(plain3.ParmsId, context.FirstParmsId);
}
[TestMethod]
public void ToStringTest()
{
Plaintext plain = new Plaintext(coeffCount: 6);
plain[0] = 1;
plain[1] = 2;
plain[2] = 3;
plain[3] = 0;
plain[4] = 5;
plain[5] = 6;
Assert.AreEqual(6ul, plain.CoeffCount);
Assert.AreEqual(5ul, plain.NonZeroCoeffCount);
string str = plain.ToString();
Assert.AreEqual("6x^5 + 5x^4 + 3x^2 + 2x^1 + 1", str);
}
[TestMethod]
public void SetZeroTest()
{
Plaintext plain = new Plaintext(coeffCount: 10);
plain[0] = 1;
plain[1] = 2;
plain[2] = 3;
plain[3] = 4;
plain[4] = 5;
plain[5] = 6;
plain[6] = 7;
plain[7] = 8;
plain[8] = 9;
plain[9] = 10;
plain.SetZero(6, 3);
Assert.AreEqual(1ul, plain[0]);
Assert.AreEqual(2ul, plain[1]);
Assert.AreEqual(3ul, plain[2]);
Assert.AreEqual(4ul, plain[3]);
Assert.AreEqual(5ul, plain[4]);
Assert.AreEqual(6ul, plain[5]);
Assert.AreEqual(0ul, plain[6]);
Assert.AreEqual(0ul, plain[7]);
Assert.AreEqual(0ul, plain[8]);
Assert.AreEqual(10ul, plain[9]);
plain[0] = 1;
plain[1] = 2;
plain[2] = 3;
plain[3] = 4;
plain[4] = 5;
plain[5] = 6;
plain[6] = 7;
plain[7] = 8;
plain[8] = 9;
plain[9] = 10;
plain.SetZero(4);
Assert.AreEqual(1ul, plain[0]);
Assert.AreEqual(2ul, plain[1]);
Assert.AreEqual(3ul, plain[2]);
Assert.AreEqual(4ul, plain[3]);
Assert.AreEqual(0ul, plain[4]);
Assert.AreEqual(0ul, plain[5]);
Assert.AreEqual(0ul, plain[6]);
Assert.AreEqual(0ul, plain[7]);
Assert.AreEqual(0ul, plain[8]);
Assert.AreEqual(0ul, plain[9]);
plain[0] = 1;
plain[1] = 2;
plain[2] = 3;
plain[3] = 4;
plain[4] = 5;
plain[5] = 6;
plain[6] = 7;
plain[7] = 8;
plain[8] = 9;
plain[9] = 10;
plain.SetZero();
Assert.AreEqual(0ul, plain[0]);
Assert.AreEqual(0ul, plain[1]);
Assert.AreEqual(0ul, plain[2]);
Assert.AreEqual(0ul, plain[3]);
Assert.AreEqual(0ul, plain[4]);
Assert.AreEqual(0ul, plain[5]);
Assert.AreEqual(0ul, plain[6]);
Assert.AreEqual(0ul, plain[7]);
Assert.AreEqual(0ul, plain[8]);
Assert.AreEqual(0ul, plain[9]);
}
[TestMethod]
public void ReserveResizeTest()
{
Plaintext plain = new Plaintext();
MemoryPoolHandle handle = plain.Pool;
Assert.AreEqual(0ul, plain.CoeffCount);
Assert.AreEqual(0ul, plain.NonZeroCoeffCount);
Assert.AreEqual(0ul, plain.Capacity);
plain.Reserve(capacity: 10);
ulong alloced = handle.AllocByteCount;
Assert.IsTrue(alloced > 0ul);
Assert.AreEqual(0ul, plain.CoeffCount);
Assert.AreEqual(0ul, plain.NonZeroCoeffCount);
Assert.AreEqual(10ul, plain.Capacity);
plain.Resize(coeffCount: 11);
Assert.AreEqual(11ul, plain.CoeffCount);
Assert.AreEqual(11ul, plain.Capacity);
Assert.AreEqual(0ul, plain.SignificantCoeffCount);
Assert.AreEqual(0ul, plain.NonZeroCoeffCount);
Assert.IsTrue(handle.AllocByteCount > 0ul);
}
[TestMethod]
public void ShrinkToFitTest()
{
Plaintext plain = new Plaintext();
plain.Reserve(10000);
Assert.AreEqual(10000ul, plain.Capacity);
Assert.AreEqual(0ul, plain.CoeffCount);
Assert.AreEqual(0ul, plain.NonZeroCoeffCount);
plain.Set("1");
Assert.AreEqual(10000ul, plain.Capacity);
Assert.AreEqual(1ul, plain.CoeffCount);
Assert.AreEqual(1ul, plain.NonZeroCoeffCount);
Assert.AreEqual(1ul, plain.SignificantCoeffCount);
plain.ShrinkToFit();
Assert.AreEqual(1ul, plain.Capacity);
Assert.AreEqual(1ul, plain.CoeffCount);
Assert.AreEqual(1ul, plain[0]);
}
[TestMethod]
public void ReleaseTest()
{
Plaintext plain = new Plaintext();
plain.Reserve(10000);
plain.Set("3x^2 + 4x^1");
Assert.AreEqual(10000ul, plain.Capacity);
Assert.AreEqual(3ul, plain.CoeffCount);
Assert.AreEqual(2ul, plain.NonZeroCoeffCount);
plain.Release();
Assert.AreEqual(0ul, plain.Capacity);
Assert.AreEqual(0ul, plain.CoeffCount);
Assert.AreEqual(0ul, plain.NonZeroCoeffCount);
}
[TestMethod]
public void EqualsTest()
{
Plaintext plain1 = new Plaintext();
Plaintext plain2 = new Plaintext();
plain1.Reserve(10000);
plain2.Reserve(500);
plain1.Set("4x^3 + 5x^2 + 6x^1 + 7");
plain2.Set("4x^3 + 5x^2 + 6x^1 + 7");
Assert.AreEqual(10000ul, plain1.Capacity);
Assert.AreEqual(500ul, plain2.Capacity);
Assert.AreNotSame(plain1, plain2);
Assert.AreEqual(plain1, plain2);
Assert.IsFalse(plain1.Equals(null));
}
[TestMethod]
public void SaveLoadTest()
{
SEALContext context = GlobalContext.BFVContext;
Plaintext plain = new Plaintext("6x^5 + 5x^4 + 4x^3 + 3x^2 + 2x^1 + 5");
Plaintext other = new Plaintext();
Assert.AreNotSame(plain, other);
Assert.AreNotEqual(plain, other);
using (MemoryStream stream = new MemoryStream())
{
plain.Save(stream);
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
other.Load(context, stream);
}
Assert.AreNotSame(plain, other);
Assert.AreEqual(plain, other);
Assert.IsTrue(ValCheck.IsValidFor(other, context));
}
[TestMethod]
public void HashCodeTest()
{
Plaintext plain1 = new Plaintext("6x^40 + 5x^35 + 4x^30 + 3x^20 + 2x^10 + 5");
Plaintext plain2 = new Plaintext("1");
Plaintext plain3 = new Plaintext("0");
Plaintext plain4 = new Plaintext("6x^40 + 5x^35 + 4x^30 + 3x^20 + 2x^10 + 5");
Assert.AreNotEqual(plain1.GetHashCode(), plain2.GetHashCode());
Assert.AreNotEqual(plain1.GetHashCode(), plain3.GetHashCode());
Assert.AreNotEqual(plain2.GetHashCode(), plain3.GetHashCode());
Assert.AreNotEqual(plain2.GetHashCode(), plain4.GetHashCode());
Assert.AreNotEqual(plain3.GetHashCode(), plain4.GetHashCode());
Assert.AreEqual(plain1.GetHashCode(), plain4.GetHashCode());
}
[TestMethod]
public void ExceptionsTest()
{
SEALContext context = GlobalContext.BFVContext;
Plaintext plain = new Plaintext();
MemoryPoolHandle pool = MemoryManager.GetPool(MMProfOpt.ForceGlobal);
MemoryPoolHandle pool_uninit = new MemoryPoolHandle();
Utilities.AssertThrows<ArgumentException>(() => plain = new Plaintext(pool_uninit));
Utilities.AssertThrows<ArgumentNullException>(() => plain = new Plaintext((string)null, pool));
Utilities.AssertThrows<ArgumentNullException>(() => plain.Set((Plaintext)null));
Utilities.AssertThrows<ArgumentNullException>(() => plain.Set((string)null));
Utilities.AssertThrows<ArgumentOutOfRangeException>(() => plain.SetZero(100000));
Utilities.AssertThrows<ArgumentOutOfRangeException>(() => plain.SetZero(1, 100000));
Utilities.AssertThrows<ArgumentOutOfRangeException>(() => plain.SetZero(100000, 1));
Utilities.AssertThrows<ArgumentNullException>(() => ValCheck.IsValidFor(plain, null));
Utilities.AssertThrows<ArgumentNullException>(() => plain.Save(null));
Utilities.AssertThrows<ArgumentNullException>(() => plain.UnsafeLoad(null, new MemoryStream()));
Utilities.AssertThrows<ArgumentNullException>(() => plain.UnsafeLoad(context, null));
Utilities.AssertThrows<EndOfStreamException>(() => plain.UnsafeLoad(context, new MemoryStream()));
Utilities.AssertThrows<ArgumentNullException>(() => plain.Load(context, null));
Utilities.AssertThrows<ArgumentNullException>(() => plain.Load(null, new MemoryStream()));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
namespace SEALNetTest
{
[TestClass]
public class PublicKeyTests
{
[TestMethod]
public void CreateTest()
{
EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 64,
PlainModulus = new Modulus(1 << 6),
CoeffModulus = CoeffModulus.Create(64, new int[] { 40 })
};
SEALContext context = new SEALContext(parms,
expandModChain: false,
secLevel: SecLevelType.None);
KeyGenerator keygen = new KeyGenerator(context);
PublicKey pub = keygen.PublicKey;
PublicKey copy = new PublicKey(pub);
Assert.IsNotNull(copy);
Assert.AreEqual(2ul, copy.Data.Size);
Assert.IsTrue(copy.Data.IsNTTForm);
PublicKey copy2 = new PublicKey();
copy2.Set(copy);
Assert.AreEqual(2ul, copy2.Data.Size);
Assert.IsTrue(copy2.Data.IsNTTForm);
}
[TestMethod]
public void SaveLoadTest()
{
EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 64,
PlainModulus = new Modulus(1 << 6),
CoeffModulus = CoeffModulus.Create(64, new int[] { 40 })
};
SEALContext context = new SEALContext(parms,
expandModChain: false,
secLevel: SecLevelType.None);
KeyGenerator keygen = new KeyGenerator(context);
PublicKey pub = keygen.PublicKey;
Assert.IsNotNull(pub);
Assert.AreEqual(2ul, pub.Data.Size);
Assert.IsTrue(pub.Data.IsNTTForm);
PublicKey pub2 = new PublicKey();
MemoryPoolHandle handle = pub2.Pool;
Assert.AreEqual(0ul, pub2.Data.Size);
Assert.IsFalse(pub2.Data.IsNTTForm);
Assert.AreEqual(ParmsId.Zero, pub2.ParmsId);
using (MemoryStream stream = new MemoryStream())
{
pub.Save(stream);
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
pub2.Load(context, stream);
}
Assert.AreNotSame(pub, pub2);
Assert.AreEqual(2ul, pub2.Data.Size);
Assert.IsTrue(pub2.Data.IsNTTForm);
Assert.AreEqual(pub.ParmsId, pub2.ParmsId);
Assert.AreNotEqual(ParmsId.Zero, pub2.ParmsId);
Assert.IsTrue(handle.AllocByteCount != 0ul);
}
[TestMethod]
public void ExceptionsTest()
{
SEALContext context = GlobalContext.BFVContext;
PublicKey key = new PublicKey();
Utilities.AssertThrows<ArgumentNullException>(() => key = new PublicKey(null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Set(null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Save(null));
Utilities.AssertThrows<ArgumentNullException>(() => key.UnsafeLoad(context, null));
Utilities.AssertThrows<ArgumentNullException>(() => key.UnsafeLoad(null, new MemoryStream()));
Utilities.AssertThrows<ArgumentNullException>(() => key.Load(context, null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Load(null, new MemoryStream()));
Utilities.AssertThrows<EndOfStreamException>(() => key.Load(context, new MemoryStream()));
Utilities.AssertThrows<ArgumentNullException>(() => ValCheck.IsValidFor(key, null));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
namespace SEALNetTest
{
[TestClass]
public class RelinKeysTests
{
[TestMethod]
public void CreateRelinKeysTest()
{
RelinKeys keys = new RelinKeys();
Assert.IsNotNull(keys);
Assert.AreEqual(0ul, keys.Size);
}
[TestMethod]
public void CreateNonEmptyRelinKeysTest()
{
SEALContext context = GlobalContext.BFVContext;
KeyGenerator keygen = new KeyGenerator(context);
RelinKeys keys = keygen.RelinKeysLocal();
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);
}
[TestMethod]
public void SaveLoadTest()
{
SEALContext context = GlobalContext.BFVContext;
KeyGenerator keygen = new KeyGenerator(context);
RelinKeys keys = keygen.RelinKeysLocal();
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]);
}
}
}
}
[TestMethod]
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.RelinKeys().Save(stream);
stream.Seek(0, SeekOrigin.Begin);
relinKeys.Load(context, stream);
}
Encryptor encryptor = new Encryptor(context, keygen.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]);
}
[TestMethod]
public void GetKeyTest()
{
SEALContext context = GlobalContext.BFVContext;
KeyGenerator keygen = new KeyGenerator(context);
RelinKeys relinKeys = keygen.RelinKeysLocal();
Assert.IsTrue(relinKeys.HasKey(2));
Assert.IsFalse(relinKeys.HasKey(3));
Utilities.AssertThrows<ArgumentException>(() => relinKeys.Key(0));
Utilities.AssertThrows<ArgumentException>(() => relinKeys.Key(1));
List<PublicKey> key1 = new List<PublicKey>(relinKeys.Key(2));
Assert.AreEqual(4, key1.Count);
Assert.AreEqual(5ul, key1[0].Data.CoeffModulusSize);
}
[TestMethod]
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));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace SEALNetTest
{
[TestClass]
public class SEALContextTests
{
[TestMethod]
public void SEALContextCreateTest()
{
EncryptionParameters encParams1 = new EncryptionParameters(SchemeType.BFV);
EncryptionParameters encParams2 = new EncryptionParameters(SchemeType.CKKS);
SEALContext context1 = new SEALContext(encParams1);
SEALContext context2 = new SEALContext(encParams2);
Assert.IsNotNull(context1);
Assert.IsNotNull(context2);
Assert.IsFalse(context1.ParametersSet);
Assert.IsFalse(context2.ParametersSet);
Assert.AreNotSame(context1.FirstParmsId, context1.LastParmsId);
Assert.AreEqual(context1.FirstParmsId, context1.LastParmsId);
SEALContext.ContextData data1 = context2.FirstContextData;
SEALContext.ContextData data2 = context2.GetContextData(context2.FirstParmsId);
Assert.AreNotSame(data1, data2);
ulong[] totalCoeffMod1 = data1.TotalCoeffModulus;
ulong[] totalCoeffMod2 = data2.TotalCoeffModulus;
int bitCount1 = data1.TotalCoeffModulusBitCount;
int bitCount2 = data2.TotalCoeffModulusBitCount;
Assert.AreEqual(bitCount1, bitCount2);
Assert.AreEqual(totalCoeffMod1.Length, totalCoeffMod2.Length);
for (int i = 0; i < totalCoeffMod1.Length; i++)
{
Assert.AreEqual(totalCoeffMod1[i], totalCoeffMod2[i]);
}
}
[TestMethod]
public void SEALContextParamsTest()
{
EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 128,
PlainModulus = new Modulus(1 << 6),
CoeffModulus = CoeffModulus.Create(128, new int[] { 30, 30, 30 })
};
SEALContext context = new SEALContext(parms, expandModChain: true, secLevel: SecLevelType.None);
SEALContext.ContextData data = context.KeyContextData;
Assert.IsNotNull(data);
EncryptionParameters parms2 = data.Parms;
Assert.AreEqual(parms.PolyModulusDegree, parms2.PolyModulusDegree);
EncryptionParameterQualifiers qualifiers = data.Qualifiers;
Assert.IsNotNull(qualifiers);
Assert.IsTrue(qualifiers.ParametersSet);
Assert.IsFalse(qualifiers.UsingBatching);
Assert.IsTrue(qualifiers.UsingFastPlainLift);
Assert.IsTrue(qualifiers.UsingFFT);
Assert.IsTrue(qualifiers.UsingNTT);
Assert.AreEqual(SecLevelType.None, qualifiers.SecLevel);
Assert.IsFalse(qualifiers.UsingDescendingModulusChain);
Assert.IsTrue(context.UsingKeyswitching);
ulong[] cdpm = data.CoeffDivPlainModulus;
Assert.AreEqual(3, cdpm.Length);
Assert.AreEqual(32ul, data.PlainUpperHalfThreshold);
Assert.AreEqual(3, data.PlainUpperHalfIncrement.Length);
Assert.IsNull(data.UpperHalfThreshold);
Assert.IsNotNull(data.UpperHalfIncrement);
Assert.AreEqual(3, data.UpperHalfIncrement.Length);
Assert.AreEqual(2ul, data.ChainIndex);
Assert.IsNull(data.PrevContextData);
SEALContext.ContextData data2 = data.NextContextData;
Assert.IsNotNull(data2);
Assert.AreEqual(1ul, data2.ChainIndex);
Assert.AreEqual(2ul, data2.PrevContextData.ChainIndex);
SEALContext.ContextData data3 = data2.NextContextData;
Assert.IsNotNull(data3);
Assert.AreEqual(0ul, data3.ChainIndex);
Assert.AreEqual(1ul, data3.PrevContextData.ChainIndex);
Assert.IsNull(data3.NextContextData);
parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 127,
PlainModulus = new Modulus(1 << 6),
CoeffModulus = CoeffModulus.Create(128, new int[] { 30, 30, 30 })
};
context = new SEALContext(parms, expandModChain: true, secLevel: SecLevelType.None);
Assert.AreEqual(context.ParameterErrorName(), "invalid_poly_modulus_degree_non_power_of_two");
Assert.AreEqual(context.ParameterErrorMessage(), "poly_modulus_degree is not a power of two");
}
[TestMethod]
public void SEALContextCKKSParamsTest()
{
int slotSize = 4;
EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS)
{
PolyModulusDegree = 2 * (ulong)slotSize,
CoeffModulus = CoeffModulus.Create(2 * (ulong)slotSize, new int[] { 40, 40, 40, 40 })
};
SEALContext context = new SEALContext(parms,
expandModChain: true,
secLevel: SecLevelType.None);
SEALContext.ContextData data = context.KeyContextData;
Assert.IsNotNull(data);
// This should be available in CKKS
Assert.IsNotNull(data.UpperHalfThreshold);
Assert.AreEqual(4, data.UpperHalfThreshold.Length);
Assert.IsNull(data.UpperHalfIncrement);
Assert.AreEqual(3ul, data.ChainIndex);
Assert.IsNull(data.PrevContextData);
SEALContext.ContextData data2 = data.NextContextData;
Assert.IsNotNull(data2);
Assert.AreEqual(2ul, data2.ChainIndex);
Assert.AreEqual(3ul, data2.PrevContextData.ChainIndex);
SEALContext.ContextData data3 = data2.NextContextData;
Assert.IsNotNull(data3);
Assert.AreEqual(1ul, data3.ChainIndex);
Assert.AreEqual(2ul, data3.PrevContextData.ChainIndex);
SEALContext.ContextData data4 = data3.NextContextData;
Assert.IsNotNull(data4);
Assert.AreEqual(0ul, data4.ChainIndex);
Assert.AreEqual(1ul, data4.PrevContextData.ChainIndex);
Assert.IsNull(data4.NextContextData);
}
[TestMethod]
public void ExpandModChainTest()
{
EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 4096,
CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree: 4096),
PlainModulus = new Modulus(1 << 20)
};
SEALContext context1 = new SEALContext(parms,
expandModChain: true,
secLevel: SecLevelType.None);
// By default there is a chain
SEALContext.ContextData contextData = context1.KeyContextData;
Assert.IsNotNull(contextData);
Assert.IsNull(contextData.PrevContextData);
Assert.IsNotNull(contextData.NextContextData);
contextData = context1.FirstContextData;
Assert.IsNotNull(contextData);
Assert.IsNotNull(contextData.PrevContextData);
Assert.IsNotNull(contextData.NextContextData);
// This should not create a chain
SEALContext context2 = new SEALContext(parms, expandModChain: false);
contextData = context2.KeyContextData;
Assert.IsNotNull(contextData);
Assert.IsNull(contextData.PrevContextData);
Assert.IsNotNull(contextData.NextContextData);
contextData = context2.FirstContextData;
Assert.IsNotNull(contextData);
Assert.IsNotNull(contextData.PrevContextData);
Assert.IsNull(contextData.NextContextData);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Authors>Microsoft Research</Authors>
<Company>Microsoft Corporation</Company>
<Description>.NET wrapper unit tests for Microsoft SEAL</Description>
<Copyright>Microsoft Corporation 2020</Copyright>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>$(ProjectDir)../../bin/dotnet/$(Configuration)</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ProjectDir)../src/SEALNet.csproj" />
</ItemGroup>
<ItemGroup>
<SEALCBinaryFiles Condition="$([MSBuild]::IsOsPlatform(Windows))" Include="$(ProjectDir)..\..\lib\x64\$(Configuration)\sealc.dll" />
<SEALCBinaryFiles Condition="$([MSBuild]::IsOsPlatform(Linux))" Include="$(ProjectDir)../../lib/libsealc.so.*" />
<SEALCBinaryFiles Condition="$([MSBuild]::IsOsPlatform(OSX))" Include="$(ProjectDir)../../lib/libsealc*.dylib" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Copy SourceFiles="@(SEALCBinaryFiles)" DestinationFolder="$(TargetDir)" />
</Target>
</Project>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
namespace SEALNetTest
{
[TestClass]
public class SecretKeyTests
{
[TestMethod]
public void CreateTest()
{
EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 64,
PlainModulus = new Modulus(1 << 6),
CoeffModulus = CoeffModulus.Create(64, new int[] { 40 })
};
SEALContext context = new SEALContext(parms,
expandModChain: false,
secLevel: SecLevelType.None);
KeyGenerator keygen = new KeyGenerator(context);
SecretKey secret = keygen.SecretKey;
SecretKey copy = new SecretKey(secret);
Assert.AreEqual(64ul, copy.Data.CoeffCount);
Assert.IsTrue(copy.Data.IsNTTForm);
SecretKey copy2 = new SecretKey();
copy2.Set(copy);
Assert.AreEqual(64ul, copy2.Data.CoeffCount);
Assert.IsTrue(copy2.Data.IsNTTForm);
}
[TestMethod]
public void SaveLoadTest()
{
EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = 64,
PlainModulus = new Modulus(1 << 6),
CoeffModulus = CoeffModulus.Create(64, new int[] { 40 })
};
SEALContext context = new SEALContext(parms,
expandModChain: false,
secLevel: SecLevelType.None);
KeyGenerator keygen = new KeyGenerator(context);
SecretKey secret = keygen.SecretKey;
Assert.AreEqual(64ul, secret.Data.CoeffCount);
Assert.IsTrue(secret.Data.IsNTTForm);
Assert.AreNotEqual(ParmsId.Zero, secret.ParmsId);
SecretKey secret2 = new SecretKey();
Assert.IsNotNull(secret2);
Assert.AreEqual(0ul, secret2.Data.CoeffCount);
Assert.IsFalse(secret2.Data.IsNTTForm);
using (MemoryStream stream = new MemoryStream())
{
secret.Save(stream);
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
secret2.Load(context, stream);
}
Assert.AreNotSame(secret, secret2);
Assert.AreEqual(64ul, secret2.Data.CoeffCount);
Assert.IsTrue(secret2.Data.IsNTTForm);
Assert.AreNotEqual(ParmsId.Zero, secret2.ParmsId);
Assert.AreEqual(secret.ParmsId, secret2.ParmsId);
}
[TestMethod]
public void ExceptionsTest()
{
SEALContext context = GlobalContext.BFVContext;
SecretKey key = new SecretKey();
Utilities.AssertThrows<ArgumentNullException>(() => key = new SecretKey(null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Set(null));
Utilities.AssertThrows<ArgumentNullException>(() => ValCheck.IsValidFor(key, null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Save(null));
Utilities.AssertThrows<ArgumentNullException>(() => key.UnsafeLoad(null, new MemoryStream()));
Utilities.AssertThrows<ArgumentNullException>(() => key.UnsafeLoad(context, null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Load(context, null));
Utilities.AssertThrows<ArgumentNullException>(() => key.Load(null, new MemoryStream()));
Utilities.AssertThrows<EndOfStreamException>(() => key.Load(context, new MemoryStream()));
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Research.SEAL;
using System;
using System.IO;
using System.Text;
namespace SEALNetTest
{
[TestClass]
public class SerializationTests
{
[TestMethod]
public void IsValidHeader()
{
Assert.AreEqual(Serialization.SEALHeaderSize, 0x10);
Serialization.SEALHeader header = new Serialization.SEALHeader();
Assert.IsTrue(Serialization.IsValidHeader(header));
Serialization.SEALHeader invalidHeader = new Serialization.SEALHeader();
invalidHeader.Magic = 0x1212;
Assert.IsFalse(Serialization.IsValidHeader(invalidHeader));
invalidHeader.Magic = Serialization.SEALMagic;
Assert.AreEqual(Serialization.SEALHeaderSize, invalidHeader.HeaderSize);
invalidHeader.VersionMajor = 0x02;
Assert.IsFalse(Serialization.IsValidHeader(invalidHeader));
invalidHeader.VersionMajor = SEALVersion.Major;
invalidHeader.ComprMode = (ComprModeType)0x02;
Assert.IsFalse(Serialization.IsValidHeader(invalidHeader));
}
[TestMethod]
public void SEALHeaderSaveLoad()
{
Serialization.SEALHeader header = new Serialization.SEALHeader();
Serialization.SEALHeader loaded = new Serialization.SEALHeader();
using (MemoryStream mem = new MemoryStream())
{
header.ComprMode = Serialization.ComprModeDefault;
header.Size = 256;
Assert.IsTrue(Serialization.IsValidHeader(header));
Serialization.SaveHeader(header, mem);
mem.Seek(offset: 0, loc: SeekOrigin.Begin);
Serialization.LoadHeader(mem, loaded);
Assert.AreEqual(loaded.Magic, header.Magic);
Assert.AreEqual(loaded.HeaderSize, header.HeaderSize);
Assert.AreEqual(loaded.VersionMajor, header.VersionMajor);
Assert.AreEqual(loaded.VersionMinor, header.VersionMinor);
Assert.AreEqual(loaded.ComprMode, header.ComprMode);
Assert.AreEqual(loaded.Reserved, header.Reserved);
Assert.AreEqual(loaded.Size, header.Size);
}
}
[TestMethod]
public void SEALHeaderUpgrade()
{
LegacyHeaders.SEALHeader_3_4 header_3_4 = new LegacyHeaders.SEALHeader_3_4();
using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem, Encoding.UTF8, true);
writer.Write(header_3_4.Magic);
writer.Write(header_3_4.ZeroByte);
writer.Write((byte)header_3_4.ComprMode);
writer.Write(header_3_4.Size);
writer.Write(header_3_4.Reserved);
mem.Seek(offset: 0, loc: SeekOrigin.Begin);
{
Serialization.SEALHeader loaded = new Serialization.SEALHeader();
Serialization.LoadHeader(mem, loaded);
Assert.IsTrue(Serialization.IsValidHeader(loaded));
Assert.AreEqual(header_3_4.ComprMode, loaded.ComprMode);
Assert.AreEqual(header_3_4.Size, loaded.Size);
mem.Seek(offset: 0, loc: SeekOrigin.Begin);
}
{
Serialization.SEALHeader loaded = new Serialization.SEALHeader();
Serialization.LoadHeader(mem, loaded, false);
Assert.IsFalse(Serialization.IsValidHeader(loaded));
mem.Seek(offset: 0, loc: SeekOrigin.Begin);
}
}
[TestMethod]
public void ExceptionsTest()
{
SEALContext context = GlobalContext.BFVContext;
Ciphertext cipher = new Ciphertext();
using (MemoryStream mem = new MemoryStream())
{
KeyGenerator keygen = new KeyGenerator(context);
Encryptor encryptor = new Encryptor(context, keygen.PublicKey);
Plaintext plain = new Plaintext("2x^3 + 4x^2 + 5x^1 + 6");
encryptor.Encrypt(plain, cipher);
cipher.Save(mem);
mem.Seek(offset: 8, loc: SeekOrigin.Begin);
BinaryWriter writer = new BinaryWriter(mem, Encoding.UTF8, true);
writer.Write((ulong)0x80000000);
mem.Seek(offset: 0, loc: SeekOrigin.Begin);
Utilities.AssertThrows<InvalidOperationException>(() => cipher.Load(context, mem));
}
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace SEALNetTest
{
[TestClass]
public class TestAssemblyCleanup
{
[AssemblyCleanup]
public static void AssemblyCleanup()
{
// Check that our Assert.Throw workaround is not getting out of hand
Assert.IsTrue(Utilities.WorkaroundInstanceCount <= 2, $"WorkaroundInstanceCount should be <= 2, it is: {Utilities.WorkaroundInstanceCount}");
Trace.WriteLine($"Assert.Throw workaround instances found: {Utilities.WorkaroundInstanceCount}");
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
namespace SEALNetTest
{
/// <summary>
/// Test utilities
/// </summary>
public static class Utilities
{
internal static int WorkaroundInstanceCount { get; private set; } = 0;
/// <summary>
/// Assert that an exception of the given type is thrown.
///
/// This is a workaround for a unit testing issue in VS 2019.
/// When running unit tests a couple of them fail because of a FileNotFoundException being thrown instead
/// of the expected exception. The FileNotFoundException is thrown in the boundary between a .Net call
/// and a native method, so there is not really much we can do to fix it. As a workaround this method
/// works as Assert.ThrowsException, but allows FileNotFoundException as well, and outputs a warning when
/// it is found.
/// </summary>
/// <typeparam name="T">Expected exception type</typeparam>
/// <param name="action">Function to run that should throw an exception</param>
/// <param name="caller">Path to the source file that called this method</param>
/// <param name="line">Line in the source file that called this method</param>
public static void AssertThrows<T>(Func<object> action, [CallerFilePath] string caller = "", [CallerLineNumber] int line = 0) where T : Exception
{
DoAssertThrow<T>(() => { var result = action(); }, caller, line);
}
/// <summary>
/// Assert that an exception of the given type is thrown.
///
/// This is a workaround for a unit testing issue in VS 2019.
/// When running unit tests a couple of them fail because of a FileNotFoundException being thrown instead
/// of the expected exception. The FileNotFoundException is thrown in the boundary between a .Net call
/// and a native method, so there is not really much we can do to fix it. As a workaround this method
/// works as Assert.ThrowsException, but allows FileNotFoundException as well, and outputs a warning when
/// it is found.
/// </summary>
/// <typeparam name="T">Expected exception type</typeparam>
/// <param name="action">Action to run that should throw an exception</param>
/// <param name="caller">Path to the source file that called this method</param>
/// <param name="line">Line in the source file that called this method</param>
public static void AssertThrows<T>(Action action, [CallerFilePath] string caller = "", [CallerLineNumber] int line = 0) where T : Exception
{
DoAssertThrow<T>(action, caller, line);
}
private static void DoAssertThrow<T>(Action action, string caller, int line) where T : Exception
{
string expectedStr = typeof(T).ToString();
try
{
action();
}
catch (Exception ex)
{
if (ex is T)
{
// Expected exception has been thrown
return;
}
// Workaround: Check if exception is FileNotFoundException
if (ex is FileNotFoundException workaroundExc)
{
string workaroundStr = workaroundExc.GetType().ToString();
Trace.WriteLine($"WARNING: {caller}:{line}: Expected exception of type '{expectedStr}', got type '{workaroundStr}' instead.");
WorkaroundInstanceCount++;
return;
}
// Any other exception should fail.
string actualStr = ex.GetType().ToString();
Assert.Fail($"{caller}:{line}: Expected exception of type '{expectedStr}', got type '{actualStr}' instead.");
}
Assert.Fail($"{caller}:{line}: Expected exception of type '{expectedStr}', no exception thrown.");
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include "examples.h"
using namespace std;
using namespace seal;
void example_bfv_basics()
{
print_example_banner("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:
- poly_modulus_degree (degree of polynomial modulus);
- coeff_modulus ([ciphertext] coefficient modulus);
- plain_modulus (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(scheme_type::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 poly_modulus_degree 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.
*/
size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);
/*
Next we set the [ciphertext] `coefficient modulus' (coeff_modulus). This
parameter is a large integer, which is a product of distinct prime numbers,
each up to 60 bits in size. It is represented as a vector of these prime
numbers, each represented by an instance of the Modulus class. The
bit-length of coeff_modulus means the sum of the bit-lengths of its prime
factors.
A larger coeff_modulus implies a larger noise budget, hence more encrypted
computation capabilities. However, an upper bound for the total bit-length
of the coeff_modulus is determined by the poly_modulus_degree, as follows:
+----------------------------------------------------+
| poly_modulus_degree | max coeff_modulus 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(poly_modulus_degree).
For example, if poly_modulus_degree is 4096, the coeff_modulus could consist
of three 36-bit primes (108 bits).
Microsoft SEAL comes with helper functions for selecting the coeff_modulus.
For new users the easiest way is to simply use
CoeffModulus::BFVDefault(poly_modulus_degree),
which returns std::vector<Modulus> consisting of a generally good choice
for the given poly_modulus_degree.
*/
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
/*
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(coeff_modulus/plain_modulus) (bits)
and the noise budget consumption in a homomorphic multiplication is of the
form log2(plain_modulus) + (other terms).
The plaintext modulus is specific to the BFV scheme, and cannot be set when
using the CKKS scheme.
*/
parms.set_plain_modulus(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.
*/
auto context = SEALContext::Create(parms);
/*
Print the parameters that we have chosen.
*/
print_line(__LINE__);
cout << "Set encryption parameters and print" << endl;
print_parameters(context);
/*
When parameters are used to create SEALContext, Microsoft SEAL will first
validate those parameters. The parameters chosen here are valid.
*/
cout << "Parameter validation (success): " << context->parameter_error_message() << endl;
cout << endl;
cout << "~~~~~~ A naive way to calculate 4(x^2+1)(x+1)^2. ~~~~~~" << endl;
/*
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(context);
PublicKey public_key = keygen.public_key();
SecretKey secret_key = keygen.secret_key();
/*
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(context, public_key);
/*
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(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(context, secret_key);
/*
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 poly_modulus_degree
and T is plain_modulus.
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.
*/
print_line(__LINE__);
int x = 6;
Plaintext x_plain(to_string(x));
cout << "Express x = " + to_string(x) + " as a plaintext polynomial 0x" + x_plain.to_string() + "." << endl;
/*
We then encrypt the plaintext, producing a ciphertext.
*/
print_line(__LINE__);
Ciphertext x_encrypted;
cout << "Encrypt x_plain to x_encrypted." << endl;
encryptor.encrypt(x_plain, x_encrypted);
/*
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.
*/
cout << " + size of freshly encrypted x: " << x_encrypted.size() << endl;
/*
There is plenty of noise budget left in this freshly encrypted ciphertext.
*/
cout << " + noise budget in freshly encrypted x: " << decryptor.invariant_noise_budget(x_encrypted) << " bits"
<< endl;
/*
We decrypt the ciphertext and print the resulting plaintext in order to
demonstrate correctness of the encryption.
*/
Plaintext x_decrypted;
cout << " + decryption of x_encrypted: ";
decryptor.decrypt(x_encrypted, x_decrypted);
cout << "0x" << x_decrypted.to_string() << " ...... Correct." << endl;
/*
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.
*/
print_line(__LINE__);
cout << "Compute x_sq_plus_one (x^2+1)." << endl;
Ciphertext x_sq_plus_one;
evaluator.square(x_encrypted, x_sq_plus_one);
Plaintext plain_one("1");
evaluator.add_plain_inplace(x_sq_plus_one, plain_one);
/*
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.
*/
cout << " + size of x_sq_plus_one: " << x_sq_plus_one.size() << endl;
cout << " + noise budget in x_sq_plus_one: " << decryptor.invariant_noise_budget(x_sq_plus_one) << " bits"
<< endl;
/*
Even though the size has grown, decryption works as usual as long as noise
budget has not reached 0.
*/
Plaintext decrypted_result;
cout << " + decryption of x_sq_plus_one: ";
decryptor.decrypt(x_sq_plus_one, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
/*
Next, we compute (x + 1)^2.
*/
print_line(__LINE__);
cout << "Compute x_plus_one_sq ((x+1)^2)." << endl;
Ciphertext x_plus_one_sq;
evaluator.add_plain(x_encrypted, plain_one, x_plus_one_sq);
evaluator.square_inplace(x_plus_one_sq);
cout << " + size of x_plus_one_sq: " << x_plus_one_sq.size() << endl;
cout << " + noise budget in x_plus_one_sq: " << decryptor.invariant_noise_budget(x_plus_one_sq) << " bits"
<< endl;
cout << " + decryption of x_plus_one_sq: ";
decryptor.decrypt(x_plus_one_sq, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
/*
Finally, we multiply (x^2 + 1) * (x + 1)^2 * 4.
*/
print_line(__LINE__);
cout << "Compute encrypted_result (4(x^2+1)(x+1)^2)." << endl;
Ciphertext encrypted_result;
Plaintext plain_four("4");
evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four);
evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result);
cout << " + size of encrypted_result: " << encrypted_result.size() << endl;
cout << " + noise budget in encrypted_result: " << decryptor.invariant_noise_budget(encrypted_result) << " bits"
<< endl;
cout << "NOTE: Decryption can be incorrect if noise budget is zero." << endl;
cout << endl;
cout << "~~~~~~ A better way to calculate 4(x^2+1)(x+1)^2. ~~~~~~" << endl;
/*
Noise budget has reached 0, which means that decryption cannot be expected
to give the correct result. This is because both ciphertexts x_sq_plus_one
and x_plus_one_sq 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.
Here we use the function KeyGenerator::relin_keys_local(). In production
code it is much better to use KeyGenerator::relin_keys() instead. We will
explain and discuss these differences in `6_serialization.cpp'.
*/
print_line(__LINE__);
cout << "Generate locally usable relinearization keys." << endl;
auto relin_keys = keygen.relin_keys_local();
/*
We now repeat the computation relinearizing after each multiplication.
*/
print_line(__LINE__);
cout << "Compute and relinearize x_squared (x^2)," << endl;
cout << string(13, ' ') << "then compute x_sq_plus_one (x^2+1)" << endl;
Ciphertext x_squared;
evaluator.square(x_encrypted, x_squared);
cout << " + size of x_squared: " << x_squared.size() << endl;
evaluator.relinearize_inplace(x_squared, relin_keys);
cout << " + size of x_squared (after relinearization): " << x_squared.size() << endl;
evaluator.add_plain(x_squared, plain_one, x_sq_plus_one);
cout << " + noise budget in x_sq_plus_one: " << decryptor.invariant_noise_budget(x_sq_plus_one) << " bits"
<< endl;
cout << " + decryption of x_sq_plus_one: ";
decryptor.decrypt(x_sq_plus_one, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
print_line(__LINE__);
Ciphertext x_plus_one;
cout << "Compute x_plus_one (x+1)," << endl;
cout << string(13, ' ') << "then compute and relinearize x_plus_one_sq ((x+1)^2)." << endl;
evaluator.add_plain(x_encrypted, plain_one, x_plus_one);
evaluator.square(x_plus_one, x_plus_one_sq);
cout << " + size of x_plus_one_sq: " << x_plus_one_sq.size() << endl;
evaluator.relinearize_inplace(x_plus_one_sq, relin_keys);
cout << " + noise budget in x_plus_one_sq: " << decryptor.invariant_noise_budget(x_plus_one_sq) << " bits"
<< endl;
cout << " + decryption of x_plus_one_sq: ";
decryptor.decrypt(x_plus_one_sq, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
print_line(__LINE__);
cout << "Compute and relinearize encrypted_result (4(x^2+1)(x+1)^2)." << endl;
evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four);
evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result);
cout << " + size of encrypted_result: " << encrypted_result.size() << endl;
evaluator.relinearize_inplace(encrypted_result, relin_keys);
cout << " + size of encrypted_result (after relinearization): " << encrypted_result.size() << endl;
cout << " + noise budget in encrypted_result: " << decryptor.invariant_noise_budget(encrypted_result) << " bits"
<< endl;
cout << endl;
cout << "NOTE: Notice the increase in remaining noise budget." << endl;
/*
Relinearization clearly improved our noise consumption. We have still plenty
of noise budget left, so we can expect the correct answer when decrypting.
*/
print_line(__LINE__);
cout << "Decrypt encrypted_result (4(x^2+1)(x+1)^2)." << endl;
decryptor.decrypt(encrypted_result, decrypted_result);
cout << " + decryption of 4(x^2+1)(x+1)^2 = 0x" << decrypted_result.to_string() << " ...... Correct." << endl;
cout << endl;
/*
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.
*/
/*
Sometimes we create customized encryption parameters which turn out to be invalid.
Microsoft SEAL can interpret the reason why parameters are considered invalid.
Here we simply reduce the polynomial modulus degree to make the parameters not
compliant with the HomomorphicEncryption.org security standard.
*/
print_line(__LINE__);
cout << "An example of invalid parameters" << endl;
parms.set_poly_modulus_degree(2048);
context = SEALContext::Create(parms);
print_parameters(context);
cout << "Parameter validation (failed): " << context->parameter_error_message() << endl;
/*
This information is helpful to fix invalid encryption parameters.
*/
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include "examples.h"
using namespace std;
using namespace seal;
/*
In `1_bfv_basics.cpp' we showed how to perform a very simple computation using the
BFV scheme. The computation was performed modulo the plain_modulus parameter, and
utilized only one coefficient from a BFV plaintext polynomial. This approach has
two notable problems:
(1) Practical applications typically use integer or real number arithmetic,
not modular arithmetic;
(2) We used only one coefficient of the plaintext polynomial. This is really
wasteful, as the plaintext polynomial is large and will in any case be
encrypted in its entirety.
For (1), one may ask why not just increase the plain_modulus parameter until no
overflow occurs, and the computations behave as in integer arithmetic. The problem
is that increasing plain_modulus increases noise budget consumption, and decreases
the initial noise budget too.
In these examples we will discuss other ways of laying out data into plaintext
elements (encoding) that allow more computations without data type overflow, and
can allow the full plaintext polynomial to be utilized.
*/
void example_integer_encoder()
{
print_example_banner("Example: Encoders / Integer Encoder");
/*
[IntegerEncoder] (For BFV scheme only)
The IntegerEncoder encodes integers to BFV plaintext polynomials as follows.
First, a binary expansion of the integer is computed. Next, a polynomial is
created with the bits as coefficients. For example, the integer
26 = 2^4 + 2^3 + 2^1
is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. Conversely, plaintext
polynomials are decoded by evaluating them at x=2. For negative numbers the
IntegerEncoder simply stores all coefficients as either 0 or -1, where -1 is
represented by the unsigned integer plain_modulus - 1 in memory.
Since encrypted computations operate on the polynomials rather than on the
encoded integers themselves, the polynomial coefficients will grow in the
course of such computations. For example, computing the sum of the encrypted
encoded integer 26 with itself will result in an encrypted polynomial with
larger coefficients: 2x^4 + 2x^3 + 2x^1. Squaring the encrypted encoded
integer 26 results also in increased coefficients due to cross-terms, namely,
(1x^4 + 1x^3 + 1x^1)^2 = 1x^8 + 2x^7 + 1x^6 + 2x^5 + 2x^4 + 1x^2;
further computations will quickly increase the coefficients much more.
Decoding will still work correctly in this case (evaluating the polynomial
at x=2), but since the coefficients of plaintext polynomials are really
integers modulo plain_modulus, implicit reduction modulo plain_modulus may
yield unexpected results. For example, adding 1x^4 + 1x^3 + 1x^1 to itself
plain_modulus many times will result in the constant polynomial 0, which is
clearly not equal to 26 * plain_modulus. It can be difficult to predict when
such overflow will take place especially when computing several sequential
multiplications.
The IntegerEncoder is easy to understand and use for simple computations,
and can be a good tool to experiment with for users new to Microsoft SEAL.
However, advanced users will probably prefer more efficient approaches,
such as the BatchEncoder or the CKKSEncoder.
*/
EncryptionParameters parms(scheme_type::BFV);
size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
/*
There is no hidden logic behind our choice of the plain_modulus. The only
thing that matters is that the plaintext polynomial coefficients will not
exceed this value at any point during our computation; otherwise the result
will be incorrect.
*/
parms.set_plain_modulus(512);
auto context = SEALContext::Create(parms);
print_parameters(context);
cout << endl;
KeyGenerator keygen(context);
PublicKey public_key = keygen.public_key();
SecretKey secret_key = keygen.secret_key();
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
/*
We create an IntegerEncoder.
*/
IntegerEncoder encoder(context);
/*
First, we encode two integers as plaintext polynomials. Note that encoding
is not encryption: at this point nothing is encrypted.
*/
int value1 = 5;
Plaintext plain1 = encoder.encode(value1);
print_line(__LINE__);
cout << "Encode " << value1 << " as polynomial " << plain1.to_string() << " (plain1)," << endl;
int value2 = -7;
Plaintext plain2 = encoder.encode(value2);
cout << string(13, ' ') << "encode " << value2 << " as polynomial " << plain2.to_string() << " (plain2)." << endl;
/*
Now we can encrypt the plaintext polynomials.
*/
Ciphertext encrypted1, encrypted2;
print_line(__LINE__);
cout << "Encrypt plain1 to encrypted1 and plain2 to encrypted2." << endl;
encryptor.encrypt(plain1, encrypted1);
encryptor.encrypt(plain2, encrypted2);
cout << " + Noise budget in encrypted1: " << decryptor.invariant_noise_budget(encrypted1) << " bits" << endl;
cout << " + Noise budget in encrypted2: " << decryptor.invariant_noise_budget(encrypted2) << " bits" << endl;
/*
As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2.
*/
encryptor.encrypt(plain2, encrypted2);
Ciphertext encrypted_result;
print_line(__LINE__);
cout << "Compute encrypted_result = (-encrypted1 + encrypted2) * encrypted2." << endl;
evaluator.negate(encrypted1, encrypted_result);
evaluator.add_inplace(encrypted_result, encrypted2);
evaluator.multiply_inplace(encrypted_result, encrypted2);
cout << " + Noise budget in encrypted_result: " << decryptor.invariant_noise_budget(encrypted_result) << " bits"
<< endl;
Plaintext plain_result;
print_line(__LINE__);
cout << "Decrypt encrypted_result to plain_result." << endl;
decryptor.decrypt(encrypted_result, plain_result);
/*
Print the result plaintext polynomial. The coefficients are not even close
to exceeding our plain_modulus, 512.
*/
cout << " + Plaintext polynomial: " << plain_result.to_string() << endl;
/*
Decode to obtain an integer result.
*/
print_line(__LINE__);
cout << "Decode plain_result." << endl;
cout << " + Decoded integer: " << encoder.decode_int32(plain_result);
cout << "...... Correct." << endl;
}
void example_batch_encoder()
{
print_example_banner("Example: Encoders / Batch Encoder");
/*
[BatchEncoder] (For BFV scheme only)
Let N denote the poly_modulus_degree and T denote the plain_modulus. 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(scheme_type::BFV);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
/*
To enable batching, we need to set the plain_modulus to be a prime number
congruent to 1 modulo 2*poly_modulus_degree. Microsoft SEAL provides a helper
method for finding such a prime. In this example we create a 20-bit prime
that supports batching.
*/
parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));
auto context = SEALContext::Create(parms);
print_parameters(context);
cout << endl;
/*
We can verify that batching is indeed enabled by looking at the encryption
parameter qualifiers created by SEALContext.
*/
auto qualifiers = context->first_context_data()->qualifiers();
cout << "Batching enabled: " << boolalpha << qualifiers.using_batching << endl;
KeyGenerator keygen(context);
PublicKey public_key = keygen.public_key();
SecretKey secret_key = keygen.secret_key();
RelinKeys relin_keys = keygen.relin_keys_local();
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
/*
Batching is done through an instance of the BatchEncoder class.
*/
BatchEncoder batch_encoder(context);
/*
The total number of batching `slots' equals the poly_modulus_degree, 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 plain_modulus.
*/
size_t slot_count = batch_encoder.slot_count();
size_t row_size = slot_count / 2;
cout << "Plaintext matrix row size: " << row_size << endl;
/*
The matrix plaintext is simply given to BatchEncoder as a flattened vector
of numbers. The first `row_size' 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 ]
*/
vector<uint64_t> pod_matrix(slot_count, 0ULL);
pod_matrix[0] = 0ULL;
pod_matrix[1] = 1ULL;
pod_matrix[2] = 2ULL;
pod_matrix[3] = 3ULL;
pod_matrix[row_size] = 4ULL;
pod_matrix[row_size + 1] = 5ULL;
pod_matrix[row_size + 2] = 6ULL;
pod_matrix[row_size + 3] = 7ULL;
cout << "Input plaintext matrix:" << endl;
print_matrix(pod_matrix, row_size);
/*
First we use BatchEncoder to encode the matrix into a plaintext polynomial.
*/
Plaintext plain_matrix;
print_line(__LINE__);
cout << "Encode plaintext matrix:" << endl;
batch_encoder.encode(pod_matrix, plain_matrix);
/*
We can instantly decode to verify correctness of the encoding. Note that no
encryption or decryption has yet taken place.
*/
vector<uint64_t> pod_result;
cout << " + Decode plaintext matrix ...... Correct." << endl;
batch_encoder.decode(plain_matrix, pod_result);
print_matrix(pod_result, row_size);
/*
Next we encrypt the encoded plaintext.
*/
Ciphertext encrypted_matrix;
print_line(__LINE__);
cout << "Encrypt plain_matrix to encrypted_matrix." << endl;
encryptor.encrypt(plain_matrix, encrypted_matrix);
cout << " + Noise budget in encrypted_matrix: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits"
<< endl;
/*
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.
*/
vector<uint64_t> pod_matrix2;
for (size_t i = 0; i < slot_count; i++)
{
pod_matrix2.push_back((i % 2) + 1);
}
Plaintext plain_matrix2;
batch_encoder.encode(pod_matrix2, plain_matrix2);
cout << endl;
cout << "Second input plaintext matrix:" << endl;
print_matrix(pod_matrix2, row_size);
/*
We now add the second (plaintext) matrix to the encrypted matrix, and square
the sum.
*/
print_line(__LINE__);
cout << "Sum, square, and relinearize." << endl;
evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2);
evaluator.square_inplace(encrypted_matrix);
evaluator.relinearize_inplace(encrypted_matrix, relin_keys);
/*
How much noise budget do we have left?
*/
cout << " + Noise budget in result: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
/*
We decrypt and decompose the plaintext to recover the result as a matrix.
*/
Plaintext plain_result;
print_line(__LINE__);
cout << "Decrypt and decode result." << endl;
decryptor.decrypt(encrypted_matrix, plain_result);
batch_encoder.decode(plain_result, pod_result);
cout << " + Result plaintext matrix ...... Correct." << endl;
print_matrix(pod_result, row_size);
/*
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.
*/
}
void example_ckks_encoder()
{
print_example_banner("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 plain_modulus encryption parameter;
(2) Selecting the coeff_modulus in a specific way can be very important
when using the CKKS scheme. We will explain this further in the file
`ckks_basics.cpp'. In this example we use CoeffModulus::Create to
generate 5 40-bit prime numbers.
*/
EncryptionParameters parms(scheme_type::CKKS);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 40, 40, 40, 40, 40 }));
/*
We create the SEALContext as usual and print the parameters.
*/
auto context = SEALContext::Create(parms);
print_parameters(context);
cout << endl;
/*
Keys are created the same way as for the BFV scheme.
*/
KeyGenerator keygen(context);
auto public_key = keygen.public_key();
auto secret_key = keygen.secret_key();
auto relin_keys = keygen.relin_keys_local();
/*
We also set up an Encryptor, Evaluator, and Decryptor as usual.
*/
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
/*
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(context);
/*
In CKKS the number of slots is poly_modulus_degree / 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 poly_modulus_degree
and they are arranged into a matrix with two rows.
*/
size_t slot_count = encoder.slot_count();
cout << "Number of slots: " << slot_count << endl;
/*
We create a small vector to encode; the CKKSEncoder will implicitly pad it
with zeros to full size (poly_modulus_degree / 2) when encoding.
*/
vector<double> input{ 0.0, 1.1, 2.2, 3.3 };
cout << "Input vector: " << endl;
print_vector(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 coeff_modulus (in BFV it is stored modulo
plain_modulus), so the scaled message must not get too close to the total size
of coeff_modulus. In this case our coeff_modulus 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;
double scale = pow(2.0, 30);
print_line(__LINE__);
cout << "Encode input vector." << endl;
encoder.encode(input, scale, plain);
/*
We can instantly decode to check the correctness of encoding.
*/
vector<double> output;
cout << " + Decode input vector ...... Correct." << endl;
encoder.decode(plain, output);
print_vector(output);
/*
The vector is encrypted the same was as in BFV.
*/
Ciphertext encrypted;
print_line(__LINE__);
cout << "Encrypt input vector, square, and relinearize." << endl;
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 (poly_modulus_degree / 2); this is because of
the implicit zero-padding mentioned above.
*/
evaluator.square_inplace(encrypted);
evaluator.relinearize_inplace(encrypted, relin_keys);
/*
We notice that the scale in the result has increased. In fact, it is now the
square of the original scale: 2^60.
*/
cout << " + Scale in squared input: " << encrypted.scale() << " (" << log2(encrypted.scale()) << " bits)"
<< endl;
print_line(__LINE__);
cout << "Decrypt and decode." << endl;
decryptor.decrypt(encrypted, plain);
encoder.decode(plain, output);
cout << " + Result vector ...... Correct." << endl;
print_vector(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.cpp' and later in
`4_ckks_basics.cpp'.
*/
}
void example_encoders()
{
print_example_banner("Example: Encoders");
/*
Run all encoder examples.
*/
example_integer_encoder();
example_batch_encoder();
example_ckks_encoder();
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include "examples.h"
using namespace std;
using namespace seal;
void example_levels()
{
print_example_banner("Example: Levels");
/*
In this examples we describe the concept of `levels' in BFV and CKKS and the
related objects that represent them in Microsoft SEAL.
In Microsoft SEAL a set of encryption parameters (excluding the random number
generator) is identified uniquely by a 256-bit hash of the parameters. This
hash is called the `parms_id' 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., plain_modulus is larger than the remaining coeff_modulus). 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 parms_id of its specific encryption parameters
(poly_modulus_degree remains the same but coeff_modulus varies).
*/
EncryptionParameters parms(scheme_type::BFV);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
/*
In this example we use a custom coeff_modulus, 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.cpp'. Indeed,
CoeffModulus::MaxBitCount(poly_modulus_degree)
returns 218 (greater than 50+30+30+50+50=210).
Due to the modulus switching chain, the order of the 5 primes is significant.
The last prime has a special meaning and we call it the `special prime'. Thus,
the first parameter set in the modulus switching chain is the only one that
involves the special prime. All key objects, such as SecretKey, are created
at this highest level. All data objects, such as Ciphertext, can be only at
lower levels. The special prime should be as large as the largest of the
other primes in the coeff_modulus, although this is not a strict requirement.
special prime +---------+
|
v
coeff_modulus: { 50, 30, 30, 50, 50 } +---+ Level 4 (all keys; `key level')
|
|
coeff_modulus: { 50, 30, 30, 50 } +---+ Level 3 (highest `data level')
|
|
coeff_modulus: { 50, 30, 30 } +---+ Level 2
|
|
coeff_modulus: { 50, 30 } +---+ Level 1
|
|
coeff_modulus: { 50 } +---+ Level 0 (lowest level)
*/
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 50, 30, 30, 50, 50 }));
/*
In this example the plain_modulus does not play much of a role; we choose
some reasonable value.
*/
parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));
auto context = SEALContext::Create(parms);
print_parameters(context);
cout << endl;
/*
There are convenience method for accessing the SEALContext::ContextData for
some of the most important levels:
SEALContext::key_context_data(): access to key level ContextData
SEALContext::first_context_data(): access to highest data level ContextData
SEALContext::last_context_data(): access to lowest level ContextData
We iterate over the chain and print the parms_id for each set of parameters.
*/
print_line(__LINE__);
cout << "Print the modulus switching chain." << endl;
/*
First print the key level parameter information.
*/
auto context_data = context->key_context_data();
cout << "----> Level (chain index): " << context_data->chain_index();
cout << " ...... key_context_data()" << endl;
cout << " parms_id: " << context_data->parms_id() << endl;
cout << " coeff_modulus primes: ";
cout << hex;
for (const auto &prime : context_data->parms().coeff_modulus())
{
cout << prime.value() << " ";
}
cout << dec << endl;
cout << "\\" << endl;
cout << " \\-->";
/*
Next iterate over the remaining (data) levels.
*/
context_data = context->first_context_data();
while (context_data)
{
cout << " Level (chain index): " << context_data->chain_index();
if (context_data->parms_id() == context->first_parms_id())
{
cout << " ...... first_context_data()" << endl;
}
else if (context_data->parms_id() == context->last_parms_id())
{
cout << " ...... last_context_data()" << endl;
}
else
{
cout << endl;
}
cout << " parms_id: " << context_data->parms_id() << endl;
cout << " coeff_modulus primes: ";
cout << hex;
for (const auto &prime : context_data->parms().coeff_modulus())
{
cout << prime.value() << " ";
}
cout << dec << endl;
cout << "\\" << endl;
cout << " \\-->";
/*
Step forward in the chain.
*/
context_data = context_data->next_context_data();
}
cout << " End of chain reached" << endl << endl;
/*
We create some keys and check that indeed they appear at the highest level.
*/
KeyGenerator keygen(context);
auto public_key = keygen.public_key();
auto secret_key = keygen.secret_key();
auto relin_keys = keygen.relin_keys_local();
/*
In this example we create a local version of the GaloisKeys object using
KeyGenerator::galois_keys_local(). In a production setting where the Galois
keys would need to be communicated to a server, it would be much better to
use KeyGenerator::galois_keys(), which outputs a Serializable<GaloisKeys>
object for compressed serialization.
*/
auto galois_keys = keygen.galois_keys_local();
print_line(__LINE__);
cout << "Print the parameter IDs of generated elements." << endl;
cout << " + public_key: " << public_key.parms_id() << endl;
cout << " + secret_key: " << secret_key.parms_id() << endl;
cout << " + relin_keys: " << relin_keys.parms_id() << endl;
cout << " + galois_keys: " << galois_keys.parms_id() << endl;
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
/*
In the BFV scheme plaintexts do not carry a parms_id, but ciphertexts do. Note
how the freshly encrypted ciphertext is at the highest data level.
*/
Plaintext plain("1x^3 + 2x^2 + 3x^1 + 4");
Ciphertext encrypted;
encryptor.encrypt(plain, encrypted);
cout << " + plain: " << plain.parms_id() << " (not set in BFV)" << endl;
cout << " + encrypted: " << encrypted.parms_id() << endl << endl;
/*
`Modulus switching' is a technique of changing the ciphertext parameters down
in the chain. The function Evaluator::mod_switch_to_next always switches to
the next level down the chain, whereas Evaluator::mod_switch_to switches to
a parameter set down the chain corresponding to a given parms_id. However, it
is impossible to switch up in the chain.
*/
print_line(__LINE__);
cout << "Perform modulus switching on encrypted and print." << endl;
context_data = context->first_context_data();
cout << "---->";
while (context_data->next_context_data())
{
cout << " Level (chain index): " << context_data->chain_index() << endl;
cout << " parms_id of encrypted: " << encrypted.parms_id() << endl;
cout << " Noise budget at this level: " << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
cout << "\\" << endl;
cout << " \\-->";
evaluator.mod_switch_to_next_inplace(encrypted);
context_data = context_data->next_context_data();
}
cout << " Level (chain index): " << context_data->chain_index() << endl;
cout << " parms_id of encrypted: " << encrypted.parms_id() << endl;
cout << " Noise budget at this level: " << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
cout << "\\" << endl;
cout << " \\-->";
cout << " End of chain reached" << endl << endl;
/*
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.
*/
print_line(__LINE__);
cout << "Decrypt still works after modulus switching." << endl;
decryptor.decrypt(encrypted, plain);
cout << " + Decryption of encrypted: " << plain.to_string();
cout << " ...... Correct." << endl << endl;
/*
However, there is a hidden benefit: the size of the ciphertext depends
linearly on the number of primes in the coefficient modulus. Thus, if there
is no need or intention to perform any further computations on a given
ciphertext, we might as well switch it down to the smallest (last) set of
parameters in the chain before sending it back to the secret key holder for
decryption.
Also the lost noise budget is actually not an issue at all, if we do things
right, as we will see below.
First we recreate the original ciphertext and perform some computations.
*/
cout << "Computation is more efficient with modulus switching." << endl;
print_line(__LINE__);
cout << "Compute the 8th power." << endl;
encryptor.encrypt(plain, encrypted);
cout << " + Noise budget fresh: " << decryptor.invariant_noise_budget(encrypted) << " bits"
<< endl;
evaluator.square_inplace(encrypted);
evaluator.relinearize_inplace(encrypted, relin_keys);
cout << " + Noise budget of the 2nd power: " << decryptor.invariant_noise_budget(encrypted) << " bits"
<< endl;
evaluator.square_inplace(encrypted);
evaluator.relinearize_inplace(encrypted, relin_keys);
cout << " + Noise budget of the 4th power: " << decryptor.invariant_noise_budget(encrypted) << " bits"
<< endl;
/*
Surprisingly, in this case modulus switching has no effect at all on the
noise budget.
*/
evaluator.mod_switch_to_next_inplace(encrypted);
cout << " + Noise budget after modulus switching: " << decryptor.invariant_noise_budget(encrypted) << " bits"
<< endl;
/*
This means that there is no harm at all in dropping some of the coefficient
modulus after doing enough computations. In some cases one might want to
switch to a lower level slightly earlier, actually sacrificing some of the
noise budget in the process, to gain computational performance from having
smaller parameters. We see from the print-out that the next modulus switch
should be done ideally when the noise budget is down to around 25 bits.
*/
evaluator.square_inplace(encrypted);
evaluator.relinearize_inplace(encrypted, relin_keys);
cout << " + Noise budget of the 8th power: " << decryptor.invariant_noise_budget(encrypted) << " bits"
<< endl;
evaluator.mod_switch_to_next_inplace(encrypted);
cout << " + Noise budget after modulus switching: " << decryptor.invariant_noise_budget(encrypted) << " bits"
<< endl;
/*
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);
cout << " + Decryption of the 8th power (hexadecimal) ...... Correct." << endl;
cout << " " << plain.to_string() << endl << endl;
/*
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::Create.
*/
context = SEALContext::Create(parms, 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.
*/
cout << "Optionally disable modulus switching chain expansion." << endl;
print_line(__LINE__);
cout << "Print the modulus switching chain." << endl;
cout << "---->";
for (context_data = context->key_context_data(); context_data; context_data = context_data->next_context_data())
{
cout << " Level (chain index): " << context_data->chain_index() << endl;
cout << " parms_id: " << context_data->parms_id() << endl;
cout << " coeff_modulus primes: ";
cout << hex;
for (const auto &prime : context_data->parms().coeff_modulus())
{
cout << prime.value() << " ";
}
cout << dec << endl;
cout << "\\" << endl;
cout << " \\-->";
}
cout << " End of chain reached" << endl << endl;
/*
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.
*/
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include "examples.h"
using namespace std;
using namespace seal;
void example_ckks_basics()
{
print_example_banner("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.
*/
EncryptionParameters parms(scheme_type::CKKS);
/*
We saw in `2_encoders.cpp' 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 coeff_modulus, 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.cpp').
As modulus switching, it removes the last of the primes from coeff_modulus,
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 coeff_modulus.
More precisely, suppose that the scale in a CKKS ciphertext is S, and the
last prime in the current coeff_modulus (for the ciphertext) is P. Rescaling
to the next level changes the scale to S/P, and removes the prime P from the
coeff_modulus, 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 coeff_modulus 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 coeff_modulus. This will
give the highest precision when decrypting;
(2) Choose another 60-bit prime as the last element of coeff_modulus, 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 coeff_modulus is 200 bits total, which is below the bound for our
poly_modulus_degree: CoeffModulus::MaxBitCount(8192) returns 218.
*/
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 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 = pow(2.0, 40);
auto context = SEALContext::Create(parms);
print_parameters(context);
cout << endl;
KeyGenerator keygen(context);
auto public_key = keygen.public_key();
auto secret_key = keygen.secret_key();
auto relin_keys = keygen.relin_keys_local();
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
CKKSEncoder encoder(context);
size_t slot_count = encoder.slot_count();
cout << "Number of slots: " << slot_count << endl;
vector<double> input;
input.reserve(slot_count);
double curr_point = 0;
double step_size = 1.0 / (static_cast<double>(slot_count) - 1);
for (size_t i = 0; i < slot_count; i++, curr_point += step_size)
{
input.push_back(curr_point);
}
cout << "Input vector: " << endl;
print_vector(input, 3, 7);
cout << "Evaluating polynomial PI*x^3 + 0.4x + 1 ..." << endl;
/*
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.
*/
Plaintext plain_coeff3, plain_coeff1, plain_coeff0;
encoder.encode(3.14159265, scale, plain_coeff3);
encoder.encode(0.4, scale, plain_coeff1);
encoder.encode(1.0, scale, plain_coeff0);
Plaintext x_plain;
print_line(__LINE__);
cout << "Encode input vectors." << endl;
encoder.encode(input, scale, x_plain);
Ciphertext x1_encrypted;
encryptor.encrypt(x_plain, x1_encrypted);
/*
To compute x^3 we first compute x^2 and relinearize. However, the scale has
now grown to 2^80.
*/
Ciphertext x3_encrypted;
print_line(__LINE__);
cout << "Compute x^2 and relinearize:" << endl;
evaluator.square(x1_encrypted, x3_encrypted);
evaluator.relinearize_inplace(x3_encrypted, relin_keys);
cout << " + Scale of x^2 before rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
/*
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.
*/
print_line(__LINE__);
cout << "Rescale x^2." << endl;
evaluator.rescale_to_next_inplace(x3_encrypted);
cout << " + Scale of x^2 after rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
/*
Now x3_encrypted is at a different level than x1_encrypted, which prevents us
from multiplying them to compute x^3. We could simply switch x1_encrypted to
the next parameters in the modulus switching chain. However, since we still
need to multiply the x^3 term with PI (plain_coeff3), 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.
*/
print_line(__LINE__);
cout << "Compute and rescale PI*x." << endl;
Ciphertext x1_encrypted_coeff3;
evaluator.multiply_plain(x1_encrypted, plain_coeff3, x1_encrypted_coeff3);
cout << " + Scale of PI*x before rescale: " << log2(x1_encrypted_coeff3.scale()) << " bits" << endl;
evaluator.rescale_to_next_inplace(x1_encrypted_coeff3);
cout << " + Scale of PI*x after rescale: " << log2(x1_encrypted_coeff3.scale()) << " bits" << endl;
/*
Since x3_encrypted and x1_encrypted_coeff3 have the same exact scale and use
the same encryption parameters, we can multiply them together. We write the
result to x3_encrypted, 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.
*/
print_line(__LINE__);
cout << "Compute, relinearize, and rescale (PI*x)*x^2." << endl;
evaluator.multiply_inplace(x3_encrypted, x1_encrypted_coeff3);
evaluator.relinearize_inplace(x3_encrypted, relin_keys);
cout << " + Scale of PI*x^3 before rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
evaluator.rescale_to_next_inplace(x3_encrypted);
cout << " + Scale of PI*x^3 after rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
/*
Next we compute the degree one term. All this requires is one multiply_plain
with plain_coeff1. We overwrite x1_encrypted with the result.
*/
print_line(__LINE__);
cout << "Compute and rescale 0.4*x." << endl;
evaluator.multiply_plain_inplace(x1_encrypted, plain_coeff1);
cout << " + Scale of 0.4*x before rescale: " << log2(x1_encrypted.scale()) << " bits" << endl;
evaluator.rescale_to_next_inplace(x1_encrypted);
cout << " + Scale of 0.4*x after rescale: " << log2(x1_encrypted.scale()) << " bits" << endl;
/*
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 (parms_id) match. If there
is a mismatch, Evaluator will throw an exception.
*/
cout << endl;
print_line(__LINE__);
cout << "Parameters used by all three terms are different." << endl;
cout << " + Modulus chain index for x3_encrypted: "
<< context->get_context_data(x3_encrypted.parms_id())->chain_index() << endl;
cout << " + Modulus chain index for x1_encrypted: "
<< context->get_context_data(x1_encrypted.parms_id())->chain_index() << endl;
cout << " + Modulus chain index for plain_coeff0: "
<< context->get_context_data(plain_coeff0.parms_id())->chain_index() << endl;
cout << endl;
/*
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/P_2 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.
*/
print_line(__LINE__);
cout << "The exact scales of all three terms are different:" << endl;
ios old_fmt(nullptr);
old_fmt.copyfmt(cout);
cout << fixed << setprecision(10);
cout << " + Exact scale in PI*x^3: " << x3_encrypted.scale() << endl;
cout << " + Exact scale in 0.4*x: " << x1_encrypted.scale() << endl;
cout << " + Exact scale in 1: " << plain_coeff0.scale() << endl;
cout << endl;
cout.copyfmt(old_fmt);
/*
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 multiply_plain
with 0.4*x, and finally rescale. In this case we would need to additionally
make sure to encode 1 with appropriate encryption parameters (parms_id).
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.
*/
print_line(__LINE__);
cout << "Normalize scales to 2^40." << endl;
x3_encrypted.scale() = pow(2.0, 40);
x1_encrypted.scale() = 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.
*/
print_line(__LINE__);
cout << "Normalize encryption parameters to the lowest level." << endl;
parms_id_type last_parms_id = x3_encrypted.parms_id();
evaluator.mod_switch_to_inplace(x1_encrypted, last_parms_id);
evaluator.mod_switch_to_inplace(plain_coeff0, last_parms_id);
/*
All three ciphertexts are now compatible and can be added.
*/
print_line(__LINE__);
cout << "Compute PI*x^3 + 0.4*x + 1." << endl;
Ciphertext encrypted_result;
evaluator.add(x3_encrypted, x1_encrypted, encrypted_result);
evaluator.add_plain_inplace(encrypted_result, plain_coeff0);
/*
First print the true result.
*/
Plaintext plain_result;
print_line(__LINE__);
cout << "Decrypt and decode PI*x^3 + 0.4x + 1." << endl;
cout << " + Expected result:" << endl;
vector<double> true_result;
for (size_t i = 0; i < input.size(); i++)
{
double x = input[i];
true_result.push_back((3.14159265 * x * x + 0.4) * x + 1);
}
print_vector(true_result, 3, 7);
/*
Decrypt, decode, and print the result.
*/
decryptor.decrypt(encrypted_result, plain_result);
vector<double> result;
encoder.decode(plain_result, result);
cout << " + Computed result ...... Correct." << endl;
print_vector(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.
*/
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment