summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs26
-rw-r--r--crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs20
-rw-r--r--crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs26
-rw-r--r--crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs20
-rw-r--r--crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs30
-rw-r--r--crypto/src/crypto/parameters/X25519PublicKeyParameters.cs13
-rw-r--r--crypto/src/crypto/parameters/X448PrivateKeyParameters.cs30
-rw-r--r--crypto/src/crypto/parameters/X448PublicKeyParameters.cs13
-rw-r--r--crypto/src/math/ec/rfc7748/X25519.cs42
-rw-r--r--crypto/src/math/ec/rfc7748/X25519Field.cs21
-rw-r--r--crypto/src/math/ec/rfc7748/X448.cs40
-rw-r--r--crypto/src/math/ec/rfc7748/X448Field.cs23
-rw-r--r--crypto/src/math/ec/rfc8032/Ed25519.cs265
-rw-r--r--crypto/src/math/ec/rfc8032/Ed448.cs258
-rw-r--r--crypto/src/math/raw/Nat.cs30
15 files changed, 847 insertions, 10 deletions
diff --git a/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs b/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
index 56ca19ed2..a50f71972 100644
--- a/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
@@ -35,6 +35,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed25519PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed25519PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -47,6 +58,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -58,9 +76,15 @@ namespace Org.BouncyCastle.Crypto.Parameters
             {
                 if (null == cachedPublicKey)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    Span<byte> publicKey = stackalloc byte[Ed25519.PublicKeySize];
+                    Ed25519.GeneratePublicKey(data, publicKey);
+                    cachedPublicKey = new Ed25519PublicKeyParameters(publicKey);
+#else
                     byte[] publicKey = new byte[Ed25519.PublicKeySize];
                     Ed25519.GeneratePublicKey(data, 0, publicKey, 0);
                     cachedPublicKey = new Ed25519PublicKeyParameters(publicKey, 0);
+#endif
                 }
 
                 return cachedPublicKey;
@@ -108,7 +132,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs b/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
index 8a9139e8d..9b94635d5 100644
--- a/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed25519PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed25519PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -37,6 +48,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -45,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs b/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
index a6a1b72a1..ac12a2f1d 100644
--- a/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
@@ -35,6 +35,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed448PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed448PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -47,6 +58,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -58,9 +76,15 @@ namespace Org.BouncyCastle.Crypto.Parameters
             {
                 if (null == cachedPublicKey)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    Span<byte> publicKey = stackalloc byte[Ed448.PublicKeySize];
+                    Ed448.GeneratePublicKey(data, publicKey);
+                    cachedPublicKey = new Ed448PublicKeyParameters(publicKey);
+#else
                     byte[] publicKey = new byte[Ed448.PublicKeySize];
                     Ed448.GeneratePublicKey(data, 0, publicKey, 0);
                     cachedPublicKey = new Ed448PublicKeyParameters(publicKey, 0);
+#endif
                 }
 
                 return cachedPublicKey;
@@ -100,7 +124,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs b/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
index 8a89be08f..26f6b5ba9 100644
--- a/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public Ed448PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public Ed448PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -37,6 +48,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -45,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs b/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs
index a9e28d1cc..8b263c861 100644
--- a/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X25519PrivateKeyParameters.cs
@@ -33,6 +33,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X25519PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X25519PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -45,6 +56,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -52,17 +70,27 @@ namespace Org.BouncyCastle.Crypto.Parameters
 
         public X25519PublicKeyParameters GeneratePublicKey()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> publicKey = stackalloc byte[X25519.PointSize];
+            X25519.GeneratePublicKey(data, publicKey);
+            return new X25519PublicKeyParameters(publicKey);
+#else
             byte[] publicKey = new byte[X25519.PointSize];
             X25519.GeneratePublicKey(data, 0, publicKey, 0);
             return new X25519PublicKeyParameters(publicKey, 0);
