summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-10-12 22:07:18 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-10-12 22:07:18 +0700
commitc906c8b1b8be7a05c3ce0a49e465205d337971b1 (patch)
tree647a1b64c52caf462c9afc443ed3930bae5c1864
parentImprove digest API compliance in sphincs haraka impls (diff)
downloadBouncyCastle.NET-ed25519-c906c8b1b8be7a05c3ce0a49e465205d337971b1.tar.xz
Refactoring SphincsPlus (performance)
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/Fors.cs11
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HT.cs33
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs57
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs26
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs319
-rw-r--r--crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs146
6 files changed, 489 insertions, 103 deletions
diff --git a/crypto/src/pqc/crypto/sphincsplus/Fors.cs b/crypto/src/pqc/crypto/sphincsplus/Fors.cs
index 1698d1be7..7c83c2017 100644
--- a/crypto/src/pqc/crypto/sphincsplus/Fors.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/Fors.cs
@@ -48,7 +48,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     adrsTreeIndex = (adrsTreeIndex - 1) / 2;
                     adrs.SetTreeIndex(adrsTreeIndex);
 
-                    node = engine.H(pkSeed, adrs, stack.Pop().nodeValue, node);
+                    engine.H(pkSeed, adrs, stack.Pop().nodeValue, node, node);
 
                     //topmost node is now one layer higher
                     adrs.SetTreeHeight(++adrsTreeHeight);
@@ -123,13 +123,13 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     {
                         adrsTreeIndex = adrsTreeIndex / 2;
                         adrs.SetTreeIndex(adrsTreeIndex);
-                        node = engine.H(pkSeed, adrs, node, authPath[j]);
+                        engine.H(pkSeed, adrs, node, authPath[j], node);
                     }
                     else
                     {
                         adrsTreeIndex = (adrsTreeIndex - 1) / 2;
                         adrs.SetTreeIndex(adrsTreeIndex);
-                        node = engine.H(pkSeed, adrs, authPath[j], node);
+                        engine.H(pkSeed, adrs, authPath[j], node, node);
                     }
                 }
 
@@ -139,7 +139,10 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             Adrs forspkAdrs = new Adrs(adrs); // copy address to create FTS public key address
             forspkAdrs.SetAdrsType(Adrs.FORS_PK);
             forspkAdrs.SetKeyPairAddress(adrs.GetKeyPairAddress());
