summary refs log tree commit diff
path: root/crypto/src
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-09-30 17:38:59 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-09-30 17:38:59 +0700
commit95a7590043c9bd2ceda95a06df09c2422ade0bed (patch)
treed628f6826ec075be4ea4f75093cd0250b84a7732 /crypto/src
parentFix parameter name (diff)
downloadBouncyCastle.NET-ed25519-95a7590043c9bd2ceda95a06df09c2422ade0bed.tar.xz
Port Blake2xsDigest from bc-java
- refactor Blake2bDigest, Blake2sDigest
Diffstat (limited to 'crypto/src')
-rw-r--r--crypto/src/crypto/digests/Blake2bDigest.cs105
-rw-r--r--crypto/src/crypto/digests/Blake2sDigest.cs258
-rw-r--r--crypto/src/crypto/digests/Blake2xsDigest.cs368
3 files changed, 566 insertions, 165 deletions
diff --git a/crypto/src/crypto/digests/Blake2bDigest.cs b/crypto/src/crypto/digests/Blake2bDigest.cs
index ec80a3355..953ac0062 100644
--- a/crypto/src/crypto/digests/Blake2bDigest.cs
+++ b/crypto/src/crypto/digests/Blake2bDigest.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
 
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
@@ -39,7 +42,7 @@ namespace Org.BouncyCastle.Crypto.Digests
      * BLAKE2b is optimized for 64-bit platforms and produces digests of any size
      * between 1 and 64 bytes.
      */
