summary refs log tree commit diff
path: root/crypto/src/pqc
diff options
context:
space:
mode:
authorDavid Hook <dgh@cryptoworkshop.com>2022-08-25 12:56:49 +1000
committerDavid Hook <dgh@cryptoworkshop.com>2022-08-25 12:56:49 +1000
commitc50de2843354b736ee5dcd4482f91d4155c7f6fb (patch)
tree910e2252be5d3f6d932494a7a3de107965b9fd81 /crypto/src/pqc
parentminor refactoring, added sampling (diff)
downloadBouncyCastle.NET-ed25519-c50de2843354b736ee5dcd4482f91d4155c7f6fb.tar.xz
initial CRYSTALS-Kyber implementation
Diffstat (limited to 'crypto/src/pqc')
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/CBD.cs76
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs189
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs293
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs36
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs85
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs22
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs49
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs38
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs21
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Ntt.cs97
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Poly.cs269
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs229
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Reduce.cs35
-rw-r--r--crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs32
16 files changed, 1513 insertions, 0 deletions
diff --git a/crypto/src/pqc/crypto/crystals/kyber/CBD.cs b/crypto/src/pqc/crypto/crystals/kyber/CBD.cs
new file mode 100644
index 000000000..843b0d8f6
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/CBD.cs
@@ -0,0 +1,76 @@
+
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class CBD
+    {
+        public static void Eta(Poly r, byte[] bytes, int eta)
+        {
+            int i, j;
+            uint t, d;
+            short a, b;
+
+            switch (eta)
+            {
+                case 2:
+                    for (i = 0; i < KyberEngine.N / 8; i++)
+                    {
+                        t = LittleEndianToUInt32(bytes, 4 * i);
+                        d = t & 0x55555555;
+                        d += (t >> 1) & 0x55555555;
+                        for (j = 0; j < 8; j++)
+                        {
+                            a = (short)((d >> (4 * j + 0)) & 0x3);
+                            b = (short)((d >> (4 * j + eta)) & 0x3);
+                            r.Coeffs[8 * i + j] = (short) (a - b);
+                        }
+                    }
+                    break;
+                case 3:
+                    for (i = 0; i < KyberEngine.N / 4; i++)
+                    {
+                        t = LittleEndianToUInt24(bytes, 3 * i);
+                        d = t & 0x00249249;
+                        d += (t >> 1) & 0x00249249;
+                        d += (t >> 2) & 0x00249249;
+
+                        for (j = 0; j < 4; j++)
+                        {
+                            a = (short)((d >> (6 * j + 0)) & 0x7);
+                            b = (short)((d >> (6 * j + 3)) & 0x7);
+                            r.Coeffs[4 * i + j] = (short)(a - b);
+                        }
+                    }
+                    break;
+                default:
+                    throw new ArgumentException("Wrong Eta");
+            }
+        }
+
+        private static uint LittleEndianToUInt24(byte[] x, int offset)
+        {
+            // Refer to convertByteTo32-BitUnsignedInt for explanation
+            uint r = (uint) (x[offset] & 0xFF);
+            r = r | ((uint)(x[offset + 1] & 0xFF) << 8);
+            r = r | ((uint)(x[offset + 2] & 0xFF) << 16);
+            return r;
+        }
+
+        private static uint LittleEndianToUInt32(byte[] x, int offset)
+        {
+            // Convert first byte to an unsigned integer 
+            // byte x & 0xFF allows us to grab the last 8 bits
+            uint r = (uint)(x[offset] & 0xFF);
+
+            // Perform the same operation then left bit shift to store the next 8 bits without
+            // altering the previous bits
+            r = r | ((uint)(x[offset + 1] & 0xFF) << 8);
+            r = r | ((uint)(x[offset + 2] & 0xFF) << 16);
+            r = r | ((uint)(x[offset + 3] & 0xFF) << 24);
+            return r;
+        }
+
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs
new file mode 100644
index 000000000..0cbd56e7f
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberEngine.cs
@@ -0,0 +1,189 @@
+using System;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal class KyberEngine
+    {
+        private SecureRandom _random;
+        private KyberIndCpa IndCpa;
+        private byte[] Seed;
+
+        // Constant Parameters
+        public const int N = 256;
+        public const int Q = 3329;
+        public const int QInv = 62209;
+
+        public static int SymBytes = 32;
+        private const int SharedSecretBytes = 32;
+
+        public static int PolyBytes = 384;
+
+        public const int Eta2 = 2;
+
+        public int IndCpaMsgBytes = SymBytes;
+
+
+        // Parameters
+        public int K { get; private set; }
+        public int PolyVecBytes { get; private set; }
+        public int PolyCompressedBytes { get; private set; }
+        public int PolyVecCompressedBytes { get; private set; }
+        public int Eta1 { get; private set; }
+        public int IndCpaPublicKeyBytes { get; private set; }
+        public int IndCpaSecretKeyBytes { get; private set; }
+        public int IndCpaBytes { get; private set; }
+        public int PublicKeyBytes { get; private set; }
+        public int SecretKeyBytes { get; private set; }
+        public int CipherTextBytes { get; private set; }
+
+        // Crypto
+        public int CryptoBytes { get; private set; }
+        public int CryptoSecretKeyBytes { get; private set; }
+        public int CryptoPublicKeyBytes { get; private set; }
+        public int CryptoCipherTextBytes { get; private set; }
+
+        public KyberEngine(int k)
+        {
+            K = k;
+            switch (k)
+            {
+                case 2:
+                    Eta1 = 3;
+                    PolyCompressedBytes = 128;
+                    PolyVecCompressedBytes = K * 320;
+                    break;
+                case 3:
+                    Eta1 = 2;
+                    PolyCompressedBytes = 128;
+                    PolyVecCompressedBytes = K * 320;
+                    break;
+                case 4:
+                    Eta1 = 2;
+                    PolyCompressedBytes = 160;
+                    PolyVecCompressedBytes = K * 352;
+                    break;
+                default:
+                    break;
+            }
+
+            PolyVecBytes = k * PolyBytes;
+            IndCpaPublicKeyBytes = PolyVecBytes + SymBytes;
+            IndCpaSecretKeyBytes = PolyVecBytes;
+            IndCpaBytes = PolyVecCompressedBytes + PolyCompressedBytes;
+            PublicKeyBytes = IndCpaPublicKeyBytes;
+            SecretKeyBytes = IndCpaSecretKeyBytes + IndCpaPublicKeyBytes + 2 * SymBytes;
+            CipherTextBytes = IndCpaBytes;
+
+            // Define Crypto Params
+            CryptoBytes = SharedSecretBytes;
+            CryptoSecretKeyBytes = SecretKeyBytes;
+            CryptoPublicKeyBytes = PublicKeyBytes;
+            CryptoCipherTextBytes = CipherTextBytes;
+
+            IndCpa = new KyberIndCpa(this);
+        }
+
+        public void Init(SecureRandom random)
+        {
+            this._random = random;
+        }
+
+        public void UpdateSeed(byte[] seed)
+        {
+            this.Seed = seed;
+            _random.SetSeed(seed);
+        }
+        
+        public void GenerateKemKeyPair(byte[] pk, byte[] sk)
+        {
+            Sha3Digest Sha3Digest256 = new Sha3Digest(256);
+            IndCpa.GenerateKeyPair(pk, sk);
+            Array.Copy(pk, 0, sk, IndCpaSecretKeyBytes, IndCpaPublicKeyBytes);
+            Sha3Digest256.BlockUpdate(pk, 0, PublicKeyBytes);
+            Sha3Digest256.DoFinal(sk, SecretKeyBytes - 2 * SymBytes);
+            _random.NextBytes(sk, SecretKeyBytes - SymBytes, SymBytes);            
+        }
+
+        public void KemEncrypt(byte[] cipherText, byte[] sharedSecret, byte[] pk)
+        {
+            byte[] buf = new byte[2 * SymBytes];
+            byte[] kr = new byte[2 * SymBytes];
+
+            Sha3Digest Sha3Digest256 = new Sha3Digest(256);
+
+            _random.NextBytes(buf, 0, SymBytes);
+
+            Sha3Digest256.BlockUpdate(buf, 0, SymBytes);
+            Sha3Digest256.DoFinal(buf, 0);
+
+            Sha3Digest256.BlockUpdate(pk, 0, PublicKeyBytes);
+            Sha3Digest256.DoFinal(buf, SymBytes);
+
+            Sha3Digest Sha3Digest512 = new Sha3Digest(512);
+            Sha3Digest512.BlockUpdate(buf, 0, 2 * SymBytes);
+            Sha3Digest512.DoFinal(kr, 0);
+
+            IndCpa.Encrypt(cipherText, Arrays.CopyOfRange(buf, 0, SymBytes), pk, Arrays.CopyOfRange(kr, SymBytes, 2 * SymBytes));
+            Sha3Digest256.BlockUpdate(cipherText, 0, CipherTextBytes);
+            Sha3Digest256.DoFinal(kr, SymBytes);
+
+            ShakeDigest ShakeDigest128 = new ShakeDigest(256);
+            
+            ShakeDigest128.BlockUpdate(kr, 0, 2 * SymBytes);
+            ShakeDigest128.DoFinal(sharedSecret, 0, SymBytes);
+        }
+
+        public void KemDecrypt(byte[] SharedSecret, byte[] CipherText, byte[] SecretKey)
+        {
+            int i;
+            bool fail;
+            byte[] buf = new byte[2 * SymBytes],
+                kr = new byte[2 * SymBytes],
+                cmp = new byte[CipherTextBytes];
+            byte[] pk = Arrays.CopyOfRange(SecretKey, IndCpaSecretKeyBytes, SecretKey.Length);
+            IndCpa.Decrypt(buf, CipherText, SecretKey);
+            Array.Copy(SecretKey, SecretKeyBytes - 2 * SymBytes, buf, SymBytes, SymBytes);
+
+            Sha3Digest Sha3Digest512 = new Sha3Digest(512);
+            Sha3Digest512.BlockUpdate(buf, 0, 2 * SymBytes);
+            Sha3Digest512.DoFinal(kr, 0);
+
+            IndCpa.Encrypt(cmp, Arrays.CopyOf(buf, SymBytes), pk, Arrays.CopyOfRange(kr, SymBytes, kr.Length));
+
+            fail = !(Arrays.AreEqual(CipherText, cmp));
+
+            Sha3Digest Sha3Digest256 = new Sha3Digest(256);
+            Sha3Digest256.BlockUpdate(CipherText, 0, CipherTextBytes);
+            Sha3Digest256.DoFinal(kr, SymBytes);
+
+            Cmov(kr, Arrays.CopyOfRange(SecretKey, SecretKeyBytes - SymBytes, SecretKeyBytes), SymBytes, fail);
+
+            ShakeDigest ShakeDigest256 = new ShakeDigest(256);
+            ShakeDigest256.BlockUpdate(kr, 0, 2 * SymBytes);
+            ShakeDigest256.DoFinal(SharedSecret, 0, SymBytes);
+        }
+
+        private void Cmov(byte[] r, byte[] x, int len, bool b)
+        {
+            int i;
+            if (b)
+            {
+                Array.Copy(x, 0, r, 0, len);
+            }
+            else
+            {
+                Array.Copy(r, 0, r, 0, len);
+            }
+        }
+        
+        public void RandomBytes(byte[] buf, int len)
+        {
+            _random.NextBytes(buf,0,len);
+        }
+    }
+}
+
+
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs
new file mode 100644
index 000000000..da4f94b88
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberIndCpa.cs
@@ -0,0 +1,293 @@
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Utilities;
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal class KyberIndCpa
+    {
+        KyberEngine Engine;
+        int K;
+        public KyberIndCpa(KyberEngine engine)
+        {
+            Engine = engine;
+            this.K = engine.K;
+            return;
+        }
+
+        private int XOFBlockBytes => Symmetric.Shake128Rate;
+
+        private int GenerateMatrixNBlocks => ((12 * KyberEngine.N / 8 * (1 << 12) / KyberEngine.Q + XOFBlockBytes) / XOFBlockBytes);
+
+        private void GenerateMatrix(PolyVec[] a, byte[] seed, bool transposed)
+        {
+            int ctr, i, j, k;
+            int buflen, off;
+            ShakeDigest XOF = new ShakeDigest(128);
+            byte[] buf = new byte[GenerateMatrixNBlocks * XOFBlockBytes + 2];
+            for (i = 0; i < K; i++)
+            {
+                for (j = 0; j < K; j++)
+                {
+                    if (transposed)
+                    {
+                        XOF = Symmetric.XOF(seed, (byte) i, (byte) j);
+                    }
+                    else
+                    {
+                        XOF = Symmetric.XOF(seed, (byte) j, (byte) i);
+                    }
+                    XOF.DoOutput(buf, 0, GenerateMatrixNBlocks * XOFBlockBytes);
+                    buflen = GenerateMatrixNBlocks * XOFBlockBytes;
+                    ctr = RejectionSampling(a[i].Vec[j].Coeffs, 0, KyberEngine.N, buf, buflen);
+                    while (ctr < KyberEngine.N)
+                    {
+                        off = buflen % 3;
+                        for (k = 0; k < off; k++)
+                        {
+                            buf[k] = buf[buflen - off + k];
+                        }
+                        XOF.DoOutput(buf, off, XOFBlockBytes * 2);
+                        buflen = off + XOFBlockBytes;
+                        ctr += RejectionSampling(a[i].Vec[j].Coeffs, ctr, KyberEngine.N - ctr, buf, buflen);
+                    }
+
+                }
+            }
+            return;
+        }
+
+        private int RejectionSampling(short[] r, int off, int len, byte[] buf, int buflen)
+        {
+            int ctr, pos;
+            ushort val0, val1;
+            ctr = pos = 0;
+            while (ctr < len && pos + 3 <= buflen)
+            {
+                val0 = (ushort) ((((ushort) (buf[pos + 0] & 0xFF) >> 0) | ((ushort)(buf[pos + 1] & 0xFF) << 8)) & 0xFFF);
+                val1 = (ushort) ((((ushort) (buf[pos + 1] & 0xFF) >> 4) | ((ushort)(buf[pos + 2] & 0xFF)  << 4)) & 0xFFF);
+                pos += 3;
+
+                if (val0 < KyberEngine.Q)
+                {
+                    r[off + ctr++] = (short) val0;
+                }
+                if (ctr < len && val1 < KyberEngine.Q)
+                {
+                    r[off + ctr++] = (short) val1;
+                }
+            }
+
+            return ctr;
+        }
+
+        public void GenerateKeyPair(byte[] pk, byte[] sk)
+        {
+            uint i;
+            byte[] buf = new byte[2 * KyberEngine.SymBytes];
+            byte nonce = 0;
+            PolyVec[] Matrix = new PolyVec[K];
+            PolyVec e = new PolyVec(Engine), pkpv = new PolyVec(Engine), skpv = new PolyVec(Engine);
+            Sha3Digest Sha3Digest512 = new Sha3Digest(512);
+
+            Engine.RandomBytes(buf, KyberEngine.SymBytes);
+            
+            Sha3Digest512.BlockUpdate(buf, 0, KyberEngine.SymBytes);
+            Sha3Digest512.DoFinal(buf, 0);
+
+            //Console.WriteLine(string.Format("buf = {0}", Convert.ToHexString(buf)));
+            byte[] PublicSeed = Arrays.CopyOfRange(buf, 0, KyberEngine.SymBytes);
+            byte[] NoiseSeed = Arrays.CopyOfRange(buf, KyberEngine.SymBytes, 2 * KyberEngine.SymBytes);
+
+            for (i = 0; i < K; i++)
+            {
+                Matrix[i] = new PolyVec(Engine);
+            }
+
+            GenerateMatrix(Matrix, PublicSeed, false);
+
+            for (i = 0; i < K; i++) 
+            {
+                skpv.Vec[i].GetNoiseEta1(NoiseSeed, nonce++);
+            }
+
+            for (i = 0; i < K; i++)
+            {
+                e.Vec[i].GetNoiseEta1(NoiseSeed, nonce++);
+            }
+
+
+
+            skpv.Ntt();
+            e.Ntt();
+
+            //Console.WriteLine("skpv = ");
+            //for (i = 0; i < K; i++)
+            //{
+            //    Console.Write(String.Format("{0} [", i));
+            //    foreach (short coeff in skpv.Vec[i].Coeffs)
+            //    {
+            //        Console.Write(String.Format("{0}, ", coeff));
+            //    }
+            //    Console.Write("]\n");
+            //}
+
+            //for (i = 0; i < K; i++)
+            //{
+            //    Console.Write("[");
+            //    for (int j = 0; j < K; j++)
+            //    {
+            //        Console.Write("[");
+            //        for (int k = 0; k < KyberEngine.N; k++)
+            //        {
+            //            Console.Write(String.Format("{0:G}, ", Matrix[i].Vec[j].Coeffs[k]));
+            //        }
+            //        Console.Write("], \n");
+            //    }
+            //    Console.Write("] \n");
+            //}
+
+            for (i = 0; i < K; i++)
+            {
+                PolyVec.PointwiseAccountMontgomery(pkpv.Vec[i], Matrix[i], skpv, Engine);
+                pkpv.Vec[i].ToMont();
+            }
+
+            //Console.WriteLine("pkpv = ");
+            //for (i = 0; i < K; i++)
+            //{
+            //    Console.Write(String.Format("{0} [", i));
+            //    foreach (short coeff in pkpv.Vec[i].Coeffs)
+            //    {
+            //        Console.Write(String.Format("{0}, ", coeff));
+            //    }
+            //    Console.Write("]\n");
+            //}
+            pkpv.Add(e);
+            pkpv.Reduce();
+
+
+
+
+            PackSecretKey(sk, skpv);
+            PackPublicKey(pk, pkpv, PublicSeed);
+        
+
+            return;
+        }
+
+        private void PackSecretKey(byte[] sk, PolyVec skpv)
+        {
+            skpv.ToBytes(sk);
+        }
+
+        private void UnpackSecretKey(PolyVec skpv, byte[] sk)
+        {
+            skpv.FromBytes(sk);
+        }
+
+        private void PackPublicKey(byte[] pk, PolyVec pkpv, byte[] seed)
+        {
+            int i;
+            pkpv.ToBytes(pk);
+            Array.Copy(seed, 0, pk, Engine.PolyVecBytes, KyberEngine.SymBytes);
+        }
+
+        private void UnpackPublicKey(PolyVec pkpv, byte[] seed, byte[] pk)
+        {
+            int i;
+            pkpv.FromBytes(pk);
+            Array.Copy(pk, Engine.PolyVecBytes, seed, 0, KyberEngine.SymBytes);
+        }
+
+        public void Encrypt(byte[] c, byte[] m, byte[] pk, byte[] coins)
+        {
+            int i;
+            byte[] seed = new byte[KyberEngine.SymBytes];
+            byte nonce = (byte)0;
+            PolyVec sp = new PolyVec(Engine), pkpv = new PolyVec(Engine), ep = new PolyVec(Engine), bp = new PolyVec(Engine);
+            PolyVec[] MatrixTransposed = new PolyVec[K];
+            Poly v = new Poly(Engine), k = new Poly(Engine), epp = new Poly(Engine);
+
+            UnpackPublicKey(pkpv, seed, pk);
+
+            k.FromMsg(m);
+
+            for (i = 0; i < K; i++)
+            {
+                MatrixTransposed[i] = new PolyVec(Engine);
+            }
+
+            GenerateMatrix(MatrixTransposed, seed, true);
+
+            for (i = 0; i < K; i++)
+            {
+                sp.Vec[i].GetNoiseEta1(coins, nonce++);
+            }
+
+            for (i = 0; i < K; i++)
+            {
+                ep.Vec[i].GetNoiseEta2(coins, nonce++);
+            }
+            epp.GetNoiseEta2(coins, nonce++);
+
+            sp.Ntt();
+
+            for (i = 0; i < K; i++)
+            {
+                PolyVec.PointwiseAccountMontgomery(bp.Vec[i], MatrixTransposed[i], sp, Engine);
+            }
+
+            PolyVec.PointwiseAccountMontgomery(v, pkpv, sp, Engine);
+
+            bp.InverseNttToMont();
+
+            v.PolyInverseNttToMont();
+
+            bp.Add(ep);
+
+            v.Add(epp);
+            v.Add(k);
+
+            bp.Reduce();
+            v.PolyReduce();
+
+            PackCipherText(c, bp, v);
+        }
+
+        private void PackCipherText(byte[] r, PolyVec b, Poly v)
+        {
+            b.CompressPolyVec(r);
+            v.CompressPoly(r, Engine.PolyVecCompressedBytes);
+        }
+
+        private void UnpackCipherText(PolyVec b, Poly v, byte[] c)
+        {
+            b.DecompressPolyVec(c);
+            v.DecompressPoly(c, Engine.PolyVecCompressedBytes);
+        }
+
+        public void Decrypt(byte[] m, byte[] c, byte[] sk)
+        {
+            PolyVec bp = new PolyVec(Engine),
+                skpv = new PolyVec(Engine);
+            Poly v = new Poly(Engine),
+                mp = new Poly(Engine);
+            int i;
+
+            UnpackCipherText(bp, v, c);
+
+            UnpackSecretKey(skpv, sk);
+
+            bp.Ntt();
+
+            PolyVec.PointwiseAccountMontgomery(mp, skpv, bp, Engine);
+
+            mp.PolyInverseNttToMont();
+            mp.Subtract(v);
+            mp.PolyReduce();
+            mp.ToMsg(m);
+
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs
new file mode 100644
index 000000000..e916e5575
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMExtractor.cs
@@ -0,0 +1,36 @@
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberKEMExtractor
+        : IEncapsulatedSecretExtractor
+    {
+        private KyberEngine engine;
+
+        private KyberKeyParameters key;
+
+        public KyberKEMExtractor(KyberKeyParameters privParams)
+        {
+            this.key = privParams;
+            InitCipher(key.Parameters);
+        }
+
+        private void InitCipher(KyberParameters param)
+        {
+            engine = param.GetEngine();
+        }
+
+        public byte[] ExtractSecret(byte[] encapsulation)
+        {
+            byte[] sessionKey = new byte[engine.CryptoBytes];
+            engine.KemDecrypt(sessionKey, encapsulation, ((KyberPrivateKeyParameters) key).privateKey);
+            byte[] rv = Arrays.CopyOfRange(sessionKey, 0, key.Parameters.DefaultKeySize / 8);
+            Arrays.Clear(sessionKey);
+            return rv;
+        }
+
+        public int InputSize => engine.CryptoCipherTextBytes;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs
new file mode 100644
index 000000000..c43f10ff1
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKEMGenerator.cs
@@ -0,0 +1,85 @@
+
+using System;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberKEMGenerator
+        : IEncapsulatedSecretGenerator
+    {
+        // the source of randomness
+        private SecureRandom sr;
+
+        public KyberKEMGenerator(SecureRandom random)
+        {
+            this.sr = random;
+        }
+
+        public ISecretWithEncapsulation GenerateEncapsulated(AsymmetricKeyParameter recipientKey)
+        {
+            KyberPublicKeyParameters key = (KyberPublicKeyParameters) recipientKey;
+            KyberEngine engine = key.Parameters.GetEngine();
+            engine.Init(sr);
+            byte[] CipherText = new byte[engine.CryptoCipherTextBytes];
+            byte[] SessionKey = new byte[engine.CryptoBytes];
+            engine.KemEncrypt(CipherText, SessionKey, key.publicKey);
+            byte[] rv = Arrays.CopyOfRange(SessionKey, 0, key.Parameters.DefaultKeySize / 8);
+            Arrays.Clear(SessionKey);
+            return new SecretWithEncapsulationImpl(rv, CipherText);
+        }
+
+        private class SecretWithEncapsulationImpl
+            : ISecretWithEncapsulation
+        {
+            private volatile bool hasBeenDestroyed = false;
+
+            private byte[] SessionKey;
+            private byte[] CipherText;
+
+            public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text)
+            {
+                this.SessionKey = sessionKey;
+                this.CipherText = cipher_text;
+            }
+
+            public byte[] GetSecret()
+            {
+                CheckDestroyed();
+
+                return Arrays.Clone(SessionKey);
+            }
+
+            public byte[] GetEncapsulation()
+            {
+                CheckDestroyed();
+
+                return Arrays.Clone(CipherText);
+            }
+
+            public void Dispose()
+            {
+                if (!hasBeenDestroyed)
+                {
+                    hasBeenDestroyed = true;
+                    Arrays.Clear(SessionKey);
+                    Arrays.Clear(CipherText);
+                }
+            }
+
+            public bool IsDestroyed()
+            {
+                return hasBeenDestroyed;
+            }
+
+            void CheckDestroyed()
+            {
+                if (IsDestroyed())
+                {
+                    throw new ArgumentException("data has been destroyed");
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs
new file mode 100644
index 000000000..f6adb0071
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyGenerationParameters.cs
@@ -0,0 +1,22 @@
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberKeyGenerationParameters
+        : KeyGenerationParameters
+    {
+        private KyberParameters parameters;
+
+        public KyberKeyGenerationParameters(
+            SecureRandom random,
+            KyberParameters KyberParameters)
+            : base(random, 256)
+        {
+            this.parameters = KyberParameters;
+        }
+
+        public KyberParameters Parameters => parameters;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs
new file mode 100644
index 000000000..39ee496f2
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyPairGenerator.cs
@@ -0,0 +1,49 @@
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberKeyPairGenerator
+        : IAsymmetricCipherKeyPairGenerator
+    {
+        private KyberKeyGenerationParameters KyberParams;
+
+        private int k;
+
+        private SecureRandom random;
+
+        private void Initialize(
+            KeyGenerationParameters param)
+        {
+            this.KyberParams = (KyberKeyGenerationParameters) param;
+            this.random = param.Random;
+
+            this.k = this.KyberParams.Parameters.K;
+        }
+
+        private AsymmetricCipherKeyPair GenKeyPair()
+        {
+            KyberEngine engine = KyberParams.Parameters.GetEngine();
+            engine.Init(random);
+            byte[] sk = new byte[engine.CryptoSecretKeyBytes];
+            byte[] pk = new byte[engine.CryptoPublicKeyBytes];
+            engine.GenerateKemKeyPair(pk, sk);
+
+            KyberPublicKeyParameters pubKey = new KyberPublicKeyParameters(KyberParams.Parameters, pk);
+            KyberPrivateKeyParameters privKey = new KyberPrivateKeyParameters(KyberParams.Parameters, sk);
+            return new AsymmetricCipherKeyPair(pubKey, privKey);
+        }
+
+        public void Init(KeyGenerationParameters param)
+        {
+            this.Initialize(param);
+        }
+
+        public AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            return GenKeyPair();
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs
new file mode 100644
index 000000000..37650cb4d
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberKeyParameters.cs
@@ -0,0 +1,21 @@
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberKeyParameters
+        : AsymmetricKeyParameter
+    {
+        private KyberParameters parameters;
+
+        public KyberKeyParameters(
+            bool isPrivate,
+            KyberParameters parameters)
+            : base(isPrivate)
+        {
+            this.parameters = parameters;
+        }
+
+        public KyberParameters Parameters => parameters;
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs
new file mode 100644
index 000000000..d0fd8a631
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberParameters.cs
@@ -0,0 +1,38 @@
+
+using System;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberParameters
+        : ICipherParameters
+    {
+
+        public static KyberParameters kyber512 = new KyberParameters("kyber512", 2);
+        public static KyberParameters kyber768 = new KyberParameters("kyber768", 3);
+        public static KyberParameters kyber1024 = new KyberParameters("kyber1024", 4);
+
+        private String name;
+        private int k;
+        private KyberEngine engine;
+
+        public KyberParameters(String name, int k)
+        {
+            this.name = name;
+            this.k = k;
+            this.engine = new KyberEngine(k);
+        }
+
+        public String Name => name;
+
+        public int K => k;
+
+        public int DefaultKeySize => 64 * k;
+
+        internal KyberEngine GetEngine()
+        {
+            return engine;
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs
new file mode 100644
index 000000000..955b44673
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberPrivateKeyParameters.cs
@@ -0,0 +1,21 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberPrivateKeyParameters
+        : KyberKeyParameters
+    {
+        internal byte[] privateKey;
+
+        public KyberPrivateKeyParameters(KyberParameters parameters, byte[] privateKey)
+            : base(true, parameters)
+        {
+            this.privateKey = Arrays.Clone(privateKey);
+        }
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.Clone(privateKey);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs b/crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs
new file mode 100644
index 000000000..1092c7e96
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/KyberPublicKeyParameters.cs
@@ -0,0 +1,21 @@
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    public class KyberPublicKeyParameters
+        : KyberKeyParameters
+    {
+        internal byte[] publicKey;
+
+        public byte[] GetEncoded()
+        {
+            return Arrays.Clone(publicKey);
+        }
+
+        public KyberPublicKeyParameters(KyberParameters parameters, byte[] publicKey)
+            : base(false, parameters)
+        {
+            this.publicKey = Arrays.Clone(publicKey);
+        }
+    }
+}
\ No newline at end of file
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Ntt.cs b/crypto/src/pqc/crypto/crystals/kyber/Ntt.cs
new file mode 100644
index 000000000..f07e8da26
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Ntt.cs
@@ -0,0 +1,97 @@
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class Ntt
+    {
+        public static readonly short[] Zetas = {
+            2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962,
+            2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017,
+            732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047,
+            1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830,
+            107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226,
+            430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574,
+            1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349,
+            418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193,
+            1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459,
+            478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628
+        };
+
+        public static readonly short[] ZetasInv = {
+            1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535,
+            1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465,
+            1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685,
+            1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235,
+            3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652,
+            1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853,
+            1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552,
+            2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871,
+            829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171,
+            3127, 3042, 1907, 1836, 1517, 359, 758, 1441
+        };
+
+        private static short FactorQMulMont(short a, short b)
+        {
+            return Reduce.MontgomeryReduce(a * b);
+        }
+
+        public static void NTT(short[] r)
+        {
+            int len, start, j, k;
+            short t, zeta;
+
+            k = 1;
+            for (len = 128; len >= 2; len >>= 1)
+            {
+                for (start = 0; start < 256; start = j + len)
+                {
+                    zeta = Zetas[k++];
+                    for (j = start; j < start + len; ++j)
+                    {
+                        t = FactorQMulMont(zeta, r[j + len]);
+                        r[j + len] = (short)(r[j] - t);
+                        r[j] = (short)(r[j] + t);
+                    }
+                }
+            }
+        }
+
+        public static void InvNTT(short[] r)
+        {
+            int len, start, j, k;
+            short t, zeta;
+
+            k = 0;
+            for (len = 2; len <= 128; len <<= 1)
+            {
+                for (start = 0; start < 256; start = j + len)
+                {
+                    zeta = ZetasInv[k++];
+                    for (j = start; j < start + len; ++j)
+                    {
+                        t = r[j];
+                        r[j] = Reduce.BarrettReduce((short)(t + r[j + len]));
+                        r[j + len] = (short)(t - r[j + len]);
+                        r[j + len] = FactorQMulMont(zeta, r[j + len]);
+
+                    }
+                }
+            }
+            for (j = 0; j < 256; ++j)
+            {
+                r[j] = FactorQMulMont(r[j], Ntt.ZetasInv[127]);
+            }
+        }
+
+        public static void BaseMult(short[] r, int off, short a0, short a1, short b0, short b1, short zeta)
+        {
+            short OutVal0 = FactorQMulMont(a1, b1);
+            OutVal0 = FactorQMulMont(OutVal0, zeta);
+            OutVal0 += FactorQMulMont(a0, b0);
+            r[off] = OutVal0;
+
+            short OutVal1 = FactorQMulMont(a0, b1);
+            OutVal1 += FactorQMulMont(a1, b0);
+            r[off + 1] = OutVal1;
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Poly.cs b/crypto/src/pqc/crypto/crystals/kyber/Poly.cs
new file mode 100644
index 000000000..0b226bca1
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Poly.cs
@@ -0,0 +1,269 @@
+
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    class Poly
+    {
+        private KyberEngine Engine;
+        public short[] Coeffs { get; set; }
+        private int PolyCompressedBytes;
+        private int Eta1;
+        private int Eta2;
+        private int N;
+        public Poly(KyberEngine engine)
+        {
+            Engine = engine;
+            Coeffs = new short[KyberEngine.N];
+            PolyCompressedBytes = engine.PolyCompressedBytes;
+            Eta1 = engine.Eta1;
+            N = KyberEngine.N;
+        }
+
+        public void GetNoiseEta1(byte[] seed, byte nonce)
+        {
+           byte[] buf = new byte[Engine.Eta1 * KyberEngine.N / 4];
+            Symmetric.PRF(buf, buf.Length, seed, nonce);
+            CBD.Eta(this, buf, Engine.Eta1);
+        }
+
+        public void GetNoiseEta2(byte[] seed, byte nonce)
+        {
+            byte[] buf = new byte[KyberEngine.Eta2 * KyberEngine.N / 4];
+            Symmetric.PRF(buf, buf.Length, seed, nonce);
+            CBD.Eta(this, buf, KyberEngine.Eta2);
+        }
+
+        public void PolyNtt()
+        {
+            Ntt.NTT(Coeffs);
+            PolyReduce();
+        }
+
+        public void PolyInverseNttToMont()
+        {
+            Ntt.InvNTT(Coeffs);
+        }
+
+        public static void BaseMultMontgomery(Poly r, Poly a, Poly b)
+        {
+            int i;
+            for (i = 0; i < KyberEngine.N/4; i++)
+            {
+                Ntt.BaseMult(r.Coeffs, 4 * i,
+                    a.Coeffs[4 * i], a.Coeffs[4 * i + 1],
+                    b.Coeffs[4 * i], b.Coeffs[4 * i + 1],
+                    Ntt.Zetas[64 + i]);
+                Ntt.BaseMult(r.Coeffs, 4 * i + 2,
+                    a.Coeffs[4 * i + 2], a.Coeffs[4 * i + 3],
+                    b.Coeffs[4 * i + 2], b.Coeffs[4 * i + 3],
+                    (short) (-1  * Ntt.Zetas[64 + i]));
+            }
+        }
+
+        public void ToMont()
+        {
+            int i;
+            const short f = (short) ((1UL << 32) % KyberEngine.Q);
+            for (i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = Reduce.MontgomeryReduce(Coeffs[i] * f);
+            }
+        }
+
+        public void Add(Poly a)
+        {
+            int i;
+            for (i = 0; i < N; i++)
+            {
+                Coeffs[i] += a.Coeffs[i];
+            }
+        }
+
+        public void Subtract(Poly a)
+        {
+            int i;
+            for (i = 0; i < N; i++)
+            {
+                Coeffs[i] = (short) (a.Coeffs[i] - Coeffs[i]);
+            }
+        }
+
+        public void PolyReduce()
+        {
+            int i;
+            for (i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = Reduce.BarrettReduce(Coeffs[i]);
+            }
+        }
+
+        public void CompressPoly(byte[] r, int off)
+        {
+            int i, j;
+            byte[] t = new byte[8];
+            int count = 0;
+            CondSubQ();
+
+            if (Engine.PolyCompressedBytes == 128)
+            {
+                for (i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    for (j = 0; j < 8; j++)
+                    {
+                        t[j] =
+                            (byte)(((((Coeffs[8 * i + j]) << 4)
+                                +
+                                (KyberEngine.Q / 2)
+                            ) / KyberEngine.Q)
+                                & 15);
+                    }
+
+                    r[off + count + 0] = (byte)(t[0] | (t[1] << 4));
+                    r[off + count + 1] = (byte)(t[2] | (t[3] << 4));
+                    r[off + count + 2] = (byte)(t[4] | (t[5] << 4));
+                    r[off + count + 3] = (byte)(t[6] | (t[7] << 4));
+                    count += 4;
+                }
+            }
+            else if (Engine.PolyCompressedBytes == 160)
+            {
+                for (i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    for (j = 0; j < 8; j++)
+                    {
+                        t[j] =
+                            (byte)((((Coeffs[8 * i + j] << 5)
+                                +
+                                (KyberEngine.Q / 2)
+                            ) / KyberEngine.Q
+                            ) & 31
+                            );
+                    }
+                    r[off + count + 0] = (byte)((t[0] >> 0) | (t[1] << 5));
+                    r[off + count + 1] = (byte)((t[1] >> 3) | (t[2] << 2) | (t[3] << 7));
+                    r[off + count + 2] = (byte)((t[3] >> 1) | (t[4] << 4));
+                    r[off + count + 3] = (byte)((t[4] >> 4) | (t[5] << 1) | (t[6] << 6));
+                    r[off + count + 4] = (byte)((t[6] >> 2) | (t[7] << 3));
+                    count += 5;
+                }
+            }
+            else
+            {
+                throw new ArgumentException("PolyCompressedBytes is neither 128 or 160!");
+            }
+        }
+
+        public void DecompressPoly(byte[] CompressedCipherText, int off)
+        {
+            int i, count = off;
+
+            if (Engine.PolyCompressedBytes == 128)
+            {
+                for (i = 0; i < KyberEngine.N / 2; i++)
+                {
+                    Coeffs[2 * i + 0]  = (short)((((short)((CompressedCipherText[count] & 0xFF) & 15) * KyberEngine.Q) + 8) >> 4);
+                    Coeffs[2 * i + 1] = (short)((((short)((CompressedCipherText[count] & 0xFF) >> 4) * KyberEngine.Q) + 8) >> 4);
+                    count += 1;
+                }
+            }
+            else if (Engine.PolyCompressedBytes == 160)
+            {
+                int j;
+                byte[] t = new byte[8];
+                for (i = 0; i < KyberEngine.N / 8; i++)
+                {
+                    t[0] = (byte)((CompressedCipherText[count + 0] & 0xFF) >> 0);
+                    t[1] = (byte)(((CompressedCipherText[count + 0] & 0xFF) >> 5) | ((CompressedCipherText[count + 1] & 0xFF) << 3));
+                    t[2] = (byte)((CompressedCipherText[count + 1] & 0xFF) >> 2);
+                    t[3] = (byte)(((CompressedCipherText[count + 1] & 0xFF) >> 7) | ((CompressedCipherText[count + 2] & 0xFF) << 1));
+                    t[4] = (byte)(((CompressedCipherText[count + 2] & 0xFF) >> 4) | ((CompressedCipherText[count + 3] & 0xFF) << 4));
+                    t[5] = (byte)((CompressedCipherText[count + 3] & 0xFF) >> 1);
+                    t[6] = (byte)(((CompressedCipherText[count + 3] & 0xFF) >> 6) | ((CompressedCipherText[count + 4] & 0xFF) << 2));
+                    t[7] = (byte)((CompressedCipherText[count + 4] & 0xFF) >> 3);
+                    count += 5;
+                    for (j = 0; j < 8; j++)
+                    {
+                        Coeffs[8 * i + j] = (short)(((t[j] & 31) * KyberEngine.Q + 16) >> 5);
+                    }
+                }
+            }
+            else
+            {
+                throw new ArgumentException("PolyCompressedBytes is neither 128 or 160!");
+            }
+        }
+        
+        public void ToBytes(byte[] r, int off)
+        {
+            int i;
+            ushort t0, t1;
+
+            CondSubQ();
+
+            for (i = 0; i < KyberEngine.N/2; i++)
+            {
+                t0 = (ushort) Coeffs[2 * i];
+                t1 = (ushort) Coeffs[2 * i + 1];
+                r[off + 3 * i + 0] = (byte) (ushort) (t0 >> 0);
+                r[off + 3 * i + 1] = (byte)((t0 >> 8) | (ushort) (t1 << 4));
+                r[off + 3 * i + 2] = (byte) (ushort) (t1 >> 4);
+            }
+        }
+
+        public void FromBytes(byte[] a, int off)
+        {
+            int i;
+            for (i = 0; i < KyberEngine.N / 2; i++)
+            {
+                Coeffs[2 * i] = (short) ((((a[off + 3 * i + 0] & 0xFF) >> 0) | (ushort)((a[off + 3 * i + 1] & 0xFF) << 8)) & 0xFFF);
+                Coeffs[2 * i + 1] = (short) ((((a[off + 3 * i + 1] & 0xFF) >> 4) | (ushort)((a[off + 3 * i + 2] & 0xFF) << 4)) & 0xFFF);
+            }
+        }
+
+        public void ToMsg(byte[] msg)
+        {
+            int i, j;
+            short t;
+
+            CondSubQ();
+
+            for (i = 0; i < KyberEngine.N / 8; i++)
+            {
+                msg[i] = 0;
+                for (j = 0; j < 8; j++)
+                {
+                    t = (short)(((((short)(Coeffs[8 * i + j] << 1) + KyberEngine.Q / 2) / KyberEngine.Q) & 1));
+                    msg[i] |= (byte)(t << j);
+                }
+            }
+        }
+
+        public void FromMsg(byte[] m)
+        {
+            int i, j;
+            short mask;
+            if (m.Length != KyberEngine.N / 8)
+            {
+                throw new ArgumentException("KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!");
+            }
+            for (i = 0; i < KyberEngine.N / 8; i++)
+            {
+                for (j = 0; j < 8; j++)
+                {
+                    mask = (short)((-1) * (short)(((m[i] & 0xFF) >> j) & 1));
+                    Coeffs[8 * i + j] = (short)(mask & ((KyberEngine.Q + 1) / 2));
+                }
+            }
+        }
+
+        public void CondSubQ()
+        {
+            int i;
+            for (i = 0; i < KyberEngine.N; i++)
+            {
+                Coeffs[i] = Reduce.CondSubQ(Coeffs[i]);
+            }
+        }
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs b/crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs
new file mode 100644
index 000000000..7c915dcce
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/PolyVec.cs
@@ -0,0 +1,229 @@
+using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    class PolyVec
+    {
+        private KyberEngine Engine;
+        private int K;
+        private int PolyVecBytes;
+        public Poly[] Vec;
+
+
+
+
+        public PolyVec(KyberEngine engine)
+        {
+            Engine = engine;
+            K = engine.K;
+            PolyVecBytes = engine.PolyVecBytes;
+            Vec = new Poly[engine.K];
+            for (int i = 0; i < K; i++)
+            {
+                Vec[i] = new Poly(engine);
+            }
+        }
+
+        public void Ntt()
+        {
+            int i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].PolyNtt();
+            }
+        }
+
+        public void InverseNttToMont()
+        {
+            int i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].PolyInverseNttToMont();
+            }
+        }
+        
+        public static void PointwiseAccountMontgomery(Poly r, PolyVec a, PolyVec b, KyberEngine engine)
+        {
+            int i;
+            Poly t = new Poly(engine);
+            Poly.BaseMultMontgomery(r, a.Vec[0], b.Vec[0]);
+            for (i = 1; i < engine.K; i++)
+            {
+                Poly.BaseMultMontgomery(t, a.Vec[i], b.Vec[i]);
+                r.Add(t);
+            }
+            r.PolyReduce();
+        }
+
+        public void Add(PolyVec a)
+        {
+            uint i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].Add(a.Vec[i]);
+            }
+        }
+
+        public void Reduce()
+        {
+            int i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].PolyReduce();
+            }
+        }
+
+        public void CompressPolyVec(byte[] r)
+        {
+            int i, j, k;
+            ConditionalSubQ();
+            short[] t;
+            int count = 0;
+            if (Engine.PolyVecCompressedBytes == K * 320)
+            {
+                t = new short[4];
+                for (i = 0; i < K; i++)
+                {
+                    for (j = 0; j < KyberEngine.N / 4; j++)
+                    {
+                        for (k = 0; k < 4; k++)
+                        {
+                            t[k] = (short)
+                                (
+                                    (
+                                        (((uint) Vec[i].Coeffs[4 * j + k] << 10)
+                                            + (KyberEngine.Q / 2))
+                                            / KyberEngine.Q)
+                                        & 0x3ff);
+                        }
+                        r[count + 0] = (byte)(t[0] >> 0);
+                        r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 2));
+                        r[count + 2] = (byte)((t[1] >> 6) | (t[2] << 4));
+                        r[count + 3] = (byte)((t[2] >> 4) | (t[3] << 6));
+                        r[count + 4] = (byte)((t[3] >> 2));
+                        count += 5;
+                    }
+                }
+            }
+            else if (Engine.PolyVecCompressedBytes == K * 352)
+            {
+                t = new short[8];
+                for (i = 0; i < K; i++)
+                {
+                    for (j = 0; j < KyberEngine.N / 8; j++)
+                    {
+                        for (k = 0; k < 8; k++)
+                        {
+                            t[k] = (short)
+                                (
+                                    (
+                                        (((uint) Vec[i].Coeffs[8 * j + k] << 11)
+                                            + (KyberEngine.Q / 2))
+                                            / KyberEngine.Q)
+                                        & 0x7ff);
+                        }
+                        r[count + 0] = (byte)((t[0] >> 0));
+                        r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 3));
+                        r[count + 2] = (byte)((t[1] >> 5) | (t[2] << 6));
+                        r[count + 3] = (byte)((t[2] >> 2));
+                        r[count + 4] = (byte)((t[2] >> 10) | (t[3] << 1));
+                        r[count + 5] = (byte)((t[3] >> 7) | (t[4] << 4));
+                        r[count + 6] = (byte)((t[4] >> 4) | (t[5] << 7));
+                        r[count + 7] = (byte)((t[5] >> 1));
+                        r[count + 8] = (byte)((t[5] >> 9) | (t[6] << 2));
+                        r[count + 9] = (byte)((t[6] >> 6) | (t[7] << 5));
+                        r[count + 10] = (byte)((t[7] >> 3));
+                        count += 11;
+                    }
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!");
+            }
+        }
+
+        public void DecompressPolyVec(byte[] CompressedCipherText)
+        {
+            int i, j, k, count = 0;
+
+            if (Engine.PolyVecCompressedBytes == (K * 320))
+            {
+                short[] t = new short[4];
+                for (i = 0; i < K; i++)
+                {
+                    for (j = 0; j < KyberEngine.N / 4; j++)
+                    {
+                        t[0] = (short)(((CompressedCipherText[count] & 0xFF) >> 0) | ((ushort)(CompressedCipherText[count + 1] & 0xFF) << 8));
+                        t[1] = (short)(((CompressedCipherText[count + 1] & 0xFF) >> 2) | ((ushort)(CompressedCipherText[count + 2] & 0xFF) << 6));
+                        t[2] = (short)(((CompressedCipherText[count + 2] & 0xFF) >> 4) | ((ushort)(CompressedCipherText[count + 3] & 0xFF) << 4));
+                        t[3] = (short)(((CompressedCipherText[count + 3] & 0xFF) >> 6) | ((ushort)(CompressedCipherText[count + 4] & 0xFF) << 2));
+                        count += 5;
+                        for (k = 0; k < 4; k++)
+                        {
+                            Vec[i].Coeffs[4 * j + k] = (short)(((t[k] & 0x3FF) * KyberEngine.Q + 512) >> 10);
+                        }
+                    }
+
+                }
+
+            }
+            else if (Engine.PolyVecCompressedBytes == (K * 352))
+            {
+                short[] t = new short[8];
+                for (i = 0; i < K; i++)
+                {
+                    for (j = 0; j < KyberEngine.N / 8; j++)
+                    {
+                        t[0] = (short)(((CompressedCipherText[count] & 0xFF) >> 0) | ((ushort)(CompressedCipherText[count + 1] & 0xFF) << 8));
+                        t[1] = (short)(((CompressedCipherText[count + 1] & 0xFF) >> 3) | ((ushort)(CompressedCipherText[count + 2] & 0xFF) << 5));
+                        t[2] = (short)(((CompressedCipherText[count + 2] & 0xFF) >> 6) | ((ushort)(CompressedCipherText[count + 3] & 0xFF) << 2) | ((ushort)((CompressedCipherText[count + 4] & 0xFF) << 10)));
+                        t[3] = (short)(((CompressedCipherText[count + 4] & 0xFF) >> 1) | ((ushort)(CompressedCipherText[count + 5] & 0xFF) << 7));
+                        t[4] = (short)(((CompressedCipherText[count + 5] & 0xFF) >> 4) | ((ushort)(CompressedCipherText[count + 6] & 0xFF) << 4));
+                        t[5] = (short)(((CompressedCipherText[count + 6] & 0xFF) >> 7) | ((ushort)(CompressedCipherText[count + 7] & 0xFF) << 1) | ((ushort)((CompressedCipherText[count + 8] & 0xFF) << 9)));
+                        t[6] = (short)(((CompressedCipherText[count + 8] & 0xFF) >> 2) | ((ushort)(CompressedCipherText[count + 9] & 0xFF) << 6));
+                        t[7] = (short)(((CompressedCipherText[count + 9] & 0xFF) >> 5) | ((ushort)(CompressedCipherText[count + 10] & 0xFF) << 3));
+                        count += 11;
+                        for (k = 0; k < 8; k++)
+                        {
+                            Vec[i].Coeffs[8 * j + k] = (short)(((t[k] & 0x7FF) * KyberEngine.Q + 1024) >> 11);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                throw new ArgumentException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!");
+            }
+        }
+
+        public void ToBytes(byte[] r)
+        {
+            int i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].ToBytes(r, i * KyberEngine.PolyBytes);
+            }
+        }
+
+        public void FromBytes(byte[] pk)
+        {
+            int i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].FromBytes(pk, i * KyberEngine.PolyBytes);
+            }
+        }
+
+        private void ConditionalSubQ()
+        {
+            int i;
+            for (i = 0; i < K; i++)
+            {
+                Vec[i].CondSubQ();
+            }
+        }
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Reduce.cs b/crypto/src/pqc/crypto/crystals/kyber/Reduce.cs
new file mode 100644
index 000000000..b222cdc17
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Reduce.cs
@@ -0,0 +1,35 @@
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class Reduce
+    {
+        public static short MontgomeryReduce(int a)
+        {
+            int t;
+            short u;
+
+            u = (short)(a * KyberEngine.QInv);
+            t = (int)(u * KyberEngine.Q);
+            t = a - t;
+            t >>= 16;
+            return (short)t;
+        }
+
+        public static short BarrettReduce(short a)
+        {
+            short t;
+            short v = (short)(((1U << 26) + (KyberEngine.Q / 2)) / KyberEngine.Q);
+            t = (short)((v * a) >> 26);
+            t = (short)(t * KyberEngine.Q);
+            return (short)(a - t);
+        }
+        
+        public static short CondSubQ(short a)
+        {
+            a -= KyberEngine.Q;
+            a += (short) ((a >> 15) & KyberEngine.Q);
+            return a;
+        }
+
+    }
+}
diff --git a/crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs b/crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs
new file mode 100644
index 000000000..711209e2b
--- /dev/null
+++ b/crypto/src/pqc/crypto/crystals/kyber/Symmetric.cs
@@ -0,0 +1,32 @@
+using Org.BouncyCastle.Crypto.Digests;
+using System;
+
+namespace Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber
+{
+    internal static class Symmetric
+    {
+        public static readonly int Shake128Rate = 168;
+        public static void PRF(byte[] outbuf, int outlen, byte[] key, byte nonce)
+        {
+            uint i;
+            byte[] ExtraKey = new byte[KyberEngine.SymBytes + 1];
+            Array.Copy(key, ExtraKey, KyberEngine.SymBytes);
+            ExtraKey[KyberEngine.SymBytes] = nonce;
+            ShakeDigest Shake256 = new ShakeDigest(256);
+            Shake256.BlockUpdate(ExtraKey, 0, KyberEngine.SymBytes + 1);
+            Shake256.DoFinal(outbuf, 0, outlen);
+        }
+
+        public static ShakeDigest XOF(byte[] seed, byte a, byte b)
+        {
+            ShakeDigest OutDigest = new ShakeDigest(128);
+            byte[] buf = new byte[seed.Length + 2];
+            Array.Copy(seed, buf, seed.Length);
+            buf[seed.Length] = a;
+            buf[seed.Length + 1] = b;
+            OutDigest.BlockUpdate(buf, 0, buf.Length);
+            return OutDigest;
+        }
+
+    }
+}