-            return engine.T_l(pkSeed, forspkAdrs, Arrays.ConcatenateAll(root));
+
+            byte[] result = new byte[engine.N];
+            engine.T_l(pkSeed, forspkAdrs, Arrays.ConcatenateAll(root), result);
+            return result;
         }
 
         /**
diff --git a/crypto/src/pqc/crypto/sphincsplus/HT.cs b/crypto/src/pqc/crypto/sphincsplus/HT.cs
index 59d0aeb1d..15893fc46 100644
--- a/crypto/src/pqc/crypto/sphincsplus/HT.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/HT.cs
@@ -1,11 +1,10 @@
-using System;
 using System.Collections.Generic;
 
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
-    class HT
+    internal class HT
     {
         private byte[] skSeed;
         private byte[] pkSeed;
@@ -14,7 +13,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         internal byte[] HTPubKey;
 
-        public HT(SphincsPlusEngine engine, byte[] skSeed, byte[] pkSeed)
+        internal HT(SphincsPlusEngine engine, byte[] skSeed, byte[] pkSeed)
         {
             this.skSeed = skSeed;
             this.pkSeed = pkSeed;
@@ -75,14 +74,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             return Arrays.ConcatenateAll(totSigs);
         }
 
-        byte[] xmss_PKgen(byte[] skSeed, byte[] pkSeed, Adrs adrs)
+        private byte[] xmss_PKgen(byte[] skSeed, byte[] pkSeed, Adrs adrs)
         {
             return TreeHash(skSeed, 0, engine.H_PRIME, pkSeed, adrs);
         }
 
         // Input: index idx, XMSS signature SIG_XMSS = (sig || AUTH), n-byte message M, public seed PK.seed, address Adrs
         // Output: n-byte root value node[0]
-        byte[] xmss_pkFromSig(uint idx, SIG_XMSS sig_xmss, byte[] M, byte[] pkSeed, Adrs paramAdrs)
+        private byte[] xmss_pkFromSig(uint idx, SIG_XMSS sig_xmss, byte[] M, byte[] pkSeed, Adrs paramAdrs)
         {
             Adrs adrs = new Adrs(paramAdrs);
 
@@ -92,8 +91,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             byte[] sig = sig_xmss.WotsSig;
             byte[][] AUTH = sig_xmss.XmssAuth;
 
-            byte[] node0 = wots.PKFromSig(sig, M, pkSeed, adrs);
-            byte[] node1 = null;
+            byte[] node = new byte[engine.N];
+            wots.PKFromSig(sig, M, pkSeed, adrs, node);
 
             // compute root from WOTS+ pk and AUTH
             adrs.SetAdrsType(Adrs.TREE);
@@ -104,24 +103,22 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 if (((idx / (1 << (int)k)) % 2) == 0)
                 {
                     adrs.SetTreeIndex(adrs.GetTreeIndex() / 2);
-                    node1 = engine.H(pkSeed, adrs, node0, AUTH[k]);
+                    engine.H(pkSeed, adrs, node, AUTH[k], node);
                 }
                 else
                 {
                     adrs.SetTreeIndex((adrs.GetTreeIndex() - 1) / 2);
-                    node1 = engine.H(pkSeed, adrs, AUTH[k], node0);
+                    engine.H(pkSeed, adrs, AUTH[k], node, node);
                 }
-
-                node0 = node1;
             }
 
-            return node0;
+            return node;
         }
 
         //    # Input: n-byte message M, secret seed SK.seed, index idx, public seed PK.seed,
         //    address Adrs
         //    # Output: XMSS signature SIG_XMSS = (sig || AUTH)
-        SIG_XMSS xmss_sign(byte[] M, byte[] skSeed, uint idx, byte[] pkSeed, Adrs paramAdrs)
+        private SIG_XMSS xmss_sign(byte[] M, byte[] skSeed, uint idx, byte[] pkSeed, Adrs paramAdrs)
         {
             byte[][] AUTH = new byte[engine.H_PRIME][];
             
@@ -151,7 +148,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         // Input: Secret seed SK.seed, start index s, target node height z, public seed
         //PK.seed, address Adrs
         // Output: n-byte root node - top node on Stack
-        byte[] TreeHash(byte[] skSeed, uint s, uint z, byte[] pkSeed, Adrs adrsParam)
+        private byte[] TreeHash(byte[] skSeed, uint s, uint z, byte[] pkSeed, Adrs adrsParam)
         {
             if (s % (1 << (int)z) != 0)
                 return null;
@@ -163,7 +160,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             {
                 adrs.SetAdrsType(Adrs.WOTS_HASH);
                 adrs.SetKeyPairAddress(s + idx);
-                byte[] node = wots.PKGen(skSeed, pkSeed, adrs);
+
+                byte[] node = new byte[engine.N];
+                wots.PKGen(skSeed, pkSeed, adrs, node);
 
                 adrs.SetAdrsType(Adrs.TREE);
                 adrs.SetTreeHeight(1);
@@ -178,7 +177,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     adrsTreeIndex = (adrsTreeIndex - 1) / 2;
                     adrs.SetTreeIndex(adrsTreeIndex);
 
-                    node = engine.H(pkSeed, adrs, stack.Pop().nodeValue, node);
+                    engine.H(pkSeed, adrs, stack.Pop().nodeValue, node, node);
 
                     //topmost node is now one layer higher
                     adrs.SetTreeHeight(++adrsTreeHeight);
@@ -193,7 +192,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         //    # Input: Message M, signature SIG_HT, public seed PK.seed, tree index idx_tree,
         //    leaf index idx_leaf, HT public key PK_HT.
         //    # Output: bool
-        public bool Verify(byte[] M, SIG_XMSS[] sig_ht, byte[] pkSeed, ulong idx_tree, uint idx_leaf, byte[] PK_HT)
+        internal bool Verify(byte[] M, SIG_XMSS[] sig_ht, byte[] pkSeed, ulong idx_tree, uint idx_leaf, byte[] PK_HT)
         {
             // init
             Adrs adrs = new Adrs();
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs
index f55a87778..beff653a7 100644
--- a/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaSXof.cs
@@ -1,5 +1,9 @@
 using System;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using Org.BouncyCastle.Utilities;
+#endif
+
 namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 {
     internal sealed class HarakaSXof
@@ -35,8 +39,8 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         public void BlockUpdate(byte[] input, int inOff, int len)
         {
-            int i = inOff, j, loop = (len + off) >> 5;
-            for (j = 0; j < loop; ++j)
+            int i = inOff, loop = (len + off) >> 5;
+            for (int j = 0; j < loop; ++j)
             {
                 while (off < 32)
                 {
@@ -51,6 +55,27 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int len = input.Length;
+            int i = 0, loop = (len + off) >> 5;
+            for (int j = 0; j < loop; ++j)
+            {
+                while (off < 32)
+                {
+                    buffer[off++] ^= input[i++];
+                }
+                Haraka512Perm(buffer);
+                off = 0;
+            }
+            while (i < len)
+            {
+                buffer[off++] ^= input[i++];
+            }
+        }
+#endif
+
         public int OutputFinal(byte[] output, int outOff, int len)
         {
             int outLen = len;
@@ -77,5 +102,33 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
             return outLen;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int OutputFinal(Span<byte> output)
+        {
+            int outLen = output.Length;
+
+            //Finalize
+            buffer[off] ^= 0x1F;
+            buffer[31] ^= 128;
+
+            //Squeeze
+            while (output.Length >= 32)
+            {
+                Haraka512Perm(buffer);
+                output[..32].CopyFrom(buffer);
+                output = output[32..];
+            }
+            if (!output.IsEmpty)
+            {
+                Haraka512Perm(buffer);
+                output.CopyFrom(buffer);
+            }
+
+            Reset();
+
+            return outLen;
+        }
+#endif
     }
 }
diff --git a/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs b/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs
index 35d7c883e..3975f02ff 100644
--- a/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/HarakaS_X86.cs
@@ -121,26 +121,32 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         public int Output(Span<byte> output)
         {
+            int result = output.Length;
+
             if (m_state != State.Squeezing)
             {
                 m_buf[m_bufPos] ^= 0x1F;
                 m_buf[31] ^= 0x80;
                 m_bufPos = 32;
                 m_state = State.Squeezing;
-            }
 
-            int result = output.Length;
-
-            int available = 32 - m_bufPos;
-            if (output.Length <= available)
+                if (output.IsEmpty)
+                    return result;
+            }
+            else
             {
-                output.CopyFrom(m_buf.AsSpan(m_bufPos));
-                m_bufPos += available;
-                return result;
+                int available = 32 - m_bufPos;
+                if (output.Length <= available)
+                {
+                    output.CopyFrom(m_buf.AsSpan(m_bufPos));
+                    m_bufPos += available;
+                    return result;
+                }
+
+                output[..available].CopyFrom(m_buf.AsSpan(m_bufPos));
+                output = output[available..];
             }
 
-            output[..available].CopyFrom(m_buf.AsSpan(m_bufPos));
-            output = output[available..];
             Debug.Assert(!output.IsEmpty);
 
             while (output.Length > 32)
diff --git a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs
index 3c295c3bd..72fd471d6 100644
--- a/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/SPHINCSPlusEngine.cs
@@ -94,11 +94,23 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
         public abstract byte[] F(byte[] pkSeed, Adrs adrs, byte[] m1);
 
-        public abstract byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void F(byte[] pkSeed, Adrs adrs, Span<byte> m1);
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output);
+#else
+        public abstract void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output);
+#endif
 
         public abstract IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message);
 
-        public abstract byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output);
+#else
+        public abstract void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output);
+#endif
 
         public abstract void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff);
 
@@ -177,7 +189,54 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return Arrays.CopyOfRange(sha256Buf, 0, N);
             }
 
-            public override byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                byte[] compressedAdrs = CompressedAdrs(adrs);
+
+                ((IMemoable)sha256).Reset(sha256Memo);
+
+                sha256.BlockUpdate(compressedAdrs);
+
+                if (robust)
+                {
+                    sha256.BlockUpdate(Bitmask256(Arrays.Concatenate(pkSeed, compressedAdrs), m1));
+                }
+                else
+                {
+                    sha256.BlockUpdate(m1);
+                }
+
+                sha256.DoFinal(sha256Buf);
+                m1.CopyFrom(sha256Buf);
+            }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                byte[] compressedAdrs = CompressedAdrs(adrs);
+
+                ((IMemoable)msgDigest).Reset(msgMemo);
+
+                msgDigest.BlockUpdate(compressedAdrs);
+                if (robust)
+                {
+                    byte[] m1m2 = Bitmask(Arrays.Concatenate(pkSeed, compressedAdrs), m1, m2);
+                    msgDigest.BlockUpdate(m1m2);
+                }
+                else
+                {
+                    msgDigest.BlockUpdate(m1);
+                    msgDigest.BlockUpdate(m2);
+                }
+
+                msgDigest.DoFinal(msgDigestBuf);
+
+                output[..N].CopyFrom(msgDigestBuf);
+            }
+#else
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output)
             {
                 byte[] compressedAdrs = CompressedAdrs(adrs);
 
@@ -197,8 +256,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
                 msgDigest.DoFinal(msgDigestBuf, 0);
 
-                return Arrays.CopyOfRange(msgDigestBuf, 0, N);
+                Array.Copy(msgDigestBuf, 0, output, 0, N);
             }
+#endif
 
             public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message)
             {
@@ -230,7 +290,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return new IndexedDigest(treeIndex, leafIndex, Arrays.CopyOfRange(output, 0, forsMsgBytes));
             }
 
-            public override byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+#else
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output)
+#endif
             {
                 byte[] compressedAdrs = CompressedAdrs(adrs);
                 if (robust)
@@ -238,14 +302,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     m = Bitmask(Arrays.Concatenate(pkSeed, compressedAdrs), m);
                 }
 
-
                 ((IMemoable)msgDigest).Reset(msgMemo);
 
                 msgDigest.BlockUpdate(compressedAdrs, 0, compressedAdrs.Length);
                 msgDigest.BlockUpdate(m, 0, m.Length);
                 msgDigest.DoFinal(msgDigestBuf, 0);
 
-                return Arrays.CopyOfRange(msgDigestBuf, 0, N);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                output[..N].CopyFrom(msgDigestBuf);
+#else
+                Array.Copy(msgDigestBuf, 0, output, 0, N);
+#endif
             }
 
             public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
@@ -300,7 +367,6 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return mask;
             }
 
-
             protected byte[] Bitmask(byte[] key, byte[] m1, byte[] m2)
             {
                 byte[] mask = new byte[m1.Length + m2.Length];
@@ -322,7 +388,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return mask;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            protected byte[] Bitmask256(byte[] key, ReadOnlySpan<byte> m)
+#else
             protected byte[] Bitmask256(byte[] key, byte[] m)
+#endif
             {
                 byte[] mask = new byte[m.Length];
 
@@ -368,18 +438,50 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 }
 
                 byte[] rv = new byte[N];
-
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 treeDigest.BlockUpdate(mTheta, 0, mTheta.Length);
                 treeDigest.OutputFinal(rv, 0, rv.Length);
-
                 return rv;
             }
 
-            public override byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                if (robust)
+                {
+                    Bitmask(pkSeed, adrs, m1);
+                }
+
+                treeDigest.BlockUpdate(pkSeed);
+                treeDigest.BlockUpdate(adrs.value);
+                treeDigest.BlockUpdate(m1);
+                treeDigest.OutputFinal(m1);
+            }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                treeDigest.BlockUpdate(pkSeed);
+                treeDigest.BlockUpdate(adrs.value);
+
+                if (robust)
+                {
+                    byte[] m1m2 = Bitmask(pkSeed, adrs, m1, m2);
+                    treeDigest.BlockUpdate(m1m2);
+                }
+                else
+                {
+                    treeDigest.BlockUpdate(m1);
+                    treeDigest.BlockUpdate(m2);
+                }
+
+                treeDigest.OutputFinal(output[..N]);
+            }
+#else
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output)
             {
-                byte[] rv = new byte[N];
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
 
@@ -395,10 +497,9 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     treeDigest.BlockUpdate(m2, 0, m2.Length);
                 }
 
-                treeDigest.OutputFinal(rv, 0, rv.Length);
-
-                return rv;
+                treeDigest.OutputFinal(output, 0, N);
             }
+#endif
 
             public override IndexedDigest H_msg(byte[] R, byte[] pkSeed, byte[] pkRoot, byte[] message)
             {
@@ -427,7 +528,11 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return new IndexedDigest(treeIndex, leafIndex, Arrays.CopyOfRange(output, 0, forsMsgBytes));
             }
 
-            public override byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+#else
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output)
+#endif
             {
                 byte[] mTheta = m;
                 if (robust)
@@ -435,14 +540,14 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                     mTheta = Bitmask(pkSeed, adrs, m);
                 }
 
-                byte[] rv = new byte[N];
-
                 treeDigest.BlockUpdate(pkSeed, 0, pkSeed.Length);
                 treeDigest.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 treeDigest.BlockUpdate(mTheta, 0, mTheta.Length);
-                treeDigest.OutputFinal(rv, 0, rv.Length);
-
-                return rv;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                treeDigest.OutputFinal(output[..N]);
+#else
+                treeDigest.OutputFinal(output, 0, N);
+#endif
             }
 
             public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
@@ -479,6 +584,21 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return mask;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            protected void Bitmask(ReadOnlySpan<byte> pkSeed, Adrs adrs, Span<byte> m)
+            {
+                Span<byte> mask = stackalloc byte[m.Length];
+                maskDigest.BlockUpdate(pkSeed);
+                maskDigest.BlockUpdate(adrs.value);
+                maskDigest.OutputFinal(mask);
+
+                for (int i = 0; i < m.Length; ++i)
+                {
+                    m[i] ^= mask[i];
+                }
+            }
+#endif
+
             protected byte[] Bitmask(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
             {
                 byte[] mask = new byte[m1.Length + m2.Length];
@@ -544,18 +664,59 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return N == 32 ? hash : Arrays.CopyOfRange(hash, 0, N);
             }
 
-            public override byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                Span<byte> hash = stackalloc byte[32];
+                if (robust)
+                {
+                    harakaS256Digest.BlockUpdate(adrs.value);
+                    harakaS256Digest.DoFinal(hash);
+                    for (int i = 0; i < m1.Length; ++i)
+                    {
+                        m1[i] ^= hash[i];
+                    }
+                }
+
+                harakaS512Digest.BlockUpdate(adrs.value);
+                harakaS512Digest.BlockUpdate(m1);
+                // NOTE The digest implementation implicitly pads the input with zeros up to 64 length
+                harakaS512Digest.DoFinal(hash);
+                m1.CopyFrom(hash);
+            }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
+            {
+                Span<byte> m = stackalloc byte[m1.Length + m2.Length];
+                m1.CopyTo(m);
+                m2.CopyTo(m[m1.Length..]);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
+                harakaSXof.BlockUpdate(adrs.value);
+                harakaSXof.BlockUpdate(m);
+                harakaSXof.OutputFinal(output[..N]);
+            }
+#else
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, byte[] output)
             {
-                byte[] rv = new byte[N];
                 byte[] m = new byte[m1.Length + m2.Length];
                 Array.Copy(m1, 0, m, 0, m1.Length);
                 Array.Copy(m2, 0, m, m1.Length, m2.Length);
-                m = Bitmask(adrs, m);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
                 harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 harakaSXof.BlockUpdate(m, 0, m.Length);
-                harakaSXof.OutputFinal(rv, 0, rv.Length);
-                return rv;
+                harakaSXof.OutputFinal(output, 0, N);
             }
+#endif
 
             public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message)
             {
@@ -582,14 +743,24 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return new IndexedDigest(treeIndex, leafIndex, Arrays.CopyOfRange(output, 0, forsMsgBytes));
             }
 
-            public override byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
+#else
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, byte[] output)
+#endif
             {
-                byte[] rv = new byte[N];
-                m = Bitmask(adrs, m);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
+
                 harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
                 harakaSXof.BlockUpdate(m, 0, m.Length);
-                harakaSXof.OutputFinal(rv, 0, rv.Length);
-                return rv;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                harakaSXof.OutputFinal(output[..N]);
+#else
+                harakaSXof.OutputFinal(output, 0, N);
+#endif
             }
 
             public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
@@ -611,20 +782,29 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return rv;
             }
 
-            protected byte[] Bitmask(Adrs adrs, byte[] m)
+            protected void Bitmask(Adrs adrs, byte[] m)
             {
-                if (robust)
+                byte[] mask = new byte[m.Length];
+                harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
+                harakaSXof.OutputFinal(mask, 0, mask.Length);
+                for (int i = 0; i < m.Length; ++i)
                 {
-                    byte[] mask = new byte[m.Length];
-                    harakaSXof.BlockUpdate(adrs.value, 0, adrs.value.Length);
-                    harakaSXof.OutputFinal(mask, 0, mask.Length);
-                    for (int i = 0; i < m.Length; ++i)
-                    {
-                        m[i] ^= mask[i];
-                    }
+                    m[i] ^= mask[i];
                 }
-                return m;
             }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            protected void Bitmask(Adrs adrs, Span<byte> m)
+            {
+                Span<byte> mask = stackalloc byte[m.Length];
+                harakaSXof.BlockUpdate(adrs.value);
+                harakaSXof.OutputFinal(mask);
+                for (int i = 0; i < m.Length; ++i)
+                {
+                    m[i] ^= mask[i];
+                }
+            }
+#endif
         }
 
 #if NETCOREAPP3_0_OR_GREATER
@@ -668,18 +848,41 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return buf[..N].ToArray();
             }
 
-            public override byte[] H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2)
+            public override void F(byte[] pkSeed, Adrs adrs, Span<byte> m1)
+            {
+                Span<byte> buf = stackalloc byte[64];
+                adrs.value.CopyTo(buf);
+
+                if (robust)
+                {
+                    Span<byte> mask = stackalloc byte[32];
+                    Haraka256_X86.Hash(adrs.value, mask, m_harakaS.RoundConstants);
+                    for (int i = 0; i < m1.Length; ++i)
+                    {
+                        buf[32 + i] = (byte)(m1[i] ^ mask[i]);
+                    }
+                }
+                else
+                {
+                    m1.CopyTo(buf[32..]);
+                }
+                Haraka512_X86.Hash(buf, buf, m_harakaS.RoundConstants);
+                m1.CopyFrom(buf);
+            }
+
+            public override void H(byte[] pkSeed, Adrs adrs, byte[] m1, byte[] m2, Span<byte> output)
             {
                 Span<byte> m = stackalloc byte[m1.Length + m2.Length];
                 m1.CopyTo(m);
                 m2.CopyTo(m[m1.Length..]);
-                Bitmask(adrs, m);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
 
-                byte[] rv = new byte[N];
                 m_harakaS.BlockUpdate(adrs.value);
                 m_harakaS.BlockUpdate(m);
-                m_harakaS.OutputFinal(rv);
-                return rv;
+                m_harakaS.OutputFinal(output[..N]);
             }
 
             public override IndexedDigest H_msg(byte[] prf, byte[] pkSeed, byte[] pkRoot, byte[] message)
@@ -710,15 +913,16 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 return new IndexedDigest(treeIndex, leafIndex, output);
             }
 
-            public override byte[] T_l(byte[] pkSeed, Adrs adrs, byte[] m)
+            public override void T_l(byte[] pkSeed, Adrs adrs, byte[] m, Span<byte> output)
             {
-                Bitmask(adrs, m);
+                if (robust)
+                {
+                    Bitmask(adrs, m);
+                }
 
-                byte[] rv = new byte[N];
                 m_harakaS.BlockUpdate(adrs.value);
                 m_harakaS.BlockUpdate(m);
-                m_harakaS.OutputFinal(rv);
-                return rv;
+                m_harakaS.OutputFinal(output[..N]);
             }
 
             public override void PRF(byte[] pkSeed, byte[] skSeed, Adrs adrs, byte[] prf, int prfOff)
@@ -742,15 +946,12 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
 
             protected void Bitmask(Adrs adrs, Span<byte> m)
             {
-                if (robust)
+                Span<byte> mask = stackalloc byte[m.Length];
+                m_harakaS.BlockUpdate(adrs.value);
+                m_harakaS.OutputFinal(mask);
+                for (int i = 0; i < m.Length; ++i)
                 {
-                    Span<byte> mask = stackalloc byte[m.Length];
-                    m_harakaS.BlockUpdate(adrs.value);
-                    m_harakaS.OutputFinal(mask);
-                    for (int i = 0; i < m.Length; ++i)
-                    {
-                        m[i] ^= mask[i];
-                    }
+                    m[i] ^= mask[i];
                 }
             }
         }
diff --git a/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs b/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs
index b254530d9..bd2d306b1 100644
--- a/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs
+++ b/crypto/src/pqc/crypto/sphincsplus/WotsPlus.cs
@@ -16,12 +16,20 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             this.w = this.engine.WOTS_W;
         }
 
-        internal byte[] PKGen(byte[] skSeed, byte[] pkSeed, Adrs paramAdrs)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void PKGen(byte[] skSeed, byte[] pkSeed, Adrs paramAdrs, Span<byte> output)
+#else
+        internal void PKGen(byte[] skSeed, byte[] pkSeed, Adrs paramAdrs, byte[] output)
+#endif
         {
             Adrs wotspkAdrs = new Adrs(paramAdrs); // copy address to create OTS public key address
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            byte[] tmpConcat = new byte[engine.WOTS_LEN * engine.N];
+#else
             byte[][] tmp = new byte[engine.WOTS_LEN][];
             byte[] sk = new byte[engine.N];
+#endif
             for (uint i = 0; i < engine.WOTS_LEN; i++)
             {
                 Adrs adrs = new Adrs(paramAdrs);
@@ -30,29 +38,63 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 adrs.SetChainAddress(i);
                 adrs.SetHashAddress(0);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                engine.PRF(pkSeed, skSeed, adrs, tmpConcat, engine.N * (int)i);
+#else
                 engine.PRF(pkSeed, skSeed, adrs, sk, 0);
+#endif
 
                 adrs.SetAdrsType(Adrs.WOTS_HASH);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
                 adrs.SetChainAddress(i);
                 adrs.SetHashAddress(0);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Chain(0, w - 1, pkSeed, adrs, tmpConcat.AsSpan(engine.N * (int)i, engine.N));
+#else
                 tmp[i] = Chain(sk, 0, w - 1, pkSeed, adrs);
+#endif
             }
 
             wotspkAdrs.SetAdrsType(Adrs.WOTS_PK);
             wotspkAdrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
 
-            return engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            engine.T_l(pkSeed, wotspkAdrs, tmpConcat, output);
+#else
+            engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp), output);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         // #Input: Input string X, start index i, number of steps s, public seed PK.seed, address Adrs
         // #Output: value of F iterated s times on X
-        internal byte[] Chain(byte[] X, uint i, uint s, byte[] pkSeed, Adrs adrs)
+        private bool Chain(uint i, uint s, byte[] pkSeed, Adrs adrs, Span<byte> X)
+        {
+            if (s == 0)
+                return true;
+
+            // TODO Check this since the highest we use is i + s - 1
+            if ((i + s) > (this.w - 1))
+                return false;
+
+            for (uint j = 0; j < s; ++j)
+            {
+                adrs.SetHashAddress(i + j);
+                engine.F(pkSeed, adrs, X);
+            }
+
+            return true;
+        }
+#else
+        // #Input: Input string X, start index i, number of steps s, public seed PK.seed, address Adrs
+        // #Output: value of F iterated s times on X
+        private byte[] Chain(byte[] X, uint i, uint s, byte[] pkSeed, Adrs adrs)
         {
             if (s == 0)
                 return Arrays.Clone(X);
 
+            // TODO Check this since the highest we use is i + s - 1
             if ((i + s) > (this.w - 1))
                 return null;
 
@@ -64,6 +106,7 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             }
             return result;
         }
+#endif
 
         // #Input: Message M, secret seed SK.seed, public seed PK.seed, address Adrs
         // #Output: WOTS+ signature sig
@@ -71,10 +114,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
         {
             Adrs adrs = new Adrs(paramAdrs);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<uint> msg = stackalloc uint[engine.WOTS_LEN];
+
+            // convert message to base w
+            BaseW(M, w, msg[..engine.WOTS_LEN1]);
+#else
             uint[] msg = new uint[engine.WOTS_LEN];
 
             // convert message to base w
             BaseW(M, 0, w, msg, 0, engine.WOTS_LEN1);
+#endif
 
             // compute checksum
             uint csum = 0;
@@ -89,34 +139,76 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 csum <<= 8 - (engine.WOTS_LEN2 * engine.WOTS_LOGW % 8);
             }
             int len_2_bytes = (engine.WOTS_LEN2 * engine.WOTS_LOGW + 7) / 8;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> csum_bytes = stackalloc byte[4];
+            Pack.UInt32_To_BE(csum, csum_bytes);
+            BaseW(csum_bytes[^len_2_bytes..], w, msg[engine.WOTS_LEN1..]);
+
+            byte[] sigConcat = new byte[engine.WOTS_LEN * engine.N];
+#else
             byte[] csum_bytes = Pack.UInt32_To_BE(csum);
             BaseW(csum_bytes, 4 - len_2_bytes, w, msg, engine.WOTS_LEN1, engine.WOTS_LEN2);
 
             byte[][] sig = new byte[engine.WOTS_LEN][];
             byte[] sk = new byte[engine.N];
-            for (uint i = 0; i < engine.WOTS_LEN; i++)
+#endif
+            for (int i = 0; i < engine.WOTS_LEN; i++)
             {
                 adrs.SetAdrsType(Adrs.WOTS_PRF);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
-                adrs.SetChainAddress(i);
+                adrs.SetChainAddress((uint)i);
                 adrs.SetHashAddress(0);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                engine.PRF(pkSeed, skSeed, adrs, sigConcat, engine.N * i);
+#else
                 engine.PRF(pkSeed, skSeed, adrs, sk, 0);
+#endif
 
                 adrs.SetAdrsType(Adrs.WOTS_HASH);
                 adrs.SetKeyPairAddress(paramAdrs.GetKeyPairAddress());
-                adrs.SetChainAddress(i);
+                adrs.SetChainAddress((uint)i);
                 adrs.SetHashAddress(0);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Chain(0, msg[i], pkSeed, adrs, sigConcat.AsSpan(engine.N * i, engine.N));
+#else
                 sig[i] = Chain(sk, 0, msg[i], pkSeed, adrs);
+#endif
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return sigConcat;
+#else
             return Arrays.ConcatenateAll(sig);
+#endif
         }
 
         //
         // Input: len_X-byte string X, int w, output length out_len
         // Output: outLen int array basew
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void BaseW(ReadOnlySpan<byte> X, uint w, Span<uint> output)
+        {
+            int total = 0;
+            int bits = 0;
+            int XOff = 0;
+            int outOff = 0;
+
+            for (int consumed = 0; consumed < output.Length; consumed++)
+            {
+                if (bits == 0)
+                {
+                    total = X[XOff++];
+                    bits += 8;
+                }
+
+                bits -= engine.WOTS_LOGW;
+                output[outOff++] = (uint)((total >> bits) & (w - 1));
+            }
+        }
+#else
         internal void BaseW(byte[] X, int XOff, uint w, uint[] output, int outOff, int outLen)
         {
             int total = 0;
@@ -134,15 +226,27 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
                 output[outOff++] = (uint)((total >> bits) & (w - 1));
             }
         }
+#endif
 
-        internal byte[] PKFromSig(byte[] sig, byte[] M, byte[] pkSeed, Adrs adrs)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void PKFromSig(byte[] sig, byte[] M, byte[] pkSeed, Adrs adrs, Span<byte> output)
+#else
+        internal void PKFromSig(byte[] sig, byte[] M, byte[] pkSeed, Adrs adrs, byte[] output)
+#endif
         {
             Adrs wotspkAdrs = new Adrs(adrs);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<uint> msg = stackalloc uint[engine.WOTS_LEN];
+
+            // convert message to base w
+            BaseW(M, w, msg[..engine.WOTS_LEN1]);
+#else
             uint[] msg = new uint[engine.WOTS_LEN];
 
             // convert message to base w
             BaseW(M, 0, w, msg, 0, engine.WOTS_LEN1);
+#endif
 
             // compute checksum
             uint csum = 0;
@@ -154,22 +258,42 @@ namespace Org.BouncyCastle.Pqc.Crypto.SphincsPlus
             // convert csum to base w
             csum <<= 8 - (engine.WOTS_LEN2 * engine.WOTS_LOGW % 8);
             int len_2_bytes = (engine.WOTS_LEN2 * engine.WOTS_LOGW + 7) / 8;
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> csum_bytes = stackalloc byte[4];
+            Pack.UInt32_To_BE(csum, csum_bytes);
+            BaseW(csum_bytes[^len_2_bytes..], w, msg[engine.WOTS_LEN1..]);
+
+            byte[] tmpConcat = new byte[engine.WOTS_LEN * engine.N];
+#else
             byte[] csum_bytes = Pack.UInt32_To_BE(csum);
             BaseW(csum_bytes, 4 - len_2_bytes, w, msg, engine.WOTS_LEN1, engine.WOTS_LEN2);
 
             byte[] sigI = new byte[engine.N];
             byte[][] tmp = new byte[engine.WOTS_LEN][];
-            for (uint i = 0; i < engine.WOTS_LEN; i++)
+#endif
+            for (int i = 0; i < engine.WOTS_LEN; i++)
             {
-                adrs.SetChainAddress(i);
-                Array.Copy(sig, i * engine.N, sigI, 0, engine.N);
+                adrs.SetChainAddress((uint)i);
+
+                int sigPos = engine.N * i;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Array.Copy(sig, sigPos, tmpConcat, sigPos, engine.N);
+                Chain(msg[i], w - 1 - msg[i], pkSeed, adrs, tmpConcat.AsSpan(sigPos, engine.N));
+#else
+                Array.Copy(sig, sigPos, sigI, 0, engine.N);
                 tmp[i] = Chain(sigI, msg[i], w - 1 - msg[i], pkSeed, adrs);
+#endif
             }
 
             wotspkAdrs.SetAdrsType(Adrs.WOTS_PK);
             wotspkAdrs.SetKeyPairAddress(adrs.GetKeyPairAddress());
 
-            return engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp));
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            engine.T_l(pkSeed, wotspkAdrs, tmpConcat, output);
+#else
+            engine.T_l(pkSeed, wotspkAdrs, Arrays.ConcatenateAll(tmp), output);
+#endif
         }
     }
 }