+#endif
         }
 
         public void GenerateSecret(X25519PublicKeyParameters publicKey, byte[] buf, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GenerateSecret(publicKey, buf.AsSpan(off));
+#else
             byte[] encoded = new byte[X25519.PointSize];
             publicKey.Encode(encoded, 0);
             if (!X25519.CalculateAgreement(data, 0, encoded, 0, buf, off))
                 throw new InvalidOperationException("X25519 agreement failed");
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
@@ -78,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs b/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs
index 744a1355f..5d94ac10a 100644
--- a/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X25519PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X25519PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X25519PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -52,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs b/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs
index 8ae7aa17d..555773b10 100644
--- a/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X448PrivateKeyParameters.cs
@@ -33,6 +33,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X448PrivateKeyParameters(ReadOnlySpan<byte> buf)
+            : base(true)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X448PrivateKeyParameters(Stream input)
             : base(true)
         {
@@ -45,6 +56,13 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(data, 0, buf, off, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Encode(Span<byte> buf)
+        {
+            data.CopyTo(buf);
+        }
+#endif
+
         public byte[] GetEncoded()
         {
             return Arrays.Clone(data);
@@ -52,17 +70,27 @@ namespace Org.BouncyCastle.Crypto.Parameters
 
         public X448PublicKeyParameters GeneratePublicKey()
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> publicKey = stackalloc byte[X448.PointSize];
+            X448.GeneratePublicKey(data, publicKey);
+            return new X448PublicKeyParameters(publicKey);
+#else
             byte[] publicKey = new byte[X448.PointSize];
             X448.GeneratePublicKey(data, 0, publicKey, 0);
             return new X448PublicKeyParameters(publicKey, 0);
+#endif
         }
 
         public void GenerateSecret(X448PublicKeyParameters publicKey, byte[] buf, int off)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GenerateSecret(publicKey, buf.AsSpan(off));
+#else
             byte[] encoded = new byte[X448.PointSize];
             publicKey.Encode(encoded, 0);
             if (!X448.CalculateAgreement(data, 0, encoded, 0, buf, off))
                 throw new InvalidOperationException("X448 agreement failed");
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
@@ -78,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/crypto/parameters/X448PublicKeyParameters.cs b/crypto/src/crypto/parameters/X448PublicKeyParameters.cs
index 62e73dda6..94db22147 100644
--- a/crypto/src/crypto/parameters/X448PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/X448PublicKeyParameters.cs
@@ -25,6 +25,17 @@ namespace Org.BouncyCastle.Crypto.Parameters
             Array.Copy(buf, off, data, 0, KeySize);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public X448PublicKeyParameters(ReadOnlySpan<byte> buf)
+            : base(false)
+        {
+            if (buf.Length != KeySize)
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
+
+            buf.CopyTo(data);
+        }
+#endif
+
         public X448PublicKeyParameters(Stream input)
             : base(false)
         {
@@ -52,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
         private static byte[] Validate(byte[] buf)
         {
             if (buf.Length != KeySize)
-                throw new ArgumentException("must have length " + KeySize, "buf");
+                throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
             return buf;
         }
diff --git a/crypto/src/math/ec/rfc7748/X25519.cs b/crypto/src/math/ec/rfc7748/X25519.cs
index 2a471ae26..954b2dd90 100644
--- a/crypto/src/math/ec/rfc7748/X25519.cs
+++ b/crypto/src/math/ec/rfc7748/X25519.cs
@@ -80,18 +80,42 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+
+            k[0] &= 0xF8;
+            k[ScalarSize - 1] &= 0x7F;
+            k[ScalarSize - 1] |= 0x40;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
 
             k[0] &= 0xF8;
             k[ScalarSize - 1] &= 0x7F;
             k[ScalarSize - 1] |= 0x40;
         }
+#endif
 
         public static void GeneratePublicKey(byte[] k, int kOff, byte[] r, int rOff)
         {
             ScalarMultBase(k, kOff, r, rOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            ScalarMultBase(k, r);
+        }
+#endif
+
         private static void PointDouble(int[] x, int[] z)
         {
             int[] a = F.Create();
@@ -255,5 +279,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             F.Normalize(y);
             F.Encode(y, r, rOff);
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ScalarMultBase(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            int[] y = F.Create();
+            int[] z = F.Create();
+
+            Ed25519.ScalarMultBaseYZ(k, y, z);
+
+            F.Apm(z, y, y, z);
+
+            F.Inv(z, z);
+            F.Mul(y, z, y);
+
+            F.Normalize(y);
+            F.Encode(y, r);
+        }
+#endif
     }
 }
diff --git a/crypto/src/math/ec/rfc7748/X25519Field.cs b/crypto/src/math/ec/rfc7748/X25519Field.cs
index b4ea9a9ce..8365df03b 100644
--- a/crypto/src/math/ec/rfc7748/X25519Field.cs
+++ b/crypto/src/math/ec/rfc7748/X25519Field.cs
@@ -107,6 +107,20 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CMov(int cond, ReadOnlySpan<int> x, Span<int> z)
+        {
+            Debug.Assert(0 == cond || -1 == cond);
+
+            for (int i = 0; i < Size; ++i)
+            {
+                int z_i = z[i], diff = z_i ^ x[i];
+                z_i ^= (diff & cond);
+                z[i] = z_i;
+            }
+        }
+#endif
+
         public static void CNegate(int negate, int[] z)
         {
             Debug.Assert(negate >> 1 == 0);
@@ -126,6 +140,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Copy(ReadOnlySpan<int> x, Span<int> z)
+        {
+            x[..Size].CopyTo(z);
+        }
+#endif
+
         public static int[] Create()
         {
             return new int[Size];
diff --git a/crypto/src/math/ec/rfc7748/X448.cs b/crypto/src/math/ec/rfc7748/X448.cs
index ba2660f3c..2f6016a61 100644
--- a/crypto/src/math/ec/rfc7748/X448.cs
+++ b/crypto/src/math/ec/rfc7748/X448.cs
@@ -79,17 +79,40 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
 
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
 
             k[0] &= 0xFC;
             k[ScalarSize - 1] |= 0x80;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != ScalarSize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+
+            k[0] &= 0xFC;
+            k[ScalarSize - 1] |= 0x80;
+        }
+#endif
+
         public static void GeneratePublicKey(byte[] k, int kOff, byte[] r, int rOff)
         {
             ScalarMultBase(k, kOff, r, rOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            ScalarMultBase(k, r);
+        }
+#endif
+
         private static void PointDouble(uint[] x, uint[] z)
         {
             uint[] a = F.Create();
@@ -268,5 +291,22 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             F.Normalize(x);
             F.Encode(x, r, rOff);
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void ScalarMultBase(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            uint[] x = F.Create();
+            uint[] y = F.Create();
+
+            Ed448.ScalarMultBaseXY(k, x, y);
+
+            F.Inv(x, x);
+            F.Mul(x, y, x);
+            F.Sqr(x, x);
+
+            F.Normalize(x);
+            F.Encode(x, r);
+        }
+#endif
     }
 }
diff --git a/crypto/src/math/ec/rfc7748/X448Field.cs b/crypto/src/math/ec/rfc7748/X448Field.cs
index a91ea0d5b..a1a86b61c 100644
--- a/crypto/src/math/ec/rfc7748/X448Field.cs
+++ b/crypto/src/math/ec/rfc7748/X448Field.cs
@@ -112,6 +112,22 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void CMov(int cond, ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            Debug.Assert(0 == cond || -1 == cond);
+
+            uint MASK = (uint)cond;
+
+            for (int i = 0; i < Size; ++i)
+            {
+                uint z_i = z[i], diff = z_i ^ x[i];
+                z_i ^= (diff & MASK);
+                z[i] = z_i;
+            }
+        }
+#endif
+
         public static void CNegate(int negate, uint[] z)
         {
             Debug.Assert(negate >> 1 == 0);
@@ -130,6 +146,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void Copy(ReadOnlySpan<uint> x, Span<uint> z)
+        {
+            x[..Size].CopyTo(z);
+        }
+#endif
+
         public static uint[] Create()
         {
             return new uint[Size];
diff --git a/crypto/src/math/ec/rfc8032/Ed25519.cs b/crypto/src/math/ec/rfc8032/Ed25519.cs
index d88914c90..128ec4244 100644
--- a/crypto/src/math/ec/rfc8032/Ed25519.cs
+++ b/crypto/src/math/ec/rfc8032/Ed25519.cs
@@ -92,12 +92,12 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         private static PointPrecomp[] PrecompBaseWnaf = null;
         private static int[] PrecompBaseComb = null;
 
-        private ref struct PointAccum
+        private struct PointAccum
         {
             internal int[] x, y, z, u, v;
         }
 
-        private ref struct PointAffine
+        private struct PointAffine
         {
             internal int[] x, y;
         }
@@ -238,6 +238,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+#endif
+
         private static void Decode32(byte[] bs, int bsOff, uint[] n, int nOff, int nLen)
         {
             for (int i = 0; i < nLen; ++i)
@@ -246,6 +257,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode32(ReadOnlySpan<byte> bs, Span<uint> n)
+        {
+            for (int i = 0; i < n.Length; ++i)
+            {
+                n[i] = Decode32(bs[(i * 4)..]);
+            }
+        }
+#endif
+
         private static bool DecodePointVar(byte[] p, int pOff, bool negate, ref PointAffine r)
         {
             byte[] py = Copy(p, pOff, PointBytes);
@@ -285,6 +306,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Decode32(k, kOff, n, 0, ScalarUints);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void DecodeScalar(ReadOnlySpan<byte> k, Span<uint> n)
+        {
+            Decode32(k, n[..ScalarUints]);
+        }
+#endif
+
         private static void Dom2(IDigest d, byte phflag, byte[] ctx)
         {
             if (ctx != null)
@@ -323,6 +351,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static int EncodePoint(ref PointAccum p, byte[] r, int rOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodePoint(ref p, r.AsSpan(rOff));
+#else
             int[] x = F.Create();
             int[] y = F.Create();
 
@@ -338,13 +369,48 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             r[rOff + PointBytes - 1] |= (byte)((x[0] & 1) << 7);
 
             return result;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int EncodePoint(ref PointAccum p, Span<byte> r)
+        {
+            int[] x = F.Create();
+            int[] y = F.Create();
+
+            F.Inv(p.z, y);
+            F.Mul(p.x, y, x);
+            F.Mul(p.y, y, y);
+            F.Normalize(x);
+            F.Normalize(y);
+
+            int result = CheckPoint(x, y);
+
+            F.Encode(y, r);
+            r[PointBytes - 1] |= (byte)((x[0] & 1) << 7);
+
+            return result;
         }
+#endif
 
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+        }
+#endif
+
         public static void GeneratePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff)
         {
             IDigest d = CreateDigest();
@@ -359,7 +425,27 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             ScalarMultBaseEncoded(s, pk, pkOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> sk, Span<byte> pk)
+        {
+            IDigest d = CreateDigest();
+            Span<byte> h = stackalloc byte[d.GetDigestSize()];
+
+            d.BlockUpdate(sk[..SecretKeySize]);
+            d.DoFinal(h);
+
+            Span<byte> s = stackalloc byte[ScalarBytes];
+            PruneScalar(h, s);
+
+            ScalarMultBaseEncoded(s, pk);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint GetWindow4(ReadOnlySpan<uint> x, int n)
+#else
         private static uint GetWindow4(uint[] x, int n)
+#endif
         {
             int w = (int)((uint)n >> 3), b = (n & 7) << 2;
             return (x[w] >> b) & 15U;
@@ -818,6 +904,32 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PointLookupZ(ReadOnlySpan<uint> x, int n, ReadOnlySpan<int> table, ref PointPrecompZ r)
+        {
+            // TODO This method is currently hard-coded to 4-bit windows and 8 precomputed points
+
+            uint w = GetWindow4(x, n);
+
+            int sign = (int)(w >> (4 - 1)) ^ 1;
+            int abs = ((int)w ^ -sign) & 7;
+
+            Debug.Assert(sign == 0 || sign == 1);
+            Debug.Assert(0 <= abs && abs < 8);
+
+            for (int i = 0; i < 8; ++i)
+            {
+                int cond = ((i ^ abs) - 1) >> 31;
+                F.CMov(cond, table, r.ymx_h);       table = table[F.Size..];
+                F.CMov(cond, table, r.ypx_h);       table = table[F.Size..];
+                F.CMov(cond, table, r.xyd);         table = table[F.Size..];
+                F.CMov(cond, table, r.z);           table = table[F.Size..];
+            }
+
+            F.CSwap(sign, r.ymx_h, r.ypx_h);
+            F.CNegate(sign, r.xyd);
+        }
+#else
         private static void PointLookupZ(uint[] x, int n, int[] table, ref PointPrecompZ r)
         {
             // TODO This method is currently hard-coded to 4-bit windows and 8 precomputed points
@@ -842,6 +954,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.CSwap(sign, r.ymx_h, r.ypx_h);
             F.CNegate(sign, r.xyd);
         }
+#endif
 
         private static void PointPrecompute(ref PointAffine p, PointExtended[] points, int count, ref PointTemp t)
         {
@@ -1070,6 +1183,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             r[ScalarBytes - 1] |= 0x40;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PruneScalar(ReadOnlySpan<byte> n, Span<byte> r)
+        {
+            n[..ScalarBytes].CopyTo(r);
+
+            r[0] &= 0xF8;
+            r[ScalarBytes - 1] &= 0x7F;
+            r[ScalarBytes - 1] |= 0x40;
+        }
+#endif
+
         private static byte[] ReduceScalar(byte[] n)
         {
             long x00 = Decode32(n,  0) & M32L;          // x00:32/--
@@ -1208,6 +1332,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static void ScalarMult(byte[] k, ref PointAffine p, ref PointAccum r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMult(k.AsSpan(), ref p, ref r);
+#else
             uint[] n = new uint[ScalarUints];
             DecodeScalar(k, 0, n);
 
@@ -1237,10 +1364,49 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                     PointDouble(ref r);
                 }
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMult(ReadOnlySpan<byte> k, ref PointAffine p, ref PointAccum r)
+        {
+            Span<uint> n = stackalloc uint[ScalarUints];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form
+            {
+                uint c1 = Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);   Debug.Assert(c1 == 0U);
+                uint c2 = Nat.ShiftDownBit(ScalarUints, n, 1U);             Debug.Assert(c2 == (1U << 31));
+            }
+
+            Init(out PointPrecompZ q);
+            Init(out PointTemp t);
+            int[] table = PointPrecomputeZ(ref p, 8, ref t);
+
+            PointSetNeutral(ref r);
+
+            int w = 63;
+            for (;;)
+            {
+                PointLookupZ(n, w, table, ref q);
+                PointAdd(ref q, ref r, ref t);
+
+                if (--w < 0)
+                    break;
+
+                for (int i = 0; i < 4; ++i)
+                {
+                    PointDouble(ref r);
+                }
+            }
+        }
+#endif
+
         private static void ScalarMultBase(byte[] k, ref PointAccum r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBase(k.AsSpan(), ref r);
+#else
             // Equivalent (but much slower)
             //PointAffine p; Init(out p);
             //F.Copy(B_x, 0, p.x, 0);
@@ -1302,8 +1468,76 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             F.CNegate(resultSign, r.x);
             F.CNegate(resultSign, r.u);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBase(ReadOnlySpan<byte> k, ref PointAccum r)
+        {
+            // Equivalent (but much slower)
+            //Init(out PointAffine p);
+            //F.Copy(B_x, 0, p.x, 0);
+            //F.Copy(B_y, 0, p.y, 0);
+            //ScalarMult(k, ref p, ref r);
+
+            Precompute();
+
+            Span<uint> n = stackalloc uint[ScalarUints];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form, then group comb bits in each block
+            {
+                uint c1 = Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);   Debug.Assert(c1 == 0U);
+                uint c2 = Nat.ShiftDownBit(ScalarUints, n, 1U);             Debug.Assert(c2 == (1U << 31));
+
+                /*
+                 * Because we are using 4 teeth and 8 spacing, each limb of n corresponds to one of the 8 blocks.
+                 * Therefore we can efficiently group the bits for each comb position using a (double) shuffle. 
+                 */
+                for (int i = 0; i < ScalarUints; ++i)
+                {
+                    n[i] = Interleave.Shuffle2(n[i]);
+                }
+            }
+
+            Init(out PointPrecomp p);
+            Init(out PointTemp t);
+
+            PointSetNeutral(ref r);
+            int resultSign = 0;
+
+            int cOff = (PrecompSpacing - 1) * PrecompTeeth;
+            for (;;)
+            {
+                for (int b = 0; b < PrecompBlocks; ++b)
+                {
+                    uint w = n[b] >> cOff;
+                    int sign = (int)(w >> (PrecompTeeth - 1)) & 1;
+                    int abs = ((int)w ^ -sign) & PrecompMask;
+
+                    Debug.Assert(sign == 0 || sign == 1);
+                    Debug.Assert(0 <= abs && abs < PrecompPoints);
+
+                    PointLookup(b, abs, ref p);
+
+                    F.CNegate(resultSign ^ sign, r.x);
+                    F.CNegate(resultSign ^ sign, r.u);
+                    resultSign = sign;
+
+                    PointAdd(ref p, ref r, ref t);
+                }
+
+                if ((cOff -= PrecompTeeth) < 0)
+                    break;
+
+                PointDouble(ref r);
+            }
+
+            F.CNegate(resultSign, r.x);
+            F.CNegate(resultSign, r.u);
+        }
+#endif
+
         private static void ScalarMultBaseEncoded(byte[] k, byte[] r, int rOff)
         {
             PointAccum p; Init(out p);
@@ -1312,6 +1546,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 throw new InvalidOperationException();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBaseEncoded(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            Init(out PointAccum p);
+            ScalarMultBase(k, ref p);
+            if (0 == EncodePoint(ref p, r))
+                throw new InvalidOperationException();
+        }
+#endif
+
         internal static void ScalarMultBaseYZ(byte[] k, int kOff, int[] y, int[] z)
         {
             byte[] n = new byte[ScalarBytes];
@@ -1327,6 +1571,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.Copy(p.z, 0, z, 0);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void ScalarMultBaseYZ(ReadOnlySpan<byte> k, Span<int> y, Span<int> z)
+        {
+            Span<byte> n = stackalloc byte[ScalarBytes];
+            PruneScalar(k, n);
+
+            Init(out PointAccum p);
+            ScalarMultBase(n, ref p);
+
+            if (0 == CheckPoint(p.x, p.y, p.z))
+                throw new InvalidOperationException();
+
+            F.Copy(p.y, y);
+            F.Copy(p.z, z);
+        }
+#endif
+
         private static void ScalarMultOrderVar(ref PointAffine p, ref PointAccum r)
         {
             sbyte[] ws_p = GetWnafVar(L, WnafWidth);
diff --git a/crypto/src/math/ec/rfc8032/Ed448.cs b/crypto/src/math/ec/rfc8032/Ed448.cs
index 55ec5f03b..8595f2f10 100644
--- a/crypto/src/math/ec/rfc8032/Ed448.cs
+++ b/crypto/src/math/ec/rfc8032/Ed448.cs
@@ -9,6 +9,7 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Math.EC.Rfc8032
 {
+    using static Org.BouncyCastle.Pqc.Crypto.Picnic.Signature;
     using F = Rfc7748.X448Field;
 
     /// <summary>
@@ -222,6 +223,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint Decode32(ReadOnlySpan<byte> bs)
+        {
+            uint n = bs[0];
+            n |= (uint)bs[1] << 8;
+            n |= (uint)bs[2] << 16;
+            n |= (uint)bs[3] << 24;
+            return n;
+        }
+#endif
+
         private static void Decode32(byte[] bs, int bsOff, uint[] n, int nOff, int nLen)
         {
             for (int i = 0; i < nLen; ++i)
@@ -230,6 +242,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void Decode32(ReadOnlySpan<byte> bs, Span<uint> n)
+        {
+            for (int i = 0; i < n.Length; ++i)
+            {
+                n[i] = Decode32(bs[(i * 4)..]);
+            }
+        }
+#endif
+
         private static bool DecodePointVar(byte[] p, int pOff, bool negate, ref PointProjective r)
         {
             byte[] py = Copy(p, pOff, PointBytes);
@@ -273,6 +295,15 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Decode32(k, kOff, n, 0, ScalarUints);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void DecodeScalar(ReadOnlySpan<byte> k, Span<uint> n)
+        {
+            Debug.Assert(k[ScalarBytes - 1] == 0x00);
+
+            Decode32(k, n[..ScalarUints]);
+        }
+#endif
+
         private static void Dom4(IXof d, byte phflag, byte[] ctx)
         {
             int n = Dom4Prefix.Length;
@@ -325,11 +356,45 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return result;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static int EncodePoint(ref PointProjective p, Span<byte> r)
+        {
+            uint[] x = F.Create();
+            uint[] y = F.Create();
+
+            F.Inv(p.z, y);
+            F.Mul(p.x, y, x);
+            F.Mul(p.y, y, y);
+            F.Normalize(x);
+            F.Normalize(y);
+
+            int result = CheckPoint(x, y);
+
+            F.Encode(y, r);
+            r[PointBytes - 1] = (byte)((x[0] & 1) << 7);
+
+            return result;
+        }
+#endif
+
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
             random.NextBytes(k);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePrivateKey(SecureRandom random, Span<byte> k)
+        {
+            if (k.Length != SecretKeySize)
+                throw new ArgumentException(nameof(k));
+
+            random.NextBytes(k);
+        }
+#endif
+
         public static void GeneratePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff)
         {
             IXof d = CreateXof();
@@ -344,7 +409,27 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             ScalarMultBaseEncoded(s, pk, pkOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> sk, Span<byte> pk)
+        {
+            IXof d = CreateXof();
+            Span<byte> h = stackalloc byte[ScalarBytes * 2];
+
+            d.BlockUpdate(sk[..SecretKeySize]);
+            d.OutputFinal(h);
+
+            Span<byte> s = stackalloc byte[ScalarBytes];
+            PruneScalar(h, s);
+
+            ScalarMultBaseEncoded(s, pk);
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static uint GetWindow4(ReadOnlySpan<uint> x, int n)
+#else
         private static uint GetWindow4(uint[] x, int n)
+#endif
         {
             int w = (int)((uint)n >> 3), b = (n & 7) << 2;
             return (x[w] >> b) & 15U;
@@ -763,6 +848,30 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PointLookup(ReadOnlySpan<uint> x, int n, ReadOnlySpan<uint> table, ref PointProjective r)
+        {
+            // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points
+
+            uint w = GetWindow4(x, n);
+
+            int sign = (int)(w >> (4 - 1)) ^ 1;
+            int abs = ((int)w ^ -sign) & 7;
+
+            Debug.Assert(sign == 0 || sign == 1);
+            Debug.Assert(0 <= abs && abs < 8);
+
+            for (int i = 0; i < 8; ++i)
+            {
+                int cond = ((i ^ abs) - 1) >> 31;
+                F.CMov(cond, table, r.x);       table = table[F.Size..];
+                F.CMov(cond, table, r.y);       table = table[F.Size..];
+                F.CMov(cond, table, r.z);       table = table[F.Size..];
+            }
+
+            F.CNegate(sign, r.x);
+        }
+#else
         private static void PointLookup(uint[] x, int n, uint[] table, ref PointProjective r)
         {
             // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points
@@ -785,6 +894,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             F.CNegate(sign, r.x);
         }
+#endif
 
         private static void PointLookup15(uint[] table, ref PointProjective r)
         {
@@ -960,6 +1070,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             r[ScalarBytes - 1]  = 0x00;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void PruneScalar(ReadOnlySpan<byte> n, Span<byte> r)
+        {
+            n[..(ScalarBytes - 1)].CopyTo(r);
+
+            r[0] &= 0xFC;
+            r[ScalarBytes - 2] |= 0x80;
+            r[ScalarBytes - 1]  = 0x00;
+        }
+#endif
+
         private static byte[] ReduceScalar(byte[] n)
         {
             ulong x00 =  Decode32(n,   0);          // x00:32/--
@@ -1239,6 +1360,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static void ScalarMult(byte[] k, ref PointProjective p, ref PointProjective r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMult(k.AsSpan(), ref p, ref r);
+#else
             uint[] n = new uint[ScalarUints];
             DecodeScalar(k, 0, n);
 
@@ -1271,10 +1395,52 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                     PointDouble(ref r);
                 }
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMult(ReadOnlySpan<byte> k, ref PointProjective p, ref PointProjective r)
+        {
+            Span<uint> n = stackalloc uint[ScalarUints];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form
+            {
+                uint c1 = Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);
+                uint c2 = Nat.ShiftDownBit(ScalarUints, n, c1);             Debug.Assert(c2 == (1U << 31));
+
+                // NOTE: Bit 448 is implicitly set after the signed-digit recoding
+            }
+
+            uint[] table = PointPrecompute(ref p, 8);
+            Init(out PointProjective q);
+
+            // Replace first 4 doublings (2^4 * P) with 1 addition (P + 15 * P)
+            PointLookup15(table, ref r);
+            PointAdd(ref p, ref r);
+
+            int w = 111;
+            for (;;)
+            {
+                PointLookup(n, w, table, ref q);
+                PointAdd(ref q, ref r);
+
+                if (--w < 0)
+                    break;
+
+                for (int i = 0; i < 4; ++i)
+                {
+                    PointDouble(ref r);
+                }
+            }
+        }
+#endif
+
         private static void ScalarMultBase(byte[] k, ref PointProjective r)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ScalarMultBase(k.AsSpan(), ref r);
+#else
             // Equivalent (but much slower)
             //PointProjective p; Init(out p);
             //F.Copy(B_x, 0, p.x, 0);
@@ -1333,7 +1499,72 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
                 PointDouble(ref r);
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBase(ReadOnlySpan<byte> k, ref PointProjective r)
+        {
+            // Equivalent (but much slower)
+            //Init(out PointProjective p);
+            //F.Copy(B_x, 0, p.x, 0);
+            //F.Copy(B_y, 0, p.y, 0);
+            //F.One(p.z);
+            //ScalarMult(k, ref p, ref r);
+
+            Precompute();
+
+            Span<uint> n = stackalloc uint[ScalarUints + 1];
+            DecodeScalar(k, n);
+
+            // Recode the scalar into signed-digit form
+            {
+                n[ScalarUints] = (1U << (PrecompRange - 448))
+                               + Nat.CAdd(ScalarUints, ~(int)n[0] & 1, n, L, n);
+                uint c = Nat.ShiftDownBit(n.Length, n, 0);
+                Debug.Assert(c == (1U << 31));
+            }
+
+            Init(out PointAffine p);
+
+            PointSetNeutral(ref r);
+
+            int cOff = PrecompSpacing - 1;
+            for (;;)
+            {
+                int tPos = cOff;
+
+                for (int b = 0; b < PrecompBlocks; ++b)
+                {
+                    uint w = 0;
+                    for (int t = 0; t < PrecompTeeth; ++t)
+                    {
+                        uint tBit = n[tPos >> 5] >> (tPos & 0x1F);
+                        w &= ~(1U << t);
+                        w ^= (tBit << t);
+                        tPos += PrecompSpacing;
+                    }
+
+                    int sign = (int)(w >> (PrecompTeeth - 1)) & 1;
+                    int abs = ((int)w ^ -sign) & PrecompMask;
+
+                    Debug.Assert(sign == 0 || sign == 1);
+                    Debug.Assert(0 <= abs && abs < PrecompPoints);
+
+                    PointLookup(b, abs, ref p);
+
+                    F.CNegate(sign, p.x);
+
+                    PointAdd(ref p, ref r);
+                }
+
+                if (--cOff < 0)
+                    break;
+
+                PointDouble(ref r);
+            }
         }
+#endif
 
         private static void ScalarMultBaseEncoded(byte[] k, byte[] r, int rOff)
         {
@@ -1343,6 +1574,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 throw new InvalidOperationException();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void ScalarMultBaseEncoded(ReadOnlySpan<byte> k, Span<byte> r)
+        {
+            Init(out PointProjective p);
+            ScalarMultBase(k, ref p);
+            if (0 == EncodePoint(ref p, r))
+                throw new InvalidOperationException();
+        }
+#endif
+
         internal static void ScalarMultBaseXY(byte[] k, int kOff, uint[] x, uint[] y)
         {
             byte[] n = new byte[ScalarBytes];
@@ -1358,6 +1599,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.Copy(p.y, 0, y, 0);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void ScalarMultBaseXY(ReadOnlySpan<byte> k, Span<uint> x, Span<uint> y)
+        {
+            Span<byte> n = stackalloc byte[ScalarBytes];
+            PruneScalar(k, n);
+
+            Init(out PointProjective p);
+            ScalarMultBase(n, ref p);
+
+            if (0 == CheckPoint(p.x, p.y, p.z))
+                throw new InvalidOperationException();
+
+            F.Copy(p.x, x);
+            F.Copy(p.y, y);
+        }
+#endif
+
         private static void ScalarMultOrderVar(ref PointProjective p, ref PointProjective r)
         {
             sbyte[] ws_p = GetWnafVar(L, WnafWidth);
diff --git a/crypto/src/math/raw/Nat.cs b/crypto/src/math/raw/Nat.cs
index 8e5b7a04c..469d16d55 100644
--- a/crypto/src/math/raw/Nat.cs
+++ b/crypto/src/math/raw/Nat.cs
@@ -234,6 +234,22 @@ namespace Org.BouncyCastle.Math.Raw
             return (uint)c;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint CAdd(int len, int mask, ReadOnlySpan<uint> x, ReadOnlySpan<uint> y, Span<uint> z)
+        {
+            uint MASK = (uint)-(mask & 1);
+
+            ulong c = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                c += (ulong)x[i] + (y[i] & MASK);
+                z[i] = (uint)c;
+                c >>= 32;
+            }
+            return (uint)c;
+        }
+#endif
+
         public static void CMov(int len, int mask, uint[] x, int xOff, uint[] z, int zOff)
         {
             uint MASK = (uint)-(mask & 1);
@@ -839,6 +855,20 @@ namespace Org.BouncyCastle.Math.Raw
             return c << 31;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static uint ShiftDownBit(int len, Span<uint> z, uint c)
+        {
+            int i = len;
+            while (--i >= 0)
+            {
+                uint next = z[i];
+                z[i] = (next >> 1) | (c << 31);
+                c = next;
+            }
+            return c << 31;
+        }
+#endif
+
         public static uint ShiftDownBit(int len, uint[] z, int zOff, uint c)
         {
             int i = len;