-    public class Blake2bDigest
+    public sealed class Blake2bDigest
         : IDigest
     {
         // Blake2b Initialization Vector:
@@ -276,12 +279,10 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @param b the input byte to be entered.
          */
-        public virtual void Update(byte b)
+        public void Update(byte b)
         {
-            int remainingLength = 0; // left bytes of buffer
-
             // process the buffer if full else add to buffer:
-            remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+            int remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
             if (remainingLength == 0)
             { // full buffer
                 t0 += BLOCK_LENGTH_BYTES;
@@ -289,7 +290,11 @@ namespace Org.BouncyCastle.Crypto.Digests
                 { // if message > 2^64
                     t1++;
                 }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Compress(buffer);
+#else
                 Compress(buffer, 0);
+#endif
                 Array.Clear(buffer, 0, buffer.Length);// clear buffer
                 buffer[0] = b;
                 bufferPos = 1;
@@ -298,7 +303,6 @@ namespace Org.BouncyCastle.Crypto.Digests
             {
                 buffer[bufferPos] = b;
                 bufferPos++;
-                return;
             }
         }
 
@@ -309,11 +313,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param offset  the offset into the byte array where the data starts.
          * @param len     the length of the data.
          */
-        public virtual void BlockUpdate(byte[] message, int offset, int len)
+        public void BlockUpdate(byte[] message, int offset, int len)
         {
             if (message == null || len == 0)
                 return;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(message.AsSpan(offset, len));
+#else
             int remainingLength = 0; // left bytes of buffer
 
             if (bufferPos != 0)
@@ -323,8 +330,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                 remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
                 if (remainingLength < len)
                 { // full buffer + at least 1 byte
-                    Array.Copy(message, offset, buffer, bufferPos,
-                        remainingLength);
+                    Array.Copy(message, offset, buffer, bufferPos, remainingLength);
                     t0 += BLOCK_LENGTH_BYTES;
                     if (t0 == 0)
                     { // if message > 2^64
@@ -359,10 +365,11 @@ namespace Org.BouncyCastle.Crypto.Digests
             // fill the buffer with left bytes, this might be a full block
             Array.Copy(message, messagePos, buffer, 0, offset + len - messagePos);
             bufferPos += offset + len - messagePos;
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        public void BlockUpdate(ReadOnlySpan<byte> input)
         {
             if (input.IsEmpty)
                 return;
@@ -382,7 +389,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                     { // if message > 2^64
                         t1++;
                     }
-                    Compress(buffer, 0);
+                    Compress(buffer);
                     bufferPos = 0;
                     Array.Clear(buffer, 0, buffer.Length);// clear buffer
                 }
@@ -422,8 +429,11 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param out       the array the digest is to be copied into.
          * @param outOffset the offset into the out array the digest is to start at.
          */
-        public virtual int DoFinal(byte[] output, int outOffset)
+        public int DoFinal(byte[] output, int outOffset)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOffset));
+#else
             f0 = 0xFFFFFFFFFFFFFFFFUL;
             t0 += (ulong)bufferPos;
             if (bufferPos > 0 && t0 == 0)
@@ -448,10 +458,11 @@ namespace Org.BouncyCastle.Crypto.Digests
             Reset();
 
             return digestLength;
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        public virtual int DoFinal(Span<byte> output)
+        public int DoFinal(Span<byte> output)
         {
             f0 = 0xFFFFFFFFFFFFFFFFUL;
             t0 += (ulong)bufferPos;
@@ -459,7 +470,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             {
                 t1++;
             }
-            Compress(buffer, 0);
+            Compress(buffer);
             Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
             Array.Clear(internalState, 0, internalState.Length);
 
@@ -485,7 +496,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * The key, the salt and the personal string will
          * remain for further computations.
          */
-        public virtual void Reset()
+        public void Reset()
         {
             bufferPos = 0;
             f0 = 0L;
@@ -501,25 +512,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             Init();
         }
 
-        private void Compress(byte[] message, int messagePos)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> message)
         {
             InitializeInternalState();
 
-            ulong[] m = new ulong[16];
-            Pack.LE_To_UInt64(message, messagePos, m);
+            Span<ulong> m = stackalloc ulong[16];
+            Pack.LE_To_UInt64(message, m);
 
             for (int round = 0; round < ROUNDS; round++)
             {
                 // G apply to columns of internalState:m[blake2b_sigma[round][2 * blockPos]] /+1
-                G(m[blake2b_sigma[round,0]], m[blake2b_sigma[round,1]], 0, 4, 8, 12);
-                G(m[blake2b_sigma[round,2]], m[blake2b_sigma[round,3]], 1, 5, 9, 13);
-                G(m[blake2b_sigma[round,4]], m[blake2b_sigma[round,5]], 2, 6, 10, 14);
-                G(m[blake2b_sigma[round,6]], m[blake2b_sigma[round,7]], 3, 7, 11, 15);
+                G(m[blake2b_sigma[round, 0]], m[blake2b_sigma[round, 1]], 0, 4, 8, 12);
+                G(m[blake2b_sigma[round, 2]], m[blake2b_sigma[round, 3]], 1, 5, 9, 13);
+                G(m[blake2b_sigma[round, 4]], m[blake2b_sigma[round, 5]], 2, 6, 10, 14);
+                G(m[blake2b_sigma[round, 6]], m[blake2b_sigma[round, 7]], 3, 7, 11, 15);
                 // G apply to diagonals of internalState:
-                G(m[blake2b_sigma[round,8]], m[blake2b_sigma[round,9]], 0, 5, 10, 15);
-                G(m[blake2b_sigma[round,10]], m[blake2b_sigma[round,11]], 1, 6, 11, 12);
-                G(m[blake2b_sigma[round,12]], m[blake2b_sigma[round,13]], 2, 7, 8, 13);
-                G(m[blake2b_sigma[round,14]], m[blake2b_sigma[round,15]], 3, 4, 9, 14);
+                G(m[blake2b_sigma[round, 8]], m[blake2b_sigma[round, 9]], 0, 5, 10, 15);
+                G(m[blake2b_sigma[round, 10]], m[blake2b_sigma[round, 11]], 1, 6, 11, 12);
+                G(m[blake2b_sigma[round, 12]], m[blake2b_sigma[round, 13]], 2, 7, 8, 13);
+                G(m[blake2b_sigma[round, 14]], m[blake2b_sigma[round, 15]], 3, 4, 9, 14);
             }
 
             // update chain values:
@@ -528,27 +540,26 @@ namespace Org.BouncyCastle.Crypto.Digests
                 chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
             }
         }
-
-#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        private void Compress(ReadOnlySpan<byte> message)
+#else
+        private void Compress(byte[] message, int messagePos)
         {
             InitializeInternalState();
 
-            Span<ulong> m = stackalloc ulong[16];
-            Pack.LE_To_UInt64(message, m);
+            ulong[] m = new ulong[16];
+            Pack.LE_To_UInt64(message, messagePos, m);
 
             for (int round = 0; round < ROUNDS; round++)
             {
                 // G apply to columns of internalState:m[blake2b_sigma[round][2 * blockPos]] /+1
-                G(m[blake2b_sigma[round, 0]], m[blake2b_sigma[round, 1]], 0, 4, 8, 12);
-                G(m[blake2b_sigma[round, 2]], m[blake2b_sigma[round, 3]], 1, 5, 9, 13);
-                G(m[blake2b_sigma[round, 4]], m[blake2b_sigma[round, 5]], 2, 6, 10, 14);
-                G(m[blake2b_sigma[round, 6]], m[blake2b_sigma[round, 7]], 3, 7, 11, 15);
+                G(m[blake2b_sigma[round,0]], m[blake2b_sigma[round,1]], 0, 4, 8, 12);
+                G(m[blake2b_sigma[round,2]], m[blake2b_sigma[round,3]], 1, 5, 9, 13);
+                G(m[blake2b_sigma[round,4]], m[blake2b_sigma[round,5]], 2, 6, 10, 14);
+                G(m[blake2b_sigma[round,6]], m[blake2b_sigma[round,7]], 3, 7, 11, 15);
                 // G apply to diagonals of internalState:
-                G(m[blake2b_sigma[round, 8]], m[blake2b_sigma[round, 9]], 0, 5, 10, 15);
-                G(m[blake2b_sigma[round, 10]], m[blake2b_sigma[round, 11]], 1, 6, 11, 12);
-                G(m[blake2b_sigma[round, 12]], m[blake2b_sigma[round, 13]], 2, 7, 8, 13);
-                G(m[blake2b_sigma[round, 14]], m[blake2b_sigma[round, 15]], 3, 4, 9, 14);
+                G(m[blake2b_sigma[round,8]], m[blake2b_sigma[round,9]], 0, 5, 10, 15);
+                G(m[blake2b_sigma[round,10]], m[blake2b_sigma[round,11]], 1, 6, 11, 12);
+                G(m[blake2b_sigma[round,12]], m[blake2b_sigma[round,13]], 2, 7, 8, 13);
+                G(m[blake2b_sigma[round,14]], m[blake2b_sigma[round,15]], 3, 4, 9, 14);
             }
 
             // update chain values:
@@ -559,6 +570,9 @@ namespace Org.BouncyCastle.Crypto.Digests
         }
 #endif
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         private void G(ulong m1, ulong m2, int posA, int posB, int posC, int posD)
         {
             internalState[posA] = internalState[posA] + internalState[posB] + m1;
@@ -581,17 +595,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return the algorithm name
          */
-        public virtual string AlgorithmName
-        {
-            get { return "BLAKE2b"; }
-        }
+        public string AlgorithmName => "BLAKE2b";
 
         /**
          * return the size, in bytes, of the digest produced by this message digest.
          *
          * @return the size, in bytes, of the digest produced by this message digest.
          */
-        public virtual int GetDigestSize()
+        public int GetDigestSize()
         {
             return digestLength;
         }
@@ -602,7 +613,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return byte length of the digests internal buffer.
          */
-        public virtual int GetByteLength()
+        public int GetByteLength()
         {
             return BLOCK_LENGTH_BYTES;
         }
@@ -611,7 +622,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Overwrite the key
          * if it is no longer used (zeroization)
          */
-        public virtual void ClearKey()
+        public void ClearKey()
         {
             if (key != null)
             {
@@ -624,7 +635,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Overwrite the salt (pepper) if it
          * is secret and no longer used (zeroization)
          */
-        public virtual void ClearSalt()
+        public void ClearSalt()
         {
             if (salt != null)
             {
diff --git a/crypto/src/crypto/digests/Blake2sDigest.cs b/crypto/src/crypto/digests/Blake2sDigest.cs
index 187808aa0..a6ee75af5 100644
--- a/crypto/src/crypto/digests/Blake2sDigest.cs
+++ b/crypto/src/crypto/digests/Blake2sDigest.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
 
 using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
@@ -40,7 +43,7 @@ namespace Org.BouncyCastle.Crypto.Digests
      * BLAKE2s is optimized for 32-bit platforms and produces digests of any size
      * between 1 and 32 bytes.
      */
-    public class Blake2sDigest
+    public sealed class Blake2sDigest
         : IDigest
     {
         /**
@@ -83,16 +86,13 @@ namespace Org.BouncyCastle.Crypto.Digests
         private byte[] key = null;
 
         // Tree hashing parameters:
-        // Because this class does not implement the Tree Hashing Mode,
-        // these parameters can be treated as constants (see Init() function)
-	    /*
-	     * private int fanout = 1; // 0-255
-	     * private int depth = 1; // 1 - 255
-	     * private int leafLength= 0;
-	     * private long nodeOffset = 0L;
-	     * private int nodeDepth = 0;
-	     * private int innerHashLength = 0;
-	     */
+        // The Tree Hashing Mode is not supported but these are used for the XOF implementation
+        private int fanout = 1; // 0-255
+        private int depth = 1; // 0-255
+        private int leafLength = 0;
+        private long nodeOffset = 0L;
+        private int nodeDepth = 0;
+        private int innerHashLength = 0;
 
         /**
          * Whenever this buffer overflows, it will be processed in the Compress()
@@ -145,8 +145,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             this.keyLength = digest.keyLength;
             this.key = Arrays.Clone(digest.key);
             this.digestLength = digest.digestLength;
+            this.internalState = Arrays.Clone(digest.internalState);
             this.chainValue = Arrays.Clone(digest.chainValue);
+            this.t0 = digest.t0;
+            this.t1 = digest.t1;
+            this.f0 = digest.f0;
+            this.salt = Arrays.Clone(digest.salt);
             this.personalization = Arrays.Clone(digest.personalization);
+            this.fanout = digest.fanout;
+            this.depth = digest.depth;
+            this.leafLength = digest.leafLength;
+            this.nodeOffset = digest.nodeOffset;
+            this.nodeDepth = digest.nodeDepth;
+            this.innerHashLength = digest.innerHashLength;
         }
 
         /**
@@ -159,10 +170,9 @@ namespace Org.BouncyCastle.Crypto.Digests
             if (digestBits < 8 || digestBits > 256 || digestBits % 8 != 0)
                 throw new ArgumentException("BLAKE2s digest bit length must be a multiple of 8 and not greater than 256");
 
-            buffer = new byte[BLOCK_LENGTH_BYTES];
-            keyLength = 0;
             digestLength = digestBits / 8;
-            Init();
+
+            Init(null, null, null);
         }
 
         /**
@@ -176,21 +186,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          */
         public Blake2sDigest(byte[] key)
         {
-            buffer = new byte[BLOCK_LENGTH_BYTES];
-            if (key != null)
-            {
-                if (key.Length > 32)
-                    throw new ArgumentException("Keys > 32 are not supported");
-
-                this.key = new byte[key.Length];
-                Array.Copy(key, 0, this.key, 0, key.Length);
-
-                keyLength = key.Length;
-                Array.Copy(key, 0, buffer, 0, key.Length);
-                bufferPos = BLOCK_LENGTH_BYTES; // zero padding
-            }
-            digestLength = 32;
-            Init();
+            Init(null, null, key);
         }
 
         /**
@@ -206,65 +202,79 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param salt            8 bytes or null
          * @param personalization 8 bytes or null
          */
-        public Blake2sDigest(byte[] key, int digestBytes, byte[] salt,
-                             byte[] personalization)
+        public Blake2sDigest(byte[] key, int digestBytes, byte[] salt, byte[] personalization)
         {
             if (digestBytes < 1 || digestBytes > 32)
                 throw new ArgumentException("Invalid digest length (required: 1 - 32)");
 
             this.digestLength = digestBytes;
-            this.buffer = new byte[BLOCK_LENGTH_BYTES];
 
-            if (salt != null)
-            {
-                if (salt.Length != 8)
-                    throw new ArgumentException("Salt length must be exactly 8 bytes");
+            Init(salt, personalization, key);
+        }
 
-                this.salt = new byte[8];
-                Array.Copy(salt, 0, this.salt, 0, salt.Length);
-            }
-            if (personalization != null)
-            {
-                if (personalization.Length != 8)
-                    throw new ArgumentException("Personalization length must be exactly 8 bytes");
+        // XOF root hash parameters
+        internal Blake2sDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization, long offset)
+        {
+            digestLength = digestBytes;
+            nodeOffset = offset;
 
-                this.personalization = new byte[8];
-                Array.Copy(personalization, 0, this.personalization, 0, personalization.Length);
-            }
-            if (key != null)
-            {
-                if (key.Length > 32)
-                    throw new ArgumentException("Keys > 32 bytes are not supported");
+            Init(salt, personalization, key);
+        }
 
-                this.key = new byte[key.Length];
-                Array.Copy(key, 0, this.key, 0, key.Length);
+        // XOF internal hash parameters
+        internal Blake2sDigest(int digestBytes, int hashLength, long offset)
+        {
+            digestLength = digestBytes;
+            nodeOffset = offset;
+            fanout = 0;
+            depth = 0;
+            leafLength = hashLength;
+            innerHashLength = hashLength;
+            nodeDepth = 0;
+
+            Init(null, null, null);
+        }
 
+        // initialize the digest's parameters
+        private void Init(byte[] salt, byte[] personalization, byte[] key)
+        {
+            buffer = new byte[BLOCK_LENGTH_BYTES];
+
+            if (key != null && key.Length > 0)
+            {
                 keyLength = key.Length;
-                Array.Copy(key, 0, buffer, 0, key.Length);
+                if (keyLength > 32)
+                    throw new ArgumentException("Keys > 32 bytes are not supported");
+
+                this.key = new byte[keyLength];
+                Array.Copy(key, 0, this.key, 0, keyLength);
+                Array.Copy(key, 0, buffer, 0, keyLength);
                 bufferPos = BLOCK_LENGTH_BYTES; // zero padding
             }
-            Init();
-        }
 
-        // initialize chainValue
-        private void Init()
-        {
             if (chainValue == null)
             {
                 chainValue = new uint[8];
 
-                chainValue[0] = blake2s_IV[0] ^ (uint)(digestLength | (keyLength << 8) | 0x1010000);
-                // 0x1010000 = ((fanout << 16) | (depth << 24));
-                // with fanout = 1; depth = 0;
-                chainValue[1] = blake2s_IV[1];// ^ leafLength; with leafLength = 0;
-                chainValue[2] = blake2s_IV[2];// ^ nodeOffset; with nodeOffset = 0;
-                chainValue[3] = blake2s_IV[3];// ^ ( (nodeOffset << 32) | (nodeDepth << 16) | (innerHashLength << 24) );
-                // with nodeDepth = 0; innerHashLength = 0;
+                chainValue[0] = blake2s_IV[0]
+                    ^ (uint)(digestLength | (keyLength << 8) | ((fanout << 16) | (depth << 24)));
+                chainValue[1] = blake2s_IV[1] ^ (uint)leafLength;
+
+                int nofHi = (int)(nodeOffset >> 32);
+                int nofLo = (int)nodeOffset;
+                chainValue[2] = blake2s_IV[2] ^ (uint)nofLo;
+                chainValue[3] = blake2s_IV[3] ^ (uint)(nofHi | (nodeDepth << 16) | (innerHashLength << 24));
 
                 chainValue[4] = blake2s_IV[4];
                 chainValue[5] = blake2s_IV[5];
                 if (salt != null)
                 {
+                    if (salt.Length != 8)
+                        throw new ArgumentException("Salt length must be exactly 8 bytes");
+
+                    this.salt = new byte[8];
+                    Array.Copy(salt, 0, this.salt, 0, salt.Length);
+
                     chainValue[4] ^= Pack.LE_To_UInt32(salt, 0);
                     chainValue[5] ^= Pack.LE_To_UInt32(salt, 4);
                 }
@@ -273,6 +283,12 @@ namespace Org.BouncyCastle.Crypto.Digests
                 chainValue[7] = blake2s_IV[7];
                 if (personalization != null)
                 {
+                    if (personalization.Length != 8)
+                        throw new ArgumentException("Personalization length must be exactly 8 bytes");
+
+                    this.personalization = new byte[8];
+                    Array.Copy(personalization, 0, this.personalization, 0, personalization.Length);
+
                     chainValue[6] ^= Pack.LE_To_UInt32(personalization, 0);
                     chainValue[7] ^= Pack.LE_To_UInt32(personalization, 4);
                 }
@@ -295,12 +311,10 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @param b the input byte to be entered.
          */
-        public virtual void Update(byte b)
+        public void Update(byte b)
         {
-            int remainingLength; // left bytes of buffer
-
             // process the buffer if full else add to buffer:
-            remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+            int remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
             if (remainingLength == 0)
             { // full buffer
                 t0 += BLOCK_LENGTH_BYTES;
@@ -308,7 +322,11 @@ namespace Org.BouncyCastle.Crypto.Digests
                 { // if message > 2^32
                     t1++;
                 }
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Compress(buffer);
+#else
                 Compress(buffer, 0);
+#endif
                 Array.Clear(buffer, 0, buffer.Length);// clear buffer
                 buffer[0] = b;
                 bufferPos = 1;
@@ -327,11 +345,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param offset  the offset into the byte array where the data starts.
          * @param len     the length of the data.
          */
-        public virtual void BlockUpdate(byte[] message, int offset, int len)
+        public void BlockUpdate(byte[] message, int offset, int len)
         {
             if (message == null || len == 0)
                 return;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            BlockUpdate(message.AsSpan(offset, len));
+#else
             int remainingLength = 0; // left bytes of buffer
 
             if (bufferPos != 0)
@@ -379,10 +400,11 @@ namespace Org.BouncyCastle.Crypto.Digests
             Array.Copy(message, messagePos, buffer, 0, offset + len
                 - messagePos);
             bufferPos += offset + len - messagePos;
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        public void BlockUpdate(ReadOnlySpan<byte> input)
         {
             if (input.IsEmpty)
                 return;
@@ -402,7 +424,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                     { // if message > 2^32
                         t1++;
                     }
-                    Compress(buffer, 0);
+                    Compress(buffer);
                     bufferPos = 0;
                     Array.Clear(buffer, 0, buffer.Length);// clear buffer
                 }
@@ -443,8 +465,11 @@ namespace Org.BouncyCastle.Crypto.Digests
          * @param out       the array the digest is to be copied into.
          * @param outOffset the offset into the out array the digest is to start at.
          */
-        public virtual int DoFinal(byte[] output, int outOffset)
+        public int DoFinal(byte[] output, int outOffset)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOffset));
+#else
             f0 = 0xFFFFFFFFU;
             t0 += (uint)bufferPos;
             // bufferPos may be < 64, so (t0 == 0) does not work
@@ -471,10 +496,11 @@ namespace Org.BouncyCastle.Crypto.Digests
             Reset();
 
             return digestLength;
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        public virtual int DoFinal(Span<byte> output)
+        public int DoFinal(Span<byte> output)
         {
             f0 = 0xFFFFFFFFU;
             t0 += (uint)bufferPos;
@@ -484,7 +510,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             {
                 t1++;
             }
-            Compress(buffer, 0);
+            Compress(buffer);
             Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
             Array.Clear(internalState, 0, internalState.Length);
 
@@ -509,7 +535,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Reset the digest back to its initial state. The key, the salt and the
          * personal string will remain for further computations.
          */
-        public virtual void Reset()
+        public void Reset()
         {
             bufferPos = 0;
             f0 = 0;
@@ -522,28 +548,30 @@ namespace Org.BouncyCastle.Crypto.Digests
                 Array.Copy(key, 0, buffer, 0, key.Length);
                 bufferPos = BLOCK_LENGTH_BYTES; // zero padding
             }
-            Init();
+
+            Init(this.salt, this.personalization, this.key);
         }
 
-        private void Compress(byte[] message, int messagePos)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> message)
         {
             InitializeInternalState();
 
-            uint[] m = new uint[16];
-            Pack.LE_To_UInt32(message, messagePos, m);
+            Span<uint> m = stackalloc uint[16];
+            Pack.LE_To_UInt32(message, m);
 
             for (int round = 0; round < ROUNDS; round++)
             {
                 // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1
-                G(m[blake2s_sigma[round,0]], m[blake2s_sigma[round,1]], 0, 4, 8, 12);
-                G(m[blake2s_sigma[round,2]], m[blake2s_sigma[round,3]], 1, 5, 9, 13);
-                G(m[blake2s_sigma[round,4]], m[blake2s_sigma[round,5]], 2, 6, 10, 14);
-                G(m[blake2s_sigma[round,6]], m[blake2s_sigma[round,7]], 3, 7, 11, 15);
+                G(m[blake2s_sigma[round, 0]], m[blake2s_sigma[round, 1]], 0, 4, 8, 12);
+                G(m[blake2s_sigma[round, 2]], m[blake2s_sigma[round, 3]], 1, 5, 9, 13);
+                G(m[blake2s_sigma[round, 4]], m[blake2s_sigma[round, 5]], 2, 6, 10, 14);
+                G(m[blake2s_sigma[round, 6]], m[blake2s_sigma[round, 7]], 3, 7, 11, 15);
                 // G apply to diagonals of internalState:
-                G(m[blake2s_sigma[round,8]], m[blake2s_sigma[round,9]], 0, 5, 10, 15);
-                G(m[blake2s_sigma[round,10]], m[blake2s_sigma[round,11]], 1, 6, 11, 12);
-                G(m[blake2s_sigma[round,12]], m[blake2s_sigma[round,13]], 2, 7, 8, 13);
-                G(m[blake2s_sigma[round,14]], m[blake2s_sigma[round,15]], 3, 4, 9, 14);
+                G(m[blake2s_sigma[round, 8]], m[blake2s_sigma[round, 9]], 0, 5, 10, 15);
+                G(m[blake2s_sigma[round, 10]], m[blake2s_sigma[round, 11]], 1, 6, 11, 12);
+                G(m[blake2s_sigma[round, 12]], m[blake2s_sigma[round, 13]], 2, 7, 8, 13);
+                G(m[blake2s_sigma[round, 14]], m[blake2s_sigma[round, 15]], 3, 4, 9, 14);
             }
 
             // update chain values:
@@ -552,27 +580,26 @@ namespace Org.BouncyCastle.Crypto.Digests
                 chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
             }
         }
-
-#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        private void Compress(ReadOnlySpan<byte> message)
+#else
+        private void Compress(byte[] message, int messagePos)
         {
             InitializeInternalState();
 
-            Span<uint> m = stackalloc uint[16];
-            Pack.LE_To_UInt32(message, m);
+            uint[] m = new uint[16];
+            Pack.LE_To_UInt32(message, messagePos, m);
 
             for (int round = 0; round < ROUNDS; round++)
             {
                 // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1
-                G(m[blake2s_sigma[round, 0]], m[blake2s_sigma[round, 1]], 0, 4, 8, 12);
-                G(m[blake2s_sigma[round, 2]], m[blake2s_sigma[round, 3]], 1, 5, 9, 13);
-                G(m[blake2s_sigma[round, 4]], m[blake2s_sigma[round, 5]], 2, 6, 10, 14);
-                G(m[blake2s_sigma[round, 6]], m[blake2s_sigma[round, 7]], 3, 7, 11, 15);
+                G(m[blake2s_sigma[round,0]], m[blake2s_sigma[round,1]], 0, 4, 8, 12);
+                G(m[blake2s_sigma[round,2]], m[blake2s_sigma[round,3]], 1, 5, 9, 13);
+                G(m[blake2s_sigma[round,4]], m[blake2s_sigma[round,5]], 2, 6, 10, 14);
+                G(m[blake2s_sigma[round,6]], m[blake2s_sigma[round,7]], 3, 7, 11, 15);
                 // G apply to diagonals of internalState:
-                G(m[blake2s_sigma[round, 8]], m[blake2s_sigma[round, 9]], 0, 5, 10, 15);
-                G(m[blake2s_sigma[round, 10]], m[blake2s_sigma[round, 11]], 1, 6, 11, 12);
-                G(m[blake2s_sigma[round, 12]], m[blake2s_sigma[round, 13]], 2, 7, 8, 13);
-                G(m[blake2s_sigma[round, 14]], m[blake2s_sigma[round, 15]], 3, 4, 9, 14);
+                G(m[blake2s_sigma[round,8]], m[blake2s_sigma[round,9]], 0, 5, 10, 15);
+                G(m[blake2s_sigma[round,10]], m[blake2s_sigma[round,11]], 1, 6, 11, 12);
+                G(m[blake2s_sigma[round,12]], m[blake2s_sigma[round,13]], 2, 7, 8, 13);
+                G(m[blake2s_sigma[round,14]], m[blake2s_sigma[round,15]], 3, 4, 9, 14);
             }
 
             // update chain values:
@@ -583,21 +610,19 @@ namespace Org.BouncyCastle.Crypto.Digests
         }
 #endif
 
+#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
         private void G(uint m1, uint m2, int posA, int posB, int posC, int posD)
         {
             internalState[posA] = internalState[posA] + internalState[posB] + m1;
-            internalState[posD] = rotr32(internalState[posD] ^ internalState[posA], 16);
+            internalState[posD] = Integers.RotateRight(internalState[posD] ^ internalState[posA], 16);
             internalState[posC] = internalState[posC] + internalState[posD];
-            internalState[posB] = rotr32(internalState[posB] ^ internalState[posC], 12);
+            internalState[posB] = Integers.RotateRight(internalState[posB] ^ internalState[posC], 12);
             internalState[posA] = internalState[posA] + internalState[posB] + m2;
-            internalState[posD] = rotr32(internalState[posD] ^ internalState[posA], 8);
+            internalState[posD] = Integers.RotateRight(internalState[posD] ^ internalState[posA], 8);
             internalState[posC] = internalState[posC] + internalState[posD];
-            internalState[posB] = rotr32(internalState[posB] ^ internalState[posC], 7);
-        }
-
-        private uint rotr32(uint x, int rot)
-        {
-            return x >> rot | x << -rot;
+            internalState[posB] = Integers.RotateRight(internalState[posB] ^ internalState[posC], 7);
         }
 
         /**
@@ -605,17 +630,14 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return the algorithm name
          */
-        public virtual string AlgorithmName
-        {
-            get { return "BLAKE2s"; }
-        }
+        public string AlgorithmName => "BLAKE2s";
 
         /**
          * Return the size in bytes of the digest produced by this message digest.
          *
          * @return the size in bytes of the digest produced by this message digest.
          */
-        public virtual int GetDigestSize()
+        public int GetDigestSize()
         {
             return digestLength;
         }
@@ -626,7 +648,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          *
          * @return byte length of the digest's internal buffer.
          */
-        public virtual int GetByteLength()
+        public int GetByteLength()
         {
             return BLOCK_LENGTH_BYTES;
         }
@@ -634,7 +656,7 @@ namespace Org.BouncyCastle.Crypto.Digests
         /**
          * Overwrite the key if it is no longer used (zeroization).
          */
-        public virtual void ClearKey()
+        public void ClearKey()
         {
             if (key != null)
             {
@@ -647,7 +669,7 @@ namespace Org.BouncyCastle.Crypto.Digests
          * Overwrite the salt (pepper) if it is secret and no longer used
          * (zeroization).
          */
-        public virtual void ClearSalt()
+        public void ClearSalt()
         {
             if (salt != null)
             {
diff --git a/crypto/src/crypto/digests/Blake2xsDigest.cs b/crypto/src/crypto/digests/Blake2xsDigest.cs
new file mode 100644
index 000000000..43bfbac18
--- /dev/null
+++ b/crypto/src/crypto/digests/Blake2xsDigest.cs
@@ -0,0 +1,368 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+    /*
+      The BLAKE2 cryptographic hash function was designed by Jean-
+      Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn, and Christian
+      Winnerlein.
+
+      Reference Implementation and Description can be found at: https://blake2.net/blake2x.pdf
+     */
+
+    /**
+     * Implementation of the eXtendable Output Function (XOF) BLAKE2xs.
+     * <p/>
+     * BLAKE2xs offers a built-in keying mechanism to be used directly
+     * for authentication ("Prefix-MAC") rather than a HMAC construction.
+     * <p/>
+     * BLAKE2xs offers a built-in support for a salt for randomized hashing
+     * and a personal string for defining a unique hash function for each application.
+     * <p/>
+     * BLAKE2xs is optimized for 32-bit platforms and produces digests of any size
+     * between 1 and 2^16-2 bytes. The length can also be unknown and then the maximum
+     * length will be 2^32 blocks of 32 bytes.
+     */
+    public sealed class Blake2xsDigest
+        : IXof
+    {
+        /**
+         * Magic number to indicate an unknown length of digest
+         */
+        public const int UnknownDigestLength = 65535;
+
+        private const int DigestLength = 32;
+        private const long MaxNumberBlocks = 1L << 32;
+
+        /**
+         * Expected digest length for the xof. It can be unknown.
+         */
+        private int digestLength;
+
+        /**
+         * Root hash that will take the updates
+         */
+        private Blake2sDigest hash;
+
+        /**
+         * Digest of the root hash
+         */
+        private byte[] h0 = null;
+
+        /**
+         * Digest of each round of the XOF
+         */
+        private byte[] buf = new byte[32];
+
+        /**
+         * Current position for a round
+         */
+        private int bufPos = 32;
+
+        /**
+         * Overall position of the digest. It is useful when the length is known
+         * in advance to get last block length.
+         */
+        private int digestPos = 0;
+
+        /**
+         * Keep track of the round number to detect the end of the digest after
+         * 2^32 blocks of 32 bytes.
+         */
+        private long blockPos = 0;
+
+        /**
+         * Current node offset incremented by 1 every round.
+         */
+        private long nodeOffset;
+
+        /**
+         * BLAKE2xs for hashing with unknown digest length
+         */
+        public Blake2xsDigest()
+            : this(UnknownDigestLength)
+        {
+        }
+
+        /**
+         * BLAKE2xs for hashing
+         *
+         * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1
+         */
+        public Blake2xsDigest(int digestBytes)
+            : this(digestBytes, null, null, null)
+        {
+        }
+
+        /**
+         * BLAKE2xs with key
+         *
+         * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1
+         * @param key         A key up to 32 bytes or null
+         */
+        public Blake2xsDigest(int digestBytes, byte[] key)
+            : this(digestBytes, key, null, null)
+        {
+        }
+
+        /**
+         * BLAKE2xs with key, salt and personalization
+         *
+         * @param digestBytes     The desired digest length in bytes. Must be above 1 and less than 2^16-1
+         * @param key             A key up to 32 bytes or null
+         * @param salt            8 bytes or null
+         * @param personalization 8 bytes or null
+         */
+        public Blake2xsDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization)
+        {
+            if (digestBytes < 1 || digestBytes > UnknownDigestLength)
+                throw new ArgumentException("BLAKE2xs digest length must be between 1 and 2^16-1");
+
+            digestLength = digestBytes;
+            nodeOffset = ComputeNodeOffset();
+            hash = new Blake2sDigest(DigestLength, key, salt, personalization, nodeOffset);
+        }
+
+        public Blake2xsDigest(Blake2xsDigest digest)
+        {
+            digestLength = digest.digestLength;
+            hash = new Blake2sDigest(digest.hash);
+            h0 = Arrays.Clone(digest.h0);
+            buf = Arrays.Clone(digest.buf);
+            bufPos = digest.bufPos;
+            digestPos = digest.digestPos;
+            blockPos = digest.blockPos;
+            nodeOffset = digest.nodeOffset;
+        }
+
+        /**
+         * Return the algorithm name.
+         *
+         * @return the algorithm name
+         */
+        public string AlgorithmName => "BLAKE2xs";
+
+        /**
+         * Return the size in bytes of the digest produced by this message digest.
+         *
+         * @return the size in bytes of the digest produced by this message digest.
+         */
+        public int GetDigestSize() => digestLength;
+
+        /**
+         * Return the size in bytes of the internal buffer the digest applies its
+         * compression function to.
+         *
+         * @return byte length of the digest's internal buffer.
+         */
+        public int GetByteLength() => hash.GetByteLength();
+
+        /**
+         * Return the maximum size in bytes the digest can produce when the length
+         * is unknown
+         *
+         * @return byte length of the largest digest with unknown length
+         */
+        public long GetUnknownMaxLength()
+        {
+            return MaxNumberBlocks * DigestLength;
+        }
+
+        /**
+         * Update the message digest with a single byte.
+         *
+         * @param in the input byte to be entered.
+         */
+        public void Update(byte b)
+        {
+            hash.Update(b);
+        }
+
+        /**
+         * Update the message digest with a block of bytes.
+         *
+         * @param in    the byte array containing the data.
+         * @param inOff the offset into the byte array where the data starts.
+         * @param len   the length of the data.
+         */
+        public void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            hash.BlockUpdate(input, inOff, inLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            hash.BlockUpdate(input);
+        }
+#endif
+
+        /**
+         * Reset the digest back to its initial state. The key, the salt and the
+         * personal string will remain for further computations.
+         */
+        public void Reset()
+        {
+            hash.Reset();
+
+            h0 = null;
+            bufPos = DigestLength;
+            digestPos = 0;
+            blockPos = 0;
+            nodeOffset = ComputeNodeOffset();
+        }
+
+        /**
+         * Close the digest, producing the final digest value. The doFinal() call
+         * leaves the digest reset. Key, salt and personal string remain.
+         *
+         * @param out       the array the digest is to be copied into.
+         * @param outOffset the offset into the out array the digest is to start at.
+         */
+        public int DoFinal(byte[] output, int outOff)
+        {
+            return DoFinal(output, outOff, digestLength);
+        }
+
+        /**
+         * Close the digest, producing the final digest value. The doFinal() call
+         * leaves the digest reset. Key, salt, personal string remain.
+         *
+         * @param out    output array to write the output bytes to.
+         * @param outOff offset to start writing the bytes at.
+         * @param outLen the number of output bytes requested.
+         */
+        public int DoFinal(byte[] output, int outOff, int outLen)
+        {
+            int ret = DoOutput(output, outOff, outLen);
+
+            Reset();
+
+            return ret;
+        }
+
+        /**
+         * Start outputting the results of the final calculation for this digest. Unlike doFinal, this method
+         * will continue producing output until the Xof is explicitly reset, or signals otherwise.
+         *
+         * @param out    output array to write the output bytes to.
+         * @param outOff offset to start writing the bytes at.
+         * @param outLen the number of output bytes requested.
+         * @return the number of bytes written
+         */
+        public int DoOutput(byte[] output, int outOff, int outLen)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Output(output.AsSpan(outOff, outLen));
+#else
+            if (h0 == null)
+            {
+                h0 = new byte[hash.GetDigestSize()];
+                hash.DoFinal(h0, 0);
+            }
+
+            if (digestLength != UnknownDigestLength)
+            {
+                if (digestPos + outLen > digestLength)
+                    throw new ArgumentException("Output length is above the digest length");
+            }
+            else if (blockPos << 5 >= GetUnknownMaxLength())
+            {
+                throw new ArgumentException("Maximum length is 2^32 blocks of 32 bytes");
+            }
+
+            for (int i = 0; i < outLen; i++)
+            {
+                if (bufPos >= DigestLength)
+                {
+                    Blake2sDigest h = new Blake2sDigest(ComputeStepLength(), DigestLength, nodeOffset);
+                    h.BlockUpdate(h0, 0, h0.Length);
+
+                    Arrays.Fill(buf, 0);
+                    h.DoFinal(buf, 0);
+                    bufPos = 0;
+                    nodeOffset++;
+                    blockPos++;
+                }
+                output[outOff + i] = buf[bufPos];
+                bufPos++;
+                digestPos++;
+            }
+
+            return outLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..digestLength]);
+        }
+
+        public int OutputFinal(Span<byte> output)
+        {
+            int ret = Output(output);
+
+            Reset();
+
+            return ret;
+        }
+
+        public int Output(Span<byte> output)
+        {
+            int outLen = output.Length;
+            if (h0 == null)
+            {
+                h0 = new byte[hash.GetDigestSize()];
+                hash.DoFinal(h0);
+            }
+
+            if (digestLength != UnknownDigestLength)
+            {
+                if (digestPos + outLen > digestLength)
+                    throw new ArgumentException("Output length is above the digest length");
+            }
+            else if (blockPos << 5 >= GetUnknownMaxLength())
+            {
+                throw new ArgumentException("Maximum length is 2^32 blocks of 32 bytes");
+            }
+
+            for (int i = 0; i < outLen; i++)
+            {
+                if (bufPos >= DigestLength)
+                {
+                    Blake2sDigest h = new Blake2sDigest(ComputeStepLength(), DigestLength, nodeOffset);
+                    h.BlockUpdate(h0);
+
+                    Arrays.Fill(buf, 0);
+                    h.DoFinal(buf);
+                    bufPos = 0;
+                    nodeOffset++;
+                    blockPos++;
+                }
+                output[i] = buf[bufPos];
+                bufPos++;
+                digestPos++;
+            }
+
+            return outLen;
+        }
+#endif
+
+        // get the next round length. If the length is unknown, the digest length is always the maximum.
+        private int ComputeStepLength()
+        {
+            if (digestLength == UnknownDigestLength)
+                return DigestLength;
+
+            return System.Math.Min(DigestLength, digestLength - digestPos);
+        }
+
+        private long ComputeNodeOffset()
+        {
+            return digestLength * 0x100000000L;
+        }
+    }
+}