summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-11-26 22:12:14 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-11-26 22:12:14 +0700
commit11529c83fbdd81f9a25658ec8d3c526618576be6 (patch)
tree517803479099edff45dedc6799fe184b0f9952c1
parentRefactoring in Math.EC.Rfc8032 (diff)
downloadBouncyCastle.NET-ed25519-11529c83fbdd81f9a25658ec8d3c526618576be6.tar.xz
EdDSA: Hold decoded pubilc point in public keys
-rw-r--r--crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs9
-rw-r--r--crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs43
-rw-r--r--crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs9
-rw-r--r--crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs41
-rw-r--r--crypto/src/math/ec/rfc7748/X25519Field.cs6
-rw-r--r--crypto/src/math/ec/rfc7748/X448Field.cs12
-rw-r--r--crypto/src/math/ec/rfc8032/Ed25519.cs444
-rw-r--r--crypto/src/math/ec/rfc8032/Ed448.cs499
-rw-r--r--crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs99
-rw-r--r--crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs70
10 files changed, 1057 insertions, 175 deletions
diff --git a/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs b/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
index 2bd6445b8..161dedb6d 100644
--- a/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed25519PrivateKeyParameters.cs
@@ -77,14 +77,11 @@ 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);
+                    Ed25519.GeneratePublicKey(data, out var publicPoint);
 #else
-                    byte[] publicKey = new byte[Ed25519.PublicKeySize];
-                    Ed25519.GeneratePublicKey(data, 0, publicKey, 0);
-                    cachedPublicKey = new Ed25519PublicKeyParameters(publicKey, 0);
+                    Ed25519.GeneratePublicKey(data, 0, out var publicPoint);
 #endif
+                    cachedPublicKey = new Ed25519PublicKeyParameters(publicPoint);
                 }
 
                 return cachedPublicKey;
diff --git a/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs b/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
index a454754f5..57c63e624 100644
--- a/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed25519PublicKeyParameters.cs
@@ -2,7 +2,6 @@
 using System.IO;
 
 using Org.BouncyCastle.Math.EC.Rfc8032;
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.Parameters
@@ -12,7 +11,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
     {
         public static readonly int KeySize = Ed25519.PublicKeySize;
 
-        private readonly byte[] data = new byte[KeySize];
+        private readonly Ed25519.PublicPoint m_publicPoint;
 
         public Ed25519PublicKeyParameters(byte[] buf)
             : this(Validate(buf), 0)
@@ -22,7 +21,8 @@ namespace Org.BouncyCastle.Crypto.Parameters
         public Ed25519PublicKeyParameters(byte[] buf, int off)
             : base(false)
         {
-            Array.Copy(buf, off, data, 0, KeySize);
+            if (!Ed25519.ValidatePublicKeyPartial(buf, off, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
@@ -32,32 +32,55 @@ namespace Org.BouncyCastle.Crypto.Parameters
             if (buf.Length != KeySize)
                 throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
-            buf.CopyTo(data);
+            if (!Ed25519.ValidatePublicKeyPartial(buf, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
         }
 #endif
 
         public Ed25519PublicKeyParameters(Stream input)
             : base(false)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> data = stackalloc byte[KeySize];
+#else
+            byte[] data = new byte[KeySize];
+#endif
+
             if (KeySize != Streams.ReadFully(input, data))
                 throw new EndOfStreamException("EOF encountered in middle of Ed25519 public key");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (!Ed25519.ValidatePublicKeyPartial(data, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
+#else
+            if (!Ed25519.ValidatePublicKeyPartial(data, 0, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
+#endif
+        }
+
+        public Ed25519PublicKeyParameters(Ed25519.PublicPoint publicPoint)
+            : base(false)
+        {
+            m_publicPoint = publicPoint ?? throw new ArgumentNullException(nameof(publicPoint));
         }
 
         public void Encode(byte[] buf, int off)
         {
-            Array.Copy(data, 0, buf, off, KeySize);
+            Ed25519.EncodePublicPoint(m_publicPoint, buf, off);
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public void Encode(Span<byte> buf)
         {
-            data.CopyTo(buf);
+            Ed25519.EncodePublicPoint(m_publicPoint, buf);
         }
 #endif
 
         public byte[] GetEncoded()
         {
-            return Arrays.Clone(data);
+            byte[] data = new byte[KeySize];
+            Encode(data, 0);
+            return data;
         }
 
         public bool Verify(Ed25519.Algorithm algorithm, byte[] ctx, byte[] msg, int msgOff, int msgLen,
@@ -70,7 +93,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
                 if (null != ctx)
                     throw new ArgumentOutOfRangeException(nameof(ctx));
 
-                return Ed25519.Verify(sig, sigOff, data, 0, msg, msgOff, msgLen);
+                return Ed25519.Verify(sig, sigOff, m_publicPoint, msg, msgOff, msgLen);
             }
             case Ed25519.Algorithm.Ed25519ctx:
             {
@@ -79,7 +102,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
                 if (ctx.Length > 255)
                     throw new ArgumentOutOfRangeException(nameof(ctx));
 
-                return Ed25519.Verify(sig, sigOff, data, 0, ctx, msg, msgOff, msgLen);
+                return Ed25519.Verify(sig, sigOff, m_publicPoint, ctx, msg, msgOff, msgLen);
             }
             case Ed25519.Algorithm.Ed25519ph:
             {
@@ -90,7 +113,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
                 if (Ed25519.PrehashSize != msgLen)
                     throw new ArgumentOutOfRangeException(nameof(msgLen));
 
-                return Ed25519.VerifyPrehash(sig, sigOff, data, 0, ctx, msg, msgOff);
+                return Ed25519.VerifyPrehash(sig, sigOff, m_publicPoint, ctx, msg, msgOff);
             }
             default:
             {
diff --git a/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs b/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
index 9f442b21a..b80b68529 100644
--- a/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed448PrivateKeyParameters.cs
@@ -77,14 +77,11 @@ 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);
+                    Ed448.GeneratePublicKey(data, out var publicPoint);
 #else
-                    byte[] publicKey = new byte[Ed448.PublicKeySize];
-                    Ed448.GeneratePublicKey(data, 0, publicKey, 0);
-                    cachedPublicKey = new Ed448PublicKeyParameters(publicKey, 0);
+                    Ed448.GeneratePublicKey(data, 0, out var publicPoint);
 #endif
+                    cachedPublicKey = new Ed448PublicKeyParameters(publicPoint);
                 }
 
                 return cachedPublicKey;
diff --git a/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs b/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
index d2ef7c891..0f07fa783 100644
--- a/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
+++ b/crypto/src/crypto/parameters/Ed448PublicKeyParameters.cs
@@ -2,7 +2,6 @@
 using System.IO;
 
 using Org.BouncyCastle.Math.EC.Rfc8032;
-using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.Parameters
@@ -12,7 +11,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
     {
         public static readonly int KeySize = Ed448.PublicKeySize;
 
-        private readonly byte[] data = new byte[KeySize];
+        private readonly Ed448.PublicPoint m_publicPoint;
 
         public Ed448PublicKeyParameters(byte[] buf)
             : this(Validate(buf), 0)
@@ -22,7 +21,8 @@ namespace Org.BouncyCastle.Crypto.Parameters
         public Ed448PublicKeyParameters(byte[] buf, int off)
             : base(false)
         {
-            Array.Copy(buf, off, data, 0, KeySize);
+            if (!Ed448.ValidatePublicKeyPartial(buf, off, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
@@ -32,32 +32,55 @@ namespace Org.BouncyCastle.Crypto.Parameters
             if (buf.Length != KeySize)
                 throw new ArgumentException("must have length " + KeySize, nameof(buf));
 
-            buf.CopyTo(data);
+            if (!Ed448.ValidatePublicKeyPartial(buf, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
         }
 #endif
 
         public Ed448PublicKeyParameters(Stream input)
             : base(false)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> data = stackalloc byte[KeySize];
+#else
+            byte[] data = new byte[KeySize];
+#endif
+
             if (KeySize != Streams.ReadFully(input, data))
                 throw new EndOfStreamException("EOF encountered in middle of Ed448 public key");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (!Ed448.ValidatePublicKeyPartial(data, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
+#else
+            if (!Ed448.ValidatePublicKeyPartial(data, 0, out m_publicPoint))
+                throw new ArgumentException("invalid public key");
+#endif
+        }
+
+        public Ed448PublicKeyParameters(Ed448.PublicPoint publicPoint)
+            : base(false)
+        {
+            m_publicPoint = publicPoint ?? throw new ArgumentNullException(nameof(publicPoint));
         }
 
         public void Encode(byte[] buf, int off)
         {
-            Array.Copy(data, 0, buf, off, KeySize);
+            Ed448.EncodePublicPoint(m_publicPoint, buf, off);
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public void Encode(Span<byte> buf)
         {
-            data.CopyTo(buf);
+            Ed448.EncodePublicPoint(m_publicPoint, buf);
         }
 #endif
 
         public byte[] GetEncoded()
         {
-            return Arrays.Clone(data);
+            byte[] data = new byte[KeySize];
+            Encode(data, 0);
+            return data;
         }
 
         public bool Verify(Ed448.Algorithm algorithm, byte[] ctx, byte[] msg, int msgOff, int msgLen,
@@ -72,7 +95,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
                 if (ctx.Length > 255)
                     throw new ArgumentOutOfRangeException(nameof(ctx));
 
-                return Ed448.Verify(sig, sigOff, data, 0, ctx, msg, msgOff, msgLen);
+                return Ed448.Verify(sig, sigOff, m_publicPoint, ctx, msg, msgOff, msgLen);
             }
             case Ed448.Algorithm.Ed448ph:
             {
@@ -83,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Parameters
                 if (Ed448.PrehashSize != msgLen)
                     throw new ArgumentOutOfRangeException(nameof(msgLen));
 
-                return Ed448.VerifyPrehash(sig, sigOff, data, 0, ctx, msg, msgOff);
+                return Ed448.VerifyPrehash(sig, sigOff, m_publicPoint, ctx, msg, msgOff);
             }
             default:
             {
diff --git a/crypto/src/math/ec/rfc7748/X25519Field.cs b/crypto/src/math/ec/rfc7748/X25519Field.cs
index 2504592aa..1c0b928f7 100644
--- a/crypto/src/math/ec/rfc7748/X25519Field.cs
+++ b/crypto/src/math/ec/rfc7748/X25519Field.cs
@@ -444,6 +444,12 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Encode128(x, 5, z, zOff + 16);
         }
 
+        public static void Encode(int[] x, int xOff, byte[] z, int zOff)
+        {
+            Encode128(x, xOff, z, zOff);
+            Encode128(x, xOff + 5, z, zOff + 16);
+        }
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public static void Encode(ReadOnlySpan<int> x, Span<byte> z)
         {
diff --git a/crypto/src/math/ec/rfc7748/X448Field.cs b/crypto/src/math/ec/rfc7748/X448Field.cs
index 7169bd6d8..d812172bb 100644
--- a/crypto/src/math/ec/rfc7748/X448Field.cs
+++ b/crypto/src/math/ec/rfc7748/X448Field.cs
@@ -405,6 +405,18 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Encode56(x, 14, z, zOff + 49);
         }
 
+        public static void Encode(uint[] x, int xOff, byte[] z, int zOff)
+        {
+            Encode56(x, xOff, z, zOff);
+            Encode56(x, xOff + 2, z, zOff + 7);
+            Encode56(x, xOff + 4, z, zOff + 14);
+            Encode56(x, xOff + 6, z, zOff + 21);
+            Encode56(x, xOff + 8, z, zOff + 28);
+            Encode56(x, xOff + 10, z, zOff + 35);
+            Encode56(x, xOff + 12, z, zOff + 42);
+            Encode56(x, xOff + 14, z, zOff + 49);
+        }
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public static void Encode(ReadOnlySpan<uint> x, Span<byte> z)
         {
diff --git a/crypto/src/math/ec/rfc8032/Ed25519.cs b/crypto/src/math/ec/rfc8032/Ed25519.cs
index c1d8fa624..2479d94d4 100644
--- a/crypto/src/math/ec/rfc8032/Ed25519.cs
+++ b/crypto/src/math/ec/rfc8032/Ed25519.cs
@@ -34,6 +34,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Ed25519ph = 2,
         }
 
+        public sealed class PublicPoint
+        {
+            internal readonly int[] m_data;
+
+            internal PublicPoint(int[] data)
+            {
+                m_data = data;
+            }
+        }
+
         private const int CoordUints = 8;
         private const int PointBytes = CoordUints * 4;
         private const int ScalarUints = 8;
@@ -151,14 +161,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 || ctx != null && ctx.Length < 256;
         }
 
-        private static int CheckPoint(int[] x, int[] y)
+        private static int CheckPoint(ref PointAffine p)
         {
             int[] t = F.Create();
             int[] u = F.Create();
             int[] v = F.Create();
 
-            F.Sqr(x, u);
-            F.Sqr(y, v);
+            F.Sqr(p.x, u);
+            F.Sqr(p.y, v);
             F.Mul(u, v, t);
             F.Sub(v, u, v);
             F.Mul(t, C_d, t);
@@ -191,6 +201,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return F.IsZero(t);
         }
 
+        private static bool CheckPointOrderVar(ref PointAffine p)
+        {
+            Init(out PointAccum r);
+            ScalarMultOrderVar(ref p, ref r);
+            return NormalizeToNeutralElementVar(ref r);
+        }
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         private static bool CheckPointVar(ReadOnlySpan<byte> p)
         {
@@ -339,6 +356,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (negate ^ (x_0 != (r.x[0] & 1)))
             {
                 F.Negate(r.x, r.x);
+                F.Normalize(r.x);
             }
 
             return true;
@@ -369,50 +387,73 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 #endif
         }
 
-        private static int EncodePoint(ref PointAccum p, byte[] r, int rOff)
+        private static void EncodePoint(ref PointAffine p, byte[] r, int rOff)
         {
+            F.Encode(p.y, r, rOff);
+            r[rOff + PointBytes - 1] |= (byte)((p.x[0] & 1) << 7);
+        }
+
 #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();
+        private static void EncodePoint(ref PointAffine p, Span<byte> r)
+        {
+            F.Encode(p.y, r);
+            r[PointBytes - 1] |= (byte)((p.x[0] & 1) << 7);
+        }
+#endif
+
+        public static void EncodePublicPoint(PublicPoint publicPoint, byte[] pk, int pkOff)
+        {
+            F.Encode(publicPoint.m_data, F.Size, pk, pkOff);
+            pk[pkOff + PointBytes - 1] |= (byte)((publicPoint.m_data[0] & 1) << 7);
+        }
 
-            F.Inv(p.z, y);
-            F.Mul(p.x, y, x);
-            F.Mul(p.y, y, y);
-            F.Normalize(x);
-            F.Normalize(y);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void EncodePublicPoint(PublicPoint publicPoint, Span<byte> pk)
+        {
+            F.Encode(publicPoint.m_data.AsSpan(F.Size), pk);
+            pk[PointBytes - 1] |= (byte)((publicPoint.m_data[0] & 1) << 7);
+        }
+#endif
+
+        private static int EncodeResult(ref PointAccum p, byte[] r, int rOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodeResult(ref p, r.AsSpan(rOff));
+#else
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
 
-            int result = CheckPoint(x, y);
+            int result = CheckPoint(ref q);
 
-            F.Encode(y, r, rOff);
-            r[rOff + PointBytes - 1] |= (byte)((x[0] & 1) << 7);
+            EncodePoint(ref q, r, rOff);
 
             return result;
 #endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        private static int EncodePoint(ref PointAccum p, Span<byte> r)
+        private static int EncodeResult(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);
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
 
-            int result = CheckPoint(x, y);
+            int result = CheckPoint(ref q);
 
-            F.Encode(y, r);
-            r[PointBytes - 1] |= (byte)((x[0] & 1) << 7);
+            EncodePoint(ref q, r);
 
             return result;
         }
 #endif
 
+        private static void ExportPoint(ref PointAffine p, out PublicPoint publicPoint)
+        {
+            int[] data = new int[F.Size * 2];
+            F.Copy(p.x, 0, data, 0);
+            F.Copy(p.y, 0, data, F.Size);
+
+            publicPoint = new PublicPoint(data);
+        }
+
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
             if (k.Length != SecretKeySize)
@@ -465,6 +506,58 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         }
 #endif
 
+        public static void GeneratePublicKey(byte[] sk, int skOff, out PublicPoint publicPoint)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GeneratePublicKey(sk.AsSpan(skOff), out publicPoint);
+#else
+            IDigest d = CreateDigest();
+            byte[] h = new byte[64];
+
+            d.BlockUpdate(sk, skOff, SecretKeySize);
+            d.DoFinal(h, 0);
+
+            byte[] s = new byte[ScalarBytes];
+            PruneScalar(h, 0, s);
+
+            Init(out PointAccum p);
+            ScalarMultBase(s, ref p);
+
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
+
+            if (0 == CheckPoint(ref q))
+                throw new InvalidOperationException();
+
+            ExportPoint(ref q, out publicPoint);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> sk, out PublicPoint publicPoint)
+        {
+            IDigest d = CreateDigest();
+            Span<byte> h = stackalloc byte[64];
+
+            d.BlockUpdate(sk[..SecretKeySize]);
+            d.DoFinal(h);
+
+            Span<byte> s = stackalloc byte[ScalarBytes];
+            PruneScalar(h, s);
+
+            Init(out PointAccum p);
+            ScalarMultBase(s, ref p);
+
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
+
+            if (0 == CheckPoint(ref q))
+                throw new InvalidOperationException();
+
+            ExportPoint(ref q, out publicPoint);
+        }
+#endif
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         private static uint GetWindow4(ReadOnlySpan<uint> x, int n)
 #else
@@ -663,12 +756,108 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             Init(out PointAccum pZ);
             ScalarMultStraus128Var(nS, v0, ref pA, v1, ref pR, ref pZ);
+            return NormalizeToNeutralElementVar(ref pZ);
+        }
+
+        private static bool ImplVerify(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen)
+        {
+            if (!CheckContextVar(ctx, phflag))
+                throw new ArgumentException("ctx");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> signature = stackalloc byte[SignatureSize];
+            signature.CopyFrom(sig.AsSpan(sigOff, SignatureSize));
+            var R = signature[..PointBytes];
+            var S = signature[PointBytes..];
+
+            if (!CheckPointVar(R))
+                return false;
+
+            Span<uint> nS = stackalloc uint[ScalarUints];
+            if (!Scalar25519.CheckVar(S, nS))
+                return false;
+
+            Init(out PointAffine pR);
+            if (!DecodePointVar(R, true, ref pR))
+                return false;
+
+            Init(out PointAffine pA);
+            F.Negate(publicPoint.m_data, pA.x);
+            F.Copy(publicPoint.m_data.AsSpan(F.Size), pA.y);
+
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            EncodePublicPoint(publicPoint, A);
 
-            F.Normalize(pZ.x);
-            F.Normalize(pZ.y);
-            F.Normalize(pZ.z);
+            IDigest d = CreateDigest();
+            Span<byte> h = stackalloc byte[64];
 
-            return IsNeutralElementVar(pZ.x, pZ.y, pZ.z);
+            if (ctx != null)
+            {
+                Dom2(d, phflag, ctx);
+            }
+            d.BlockUpdate(R);
+            d.BlockUpdate(A);
+            d.BlockUpdate(m.AsSpan(mOff, mLen));
+            d.DoFinal(h);
+
+            Span<byte> k = stackalloc byte[ScalarBytes];
+            Scalar25519.Reduce(h, k);
+
+            Span<uint> nA = stackalloc uint[ScalarUints];
+            Scalar25519.Decode(k, nA);
+
+            Span<uint> v0 = stackalloc uint[4];
+            Span<uint> v1 = stackalloc uint[4];
+#else
+            byte[] R = Copy(sig, sigOff, PointBytes);
+            byte[] S = Copy(sig, sigOff + PointBytes, ScalarBytes);
+
+            if (!CheckPointVar(R))
+                return false;
+
+            uint[] nS = new uint[ScalarUints];
+            if (!Scalar25519.CheckVar(S, nS))
+                return false;
+
+            Init(out PointAffine pR);
+            if (!DecodePointVar(R, true, ref pR))
+                return false;
+
+            Init(out PointAffine pA);
+            F.Negate(publicPoint.m_data, pA.x);
+            F.Copy(publicPoint.m_data, F.Size, pA.y, 0);
+
+            byte[] A = new byte[PublicKeySize];
+            EncodePublicPoint(publicPoint, A, 0);
+
+            IDigest d = CreateDigest();
+            byte[] h = new byte[64];
+
+            if (ctx != null)
+            {
+                Dom2(d, phflag, ctx);
+            }
+            d.BlockUpdate(R, 0, PointBytes);
+            d.BlockUpdate(A, 0, PointBytes);
+            d.BlockUpdate(m, mOff, mLen);
+            d.DoFinal(h, 0);
+
+            byte[] k = Scalar25519.Reduce(h);
+
+            uint[] nA = new uint[ScalarUints];
+            Scalar25519.Decode(k, nA);
+
+            uint[] v0 = new uint[4];
+            uint[] v1 = new uint[4];
+#endif
+
+            Scalar25519.ReduceBasisVar(nA, v0, v1);
+            Scalar25519.Multiply128Var(nS, v1, nS);
+
+            Init(out PointAccum pZ);
+            ScalarMultStraus128Var(nS, v0, ref pA, v1, ref pR, ref pZ);
+            return NormalizeToNeutralElementVar(ref pZ);
         }
 
         private static void Init(out PointAccum r)
@@ -759,6 +948,24 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return F.IsZeroVar(x) && F.AreEqualVar(y, z);
         }
 
+        private static void NormalizeToAffine(ref PointAccum p, ref PointAffine r)
+        {
+            F.Inv(p.z, r.y);
+            F.Mul(r.y, p.x, r.x);
+            F.Mul(r.y, p.y, r.y);
+            F.Normalize(r.x);
+            F.Normalize(r.y);
+        }
+
+        private static bool NormalizeToNeutralElementVar(ref PointAccum p)
+        {
+            F.Normalize(p.x);
+            F.Normalize(p.y);
+            F.Normalize(p.z);
+
+            return IsNeutralElementVar(p.x, p.y, p.z);
+        }
+
         private static void PointAdd(ref PointExtended p, ref PointExtended q, ref PointExtended r, ref PointTemp t)
         {
             // p may ref the same point as r (or q), but q may not ref the same point as r.
@@ -1143,6 +1350,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 {
                     Init(out toothPowers[tooth]);
                 }
+
                 Init(out PointExtended u);
                 for (int block = 0; block < PrecompBlocks; ++block)
                 {
@@ -1393,7 +1601,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 #else
             Init(out PointAccum p);
             ScalarMultBase(k, ref p);
-            if (0 == EncodePoint(ref p, r, rOff))
+            if (0 == EncodeResult(ref p, r, rOff))
                 throw new InvalidOperationException();
 #endif
         }
@@ -1403,7 +1611,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             Init(out PointAccum p);
             ScalarMultBase(k, ref p);
-            if (0 == EncodePoint(ref p, r))
+            if (0 == EncodeResult(ref p, r))
                 throw new InvalidOperationException();
         }
 #endif
@@ -1624,11 +1832,26 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static bool ValidatePublicKeyFull(byte[] pk, int pkOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            Span<byte> A = stackalloc byte[PublicKeySize];
-            A.CopyFrom(pk.AsSpan(pkOff));
+            return ValidatePublicKeyFull(pk.AsSpan(pkOff));
 #else
             byte[] A = Copy(pk, pkOff, PublicKeySize);
+
+            if (!CheckPointFullVar(A))
+                return false;
+
+            Init(out PointAffine pA);
+            if (!DecodePointVar(A, false, ref pA))
+                return false;
+
+            return CheckPointOrderVar(ref pA);
 #endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyFull(ReadOnlySpan<byte> pk)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
 
             if (!CheckPointFullVar(A))
                 return false;
@@ -1637,24 +1860,79 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!DecodePointVar(A, false, ref pA))
                 return false;
 
-            Init(out PointAccum pR);
-            ScalarMultOrderVar(ref pA, ref pR);
+            return CheckPointOrderVar(ref pA);
+        }
+#endif
 
-            F.Normalize(pR.x);
-            F.Normalize(pR.y);
-            F.Normalize(pR.z);
+        public static bool ValidatePublicKeyFull(byte[] pk, int pkOff, out PublicPoint publicPoint)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ValidatePublicKeyFull(pk.AsSpan(pkOff), out publicPoint);
+#else
+            byte[] A = Copy(pk, pkOff, PublicKeySize);
 
-            return IsNeutralElementVar(pR.x, pR.y, pR.z);
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    if (CheckPointOrderVar(ref pA))
+                    {
+                        ExportPoint(ref pA, out publicPoint);
+                        return true;
+                    }
+                }
+            }
+
+            publicPoint = null;
+            return false;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyFull(ReadOnlySpan<byte> pk, out PublicPoint publicPoint)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
+
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    if (CheckPointOrderVar(ref pA))
+                    {
+                        ExportPoint(ref pA, out publicPoint);
+                        return true;
+                    }
+                }
+            }
+
+            publicPoint = null;
+            return false;
+        }
+#endif
+
         public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            Span<byte> A = stackalloc byte[PublicKeySize];
-            A.CopyFrom(pk.AsSpan(pkOff));
+            return ValidatePublicKeyPartial(pk.AsSpan(pkOff));
 #else
             byte[] A = Copy(pk, pkOff, PublicKeySize);
+
+            if (!CheckPointFullVar(A))
+                return false;
+
+            Init(out PointAffine pA);
+            return DecodePointVar(A, false, ref pA);
 #endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyPartial(ReadOnlySpan<byte> pk)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
 
             if (!CheckPointFullVar(A))
                 return false;
@@ -1662,6 +1940,50 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Init(out PointAffine pA);
             return DecodePointVar(A, false, ref pA);
         }
+#endif
+
+        public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff, out PublicPoint publicPoint)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ValidatePublicKeyPartial(pk.AsSpan(pkOff), out publicPoint);
+#else
+            byte[] A = Copy(pk, pkOff, PublicKeySize);
+
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    ExportPoint(ref pA, out publicPoint);
+                    return true;
+                }
+            }
+
+            publicPoint = null;
+            return false;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyPartial(ReadOnlySpan<byte> pk, out PublicPoint publicPoint)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
+
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    ExportPoint(ref pA, out publicPoint);
+                    return true;
+                }
+            }
+
+            publicPoint = null;
+            return false;
+        }
+#endif
 
         public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen)
         {
@@ -1671,6 +1993,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen);
         }
 
+        public static bool Verify(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] m, int mOff, int mLen)
+        {
+            byte[] ctx = null;
+            byte phflag = 0x00;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, m, mOff, mLen);
+        }
+
         public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen)
         {
             byte phflag = 0x00;
@@ -1678,6 +2008,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen);
         }
 
+        public static bool Verify(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, byte[] m, int mOff, int mLen)
+        {
+            byte phflag = 0x00;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, m, mOff, mLen);
+        }
+
         public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff)
         {
             byte phflag = 0x01;
@@ -1685,6 +2022,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, ph, phOff, PrehashSize);
         }
 
+        public static bool VerifyPrehash(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, byte[] ph,
+            int phOff)
+        {
+            byte phflag = 0x01;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, ph, phOff, PrehashSize);
+        }
+
         public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, IDigest ph)
         {
             byte[] m = new byte[PrehashSize];
@@ -1695,5 +2040,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, 0, m.Length);
         }
+
+        public static bool VerifyPrehash(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, IDigest ph)
+        {
+            byte[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.DoFinal(m, 0))
+                throw new ArgumentException("ph");
+
+            byte phflag = 0x01;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, m, 0, m.Length);
+        }
     }
 }
diff --git a/crypto/src/math/ec/rfc8032/Ed448.cs b/crypto/src/math/ec/rfc8032/Ed448.cs
index d2c29a41c..8ffbccfa4 100644
--- a/crypto/src/math/ec/rfc8032/Ed448.cs
+++ b/crypto/src/math/ec/rfc8032/Ed448.cs
@@ -31,6 +31,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Ed448ph = 1,
         }
 
+        public sealed class PublicPoint
+        {
+            internal readonly uint[] m_data;
+
+            internal PublicPoint(uint[] data)
+            {
+                m_data = data;
+            }
+        }
+
         private const int CoordUints = 14;
         private const int PointBytes = CoordUints * 4 + 1;
         private const int ScalarUints = 14;
@@ -113,14 +123,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ctx != null && ctx.Length < 256;
         }
 
-        private static int CheckPoint(uint[] x, uint[] y)
+        private static int CheckPoint(ref PointAffine p)
         {
             uint[] t = F.Create();
             uint[] u = F.Create();
             uint[] v = F.Create();
 
-            F.Sqr(x, u);
-            F.Sqr(y, v);
+            F.Sqr(p.x, u);
+            F.Sqr(p.y, v);
             F.Mul(u, v, t);
             F.Add(u, v, u);
             F.Mul(t, -C_d, t);
@@ -153,6 +163,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return F.IsZero(t);
         }
 
+        private static bool CheckPointOrderVar(ref PointAffine p)
+        {
+            Init(out PointProjective r);
+            ScalarMultOrderVar(ref p, ref r);
+            return NormalizeToNeutralElementVar(ref r);
+        }
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         private static bool CheckPointVar(ReadOnlySpan<byte> p)
         {
@@ -277,9 +294,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        private static bool DecodePointVar(ReadOnlySpan<byte> p, bool negate, ref PointProjective r)
+        private static bool DecodePointVar(ReadOnlySpan<byte> p, bool negate, ref PointAffine r)
 #else
-        private static bool DecodePointVar(byte[] p, bool negate, ref PointProjective r)
+        private static bool DecodePointVar(byte[] p, bool negate, ref PointAffine r)
 #endif
         {
             int x_0 = (p[PointBytes - 1] & 0x80) >> 7;
@@ -305,9 +322,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (negate ^ (x_0 != (r.x[0] & 1)))
             {
                 F.Negate(r.x, r.x);
+                F.Normalize(r.x);
             }
 
-            F.One(r.z);
             return true;
         }
 
@@ -334,46 +351,73 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 #endif
         }
 
-        private static int EncodePoint(ref PointProjective p, byte[] r, int rOff)
+        private static void EncodePoint(ref PointAffine p, byte[] r, int rOff)
         {
-            uint[] x = F.Create();
-            uint[] y = F.Create();
+            F.Encode(p.y, r, rOff);
+            r[rOff + PointBytes - 1] = (byte)((p.x[0] & 1) << 7);
+        }
 
-            F.Inv(p.z, y);
-            F.Mul(p.x, y, x);
-            F.Mul(p.y, y, y);
-            F.Normalize(x);
-            F.Normalize(y);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void EncodePoint(ref PointAffine p, Span<byte> r)
+        {
+            F.Encode(p.y, r);
+            r[PointBytes - 1] = (byte)((p.x[0] & 1) << 7);
+        }
+#endif
 
-            int result = CheckPoint(x, y);
+        public static void EncodePublicPoint(PublicPoint publicPoint, byte[] pk, int pkOff)
+        {
+            F.Encode(publicPoint.m_data, F.Size, pk, pkOff);
+            pk[pkOff + PointBytes - 1] = (byte)((publicPoint.m_data[0] & 1) << 7);
+        }
 
-            F.Encode(y, r, rOff);
-            r[rOff + PointBytes - 1] = (byte)((x[0] & 1) << 7);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void EncodePublicPoint(PublicPoint publicPoint, Span<byte> pk)
+        {
+            F.Encode(publicPoint.m_data.AsSpan(F.Size), pk);
+            pk[PointBytes - 1] = (byte)((publicPoint.m_data[0] & 1) << 7);
+        }
+#endif
+
+        private static int EncodeResult(ref PointProjective p, byte[] r, int rOff)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return EncodeResult(ref p, r.AsSpan(rOff));
+#else
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
+
+            int result = CheckPoint(ref q);
+
+            EncodePoint(ref q, r, rOff);
 
             return result;
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        private static int EncodePoint(ref PointProjective p, Span<byte> r)
+        private static int EncodeResult(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);
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
 
-            int result = CheckPoint(x, y);
+            int result = CheckPoint(ref q);
 
-            F.Encode(y, r);
-            r[PointBytes - 1] = (byte)((x[0] & 1) << 7);
+            EncodePoint(ref q, r);
 
             return result;
         }
 #endif
 
+        private static void ExportPoint(ref PointAffine p, out PublicPoint publicPoint)
+        {
+            uint[] data = new uint[F.Size * 2];
+            F.Copy(p.x, 0, data, 0);
+            F.Copy(p.y, 0, data, F.Size);
+
+            publicPoint = new PublicPoint(data);
+        }
+
         public static void GeneratePrivateKey(SecureRandom random, byte[] k)
         {
             if (k.Length != SecretKeySize)
@@ -426,6 +470,58 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         }
 #endif
 
+        public static void GeneratePublicKey(byte[] sk, int skOff, out PublicPoint publicPoint)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            GeneratePublicKey(sk.AsSpan(skOff), out publicPoint);
+#else
+            IXof d = CreateXof();
+            byte[] h = new byte[ScalarBytes * 2];
+
+            d.BlockUpdate(sk, skOff, SecretKeySize);
+            d.OutputFinal(h, 0, h.Length);
+
+            byte[] s = new byte[ScalarBytes];
+            PruneScalar(h, 0, s);
+
+            Init(out PointProjective p);
+            ScalarMultBase(s, ref p);
+
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
+
+            if (0 == CheckPoint(ref q))
+                throw new InvalidOperationException();
+
+            ExportPoint(ref q, out publicPoint);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static void GeneratePublicKey(ReadOnlySpan<byte> sk, out PublicPoint publicPoint)
+        {
+            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);
+
+            Init(out PointProjective p);
+            ScalarMultBase(s, ref p);
+
+            Init(out PointAffine q);
+            NormalizeToAffine(ref p, ref q);
+
+            if (0 == CheckPoint(ref q))
+                throw new InvalidOperationException();
+
+            ExportPoint(ref q, out publicPoint);
+        }
+#endif
+
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         private static uint GetWindow4(ReadOnlySpan<uint> x, int n)
 #else
@@ -525,11 +621,11 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!CheckPointFullVar(A))
                 return false;
 
-            Init(out PointProjective pR);
+            Init(out PointAffine pR);
             if (!DecodePointVar(R, true, ref pR))
                 return false;
 
-            Init(out PointProjective pA);
+            Init(out PointAffine pA);
             if (!DecodePointVar(A, true, ref pA))
                 return false;
 
@@ -565,11 +661,11 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!CheckPointFullVar(A))
                 return false;
 
-            Init(out PointProjective pR);
+            Init(out PointAffine pR);
             if (!DecodePointVar(R, true, ref pR))
                 return false;
 
-            Init(out PointProjective pA);
+            Init(out PointAffine pA);
             if (!DecodePointVar(A, true, ref pA))
                 return false;
 
@@ -596,12 +692,102 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             Init(out PointProjective pZ);
             ScalarMultStraus225Var(nS, v0, ref pA, v1, ref pR, ref pZ);
+            return NormalizeToNeutralElementVar(ref pZ);
+        }
+
+        private static bool ImplVerify(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen)
+        {
+            if (!CheckContextVar(ctx))
+                throw new ArgumentException("ctx");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> signature = stackalloc byte[SignatureSize];
+            signature.CopyFrom(sig.AsSpan(sigOff, SignatureSize));
+            var R = signature[..PointBytes];
+            var S = signature[PointBytes..];
+
+            if (!CheckPointVar(R))
+                return false;
+
+            Span<uint> nS = stackalloc uint[ScalarUints];
+            if (!Scalar448.CheckVar(S, nS))
+                return false;
+
+            Init(out PointAffine pR);
+            if (!DecodePointVar(R, true, ref pR))
+                return false;
+
+            Init(out PointAffine pA);
+            F.Negate(publicPoint.m_data, pA.x);
+            F.Copy(publicPoint.m_data.AsSpan(F.Size), pA.y);
+
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            EncodePublicPoint(publicPoint, A);
+
+            IXof d = CreateXof();
+            Span<byte> h = stackalloc byte[ScalarBytes * 2];
+
+            Dom4(d, phflag, ctx);
+            d.BlockUpdate(R);
+            d.BlockUpdate(A);
+            d.BlockUpdate(m.AsSpan(mOff, mLen));
+            d.OutputFinal(h);
+
+            Span<byte> k = stackalloc byte[ScalarBytes];
+            Scalar448.Reduce(h, k);
+
+            Span<uint> nA = stackalloc uint[ScalarUints];
+            Scalar448.Decode(k, nA);
+
+            Span<uint> v0 = stackalloc uint[8];
+            Span<uint> v1 = stackalloc uint[8];
+#else
+            byte[] R = Copy(sig, sigOff, PointBytes);
+            byte[] S = Copy(sig, sigOff + PointBytes, ScalarBytes);
+
+            if (!CheckPointVar(R))
+                return false;
+
+            uint[] nS = new uint[ScalarUints];
+            if (!Scalar448.CheckVar(S, nS))
+                return false;
+
+            Init(out PointAffine pR);
+            if (!DecodePointVar(R, true, ref pR))
+                return false;
+
+            Init(out PointAffine pA);
+            F.Negate(publicPoint.m_data, pA.x);
+            F.Copy(publicPoint.m_data, F.Size, pA.y, 0);
+
+            byte[] A = new byte[PublicKeySize];
+            EncodePublicPoint(publicPoint, A, 0);
+
+            IXof d = CreateXof();
+            byte[] h = new byte[ScalarBytes * 2];
+
+            Dom4(d, phflag, ctx);
+            d.BlockUpdate(R, 0, PointBytes);
+            d.BlockUpdate(A, 0, PointBytes);
+            d.BlockUpdate(m, mOff, mLen);
+            d.OutputFinal(h, 0, h.Length);
+
+            byte[] k = Scalar448.Reduce(h);
+
+            uint[] nA = new uint[ScalarUints];
+            Scalar448.Decode(k, nA);
+
+            uint[] v0 = new uint[8];
+            uint[] v1 = new uint[8];
+#endif
 
-            F.Normalize(pZ.x);
-            F.Normalize(pZ.y);
-            F.Normalize(pZ.z);
+            Scalar448.ReduceBasisVar(nA, v0, v1);
+            Scalar448.Multiply225Var(nS, v1, nS);
 
-            return IsNeutralElementVar(pZ.x, pZ.y, pZ.z);
+            Init(out PointProjective pZ);
+            ScalarMultStraus225Var(nS, v0, ref pA, v1, ref pR, ref pZ);
+            return NormalizeToNeutralElementVar(ref pZ);
         }
 
         private static void Init(out PointAffine r)
@@ -655,6 +841,24 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return F.IsZeroVar(x) && F.AreEqualVar(y, z);
         }
 
+        private static void NormalizeToAffine(ref PointProjective p, ref PointAffine r)
+        {
+            F.Inv(p.z, r.y);
+            F.Mul(r.y, p.x, r.x);
+            F.Mul(r.y, p.y, r.y);
+            F.Normalize(r.x);
+            F.Normalize(r.y);
+        }
+
+        private static bool NormalizeToNeutralElementVar(ref PointProjective p)
+        {
+            F.Normalize(p.x);
+            F.Normalize(p.y);
+            F.Normalize(p.z);
+
+            return IsNeutralElementVar(p.x, p.y, p.z);
+        }
+
         private static void PointAdd(ref PointAffine p, ref PointProjective r)
         {
             uint[] b = F.Create();
@@ -813,6 +1017,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.Mul(f, g, r.z);
         }
 
+        private static void PointCopy(ref PointAffine p, ref PointProjective r)
+        {
+            F.Copy(p.x, 0, r.x, 0);
+            F.Copy(p.y, 0, r.y, 0);
+            F.One(r.z);
+        }
+
         private static void PointCopy(ref PointProjective p, ref PointProjective r)
         {
             F.Copy(p.x, 0, r.x, 0);
@@ -948,8 +1159,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return table;
         }
 
-        private static void PointPrecomputeVar(ref PointProjective p, PointProjective[] points, int pointsOff,
-            int pointsLen)
+        private static void PointPrecompute(ref PointAffine p, PointProjective[] points, int pointsOff, int pointsLen)
         {
             Debug.Assert(pointsLen > 0);
 
@@ -959,6 +1169,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             Init(out points[pointsOff]);
             PointCopy(ref p, ref points[pointsOff]);
+
             for (int i = 1; i < pointsLen; ++i)
             {
                 Init(out points[pointsOff + i]);
@@ -990,19 +1201,20 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
                 PointProjective[] points = new PointProjective[totalPoints];
 
-                Init(out PointProjective p);
-                F.Copy(B_x, 0, p.x, 0);
-                F.Copy(B_y, 0, p.y, 0);
-                F.One(p.z);
+                Init(out PointAffine B);
+                F.Copy(B_x, 0, B.x, 0);
+                F.Copy(B_y, 0, B.y, 0);
 
-                PointPrecomputeVar(ref p, points, 0, wnafPoints);
+                PointPrecompute(ref B, points, 0, wnafPoints);
 
-                Init(out PointProjective p225);
-                F.Copy(B225_x, 0, p225.x, 0);
-                F.Copy(B225_y, 0, p225.y, 0);
-                F.One(p225.z);
+                Init(out PointAffine B225);
+                F.Copy(B225_x, 0, B225.x, 0);
+                F.Copy(B225_y, 0, B225.y, 0);
 
-                PointPrecomputeVar(ref p225, points, wnafPoints, wnafPoints);
+                PointPrecompute(ref B225, points, wnafPoints, wnafPoints);
+
+                Init(out PointProjective p);
+                PointCopy(ref B, ref p);
 
                 int pointsIndex = wnafPoints * 2;
                 PointProjective[] toothPowers = new PointProjective[PrecompTeeth];
@@ -1010,6 +1222,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 {
                     Init(out toothPowers[tooth]);
                 }
+
                 for (int block = 0; block < PrecompBlocks; ++block)
                 {
                     ref PointProjective sum = ref points[pointsIndex++];
@@ -1225,7 +1438,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 #else
             Init(out PointProjective p);
             ScalarMultBase(k, ref p);
-            if (0 == EncodePoint(ref p, r, rOff))
+            if (0 == EncodeResult(ref p, r, rOff))
                 throw new InvalidOperationException();
 #endif
         }
@@ -1235,7 +1448,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             Init(out PointProjective p);
             ScalarMultBase(k, ref p);
-            if (0 == EncodePoint(ref p, r))
+            if (0 == EncodeResult(ref p, r))
                 throw new InvalidOperationException();
         }
 #endif
@@ -1276,7 +1489,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         }
 #endif
 
-        private static void ScalarMultOrderVar(ref PointProjective p, ref PointProjective r)
+        private static void ScalarMultOrderVar(ref PointAffine p, ref PointProjective r)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
             Span<sbyte> ws_p = stackalloc sbyte[447];
@@ -1288,7 +1501,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             int count = 1 << (WnafWidth225 - 2);
             PointProjective[] tp = new PointProjective[count];
-            PointPrecomputeVar(ref p, tp, 0, count);
+            PointPrecompute(ref p, tp, 0, count);
 
             PointSetNeutral(ref r);
 
@@ -1309,11 +1522,11 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-        private static void ScalarMultStraus225Var(ReadOnlySpan<uint> nb, ReadOnlySpan<uint> np, ref PointProjective p,
-            ReadOnlySpan<uint> nq, ref PointProjective q, ref PointProjective r)
+        private static void ScalarMultStraus225Var(ReadOnlySpan<uint> nb, ReadOnlySpan<uint> np, ref PointAffine p,
+            ReadOnlySpan<uint> nq, ref PointAffine q, ref PointProjective r)
 #else
-        private static void ScalarMultStraus225Var(uint[] nb, uint[] np, ref PointProjective p, uint[] nq,
-            ref PointProjective q, ref PointProjective r)
+        private static void ScalarMultStraus225Var(uint[] nb, uint[] np, ref PointAffine p, uint[] nq,
+            ref PointAffine q, ref PointProjective r)
 #endif
         {
             Debug.Assert(nb.Length == ScalarUints);
@@ -1342,8 +1555,8 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             int count = 1 << (WnafWidth225 - 2);
             PointProjective[] tp = new PointProjective[count];
             PointProjective[] tq = new PointProjective[count];
-            PointPrecomputeVar(ref p, tp, 0, count);
-            PointPrecomputeVar(ref q, tq, 0, count);
+            PointPrecompute(ref p, tp, 0, count);
+            PointPrecompute(ref q, tq, 0, count);
 
             PointSetNeutral(ref r);
 
@@ -1438,52 +1651,175 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static bool ValidatePublicKeyFull(byte[] pk, int pkOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            Span<byte> A = stackalloc byte[PublicKeySize];
-            A.CopyFrom(pk.AsSpan(pkOff));
+            return ValidatePublicKeyFull(pk.AsSpan(pkOff));
 #else
             byte[] A = Copy(pk, pkOff, PublicKeySize);
+
+            if (!CheckPointFullVar(A))
+                return false;
+
+            Init(out PointAffine pA);
+            if (!DecodePointVar(A, false, ref pA))
+                return false;
+
+            return CheckPointOrderVar(ref pA);
 #endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyFull(ReadOnlySpan<byte> pk)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
 
             if (!CheckPointFullVar(A))
                 return false;
 
-            Init(out PointProjective pA);
+            Init(out PointAffine pA);
             if (!DecodePointVar(A, false, ref pA))
                 return false;
 
-            Init(out PointProjective pR);
-            ScalarMultOrderVar(ref pA, ref pR);
+            return CheckPointOrderVar(ref pA);
+        }
+#endif
 
-            F.Normalize(pR.x);
-            F.Normalize(pR.y);
-            F.Normalize(pR.z);
+        public static bool ValidatePublicKeyFull(byte[] pk, int pkOff, out PublicPoint publicPoint)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ValidatePublicKeyFull(pk.AsSpan(pkOff), out publicPoint);
+#else
+            byte[] A = Copy(pk, pkOff, PublicKeySize);
 
-            return IsNeutralElementVar(pR.x, pR.y, pR.z);
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    if (CheckPointOrderVar(ref pA))
+                    {
+                        ExportPoint(ref pA, out publicPoint);
+                        return true;
+                    }
+                }
+            }
+
+            publicPoint = null;
+            return false;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyFull(ReadOnlySpan<byte> pk, out PublicPoint publicPoint)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
+
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    if (CheckPointOrderVar(ref pA))
+                    {
+                        ExportPoint(ref pA, out publicPoint);
+                        return true;
+                    }
+                }
+            }
+
+            publicPoint = null;
+            return false;
+        }
+#endif
+
         public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            Span<byte> A = stackalloc byte[PublicKeySize];
-            A.CopyFrom(pk.AsSpan(pkOff));
+            return ValidatePublicKeyPartial(pk.AsSpan(pkOff));
 #else
             byte[] A = Copy(pk, pkOff, PublicKeySize);
+
+            if (!CheckPointFullVar(A))
+                return false;
+
+            Init(out PointAffine pA);
+            return DecodePointVar(A, false, ref pA);
 #endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyPartial(ReadOnlySpan<byte> pk)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
 
             if (!CheckPointFullVar(A))
                 return false;
 
-            Init(out PointProjective pA);
+            Init(out PointAffine pA);
             return DecodePointVar(A, false, ref pA);
         }
+#endif
 
-        public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen)
+        public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff, out PublicPoint publicPoint)
+        {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ValidatePublicKeyPartial(pk.AsSpan(pkOff), out publicPoint);
+#else
+            byte[] A = Copy(pk, pkOff, PublicKeySize);
+
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    ExportPoint(ref pA, out publicPoint);
+                    return true;
+                }
+            }
+
+            publicPoint = null;
+            return false;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ValidatePublicKeyPartial(ReadOnlySpan<byte> pk, out PublicPoint publicPoint)
+        {
+            Span<byte> A = stackalloc byte[PublicKeySize];
+            A.CopyFrom(pk);
+
+            if (CheckPointFullVar(A))
+            {
+                Init(out PointAffine pA);
+                if (DecodePointVar(A, false, ref pA))
+                {
+                    ExportPoint(ref pA, out publicPoint);
+                    return true;
+                }
+            }
+
+            publicPoint = null;
+            return false;
+        }
+#endif
+
+        public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff,
+            int mLen)
         {
             byte phflag = 0x00;
 
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen);
         }
 
+        public static bool Verify(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, byte[] m, int mOff,
+            int mLen)
+        {
+            byte phflag = 0x00;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, m, mOff, mLen);
+        }
+
         public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff)
         {
             byte phflag = 0x01;
@@ -1491,6 +1827,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, ph, phOff, PrehashSize);
         }
 
+        public static bool VerifyPrehash(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, byte[] ph,
+            int phOff)
+        {
+            byte phflag = 0x01;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, ph, phOff, PrehashSize);
+        }
+
         public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, IXof ph)
         {
             byte[] m = new byte[PrehashSize];
@@ -1501,5 +1845,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
             return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, 0, m.Length);
         }
+
+        public static bool VerifyPrehash(byte[] sig, int sigOff, PublicPoint publicPoint, byte[] ctx, IXof ph)
+        {
+            byte[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.OutputFinal(m, 0, PrehashSize))
+                throw new ArgumentException("ph");
+
+            byte phflag = 0x01;
+
+            return ImplVerify(sig, sigOff, publicPoint, ctx, phflag, m, 0, m.Length);
+        }
     }
 }
diff --git a/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs b/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
index 90fc24a4c..4155d4a0f 100644
--- a/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
+++ b/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
@@ -26,6 +26,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
         {
             byte[] sk = new byte[Ed25519.SecretKeySize];
             byte[] pk = new byte[Ed25519.PublicKeySize];
+            byte[] pk2 = new byte[Ed25519.PublicKeySize];
             byte[] m = new byte[255];
             byte[] sig1 = new byte[Ed25519.SignatureSize];
 			byte[] sig2 = new byte[Ed25519.SignatureSize];
@@ -35,7 +36,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             for (int i = 0; i < 10; ++i)
             {
 				Random.NextBytes(sk);
-                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+                Ed25519.GeneratePublicKey(sk, 0, out var publicPoint);
+                Ed25519.EncodePublicPoint(publicPoint, pk, 0);
+
+                {
+                    Ed25519.GeneratePublicKey(sk, 0, pk2, 0);
+
+                    Assert.IsTrue(Arrays.AreEqual(pk, pk2), "Ed25519 consistent generation #" + i);
+                }
 
                 int mLen = Random.NextInt() & 255;
 
@@ -44,14 +52,29 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
 
                 Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed25519 consistent signatures #" + i);
 
-                bool shouldVerify = Ed25519.Verify(sig1, 0, pk, 0, m, 0, mLen);
+                {
+                    bool shouldVerify = Ed25519.Verify(sig1, 0, pk, 0, m, 0, mLen);
 
-                Assert.IsTrue(shouldVerify, "Ed25519 consistent sign/verify #" + i);
+                    Assert.IsTrue(shouldVerify, "Ed25519 consistent sign/verify #" + i);
+                }
+                {
+                    bool shouldVerify = Ed25519.Verify(sig1, 0, publicPoint, m, 0, mLen);
+
+                    Assert.IsTrue(shouldVerify, "Ed25519 consistent sign/verify #" + i);
+                }
 
                 sig1[Ed25519.PublicKeySize - 1] ^= 0x80;
-                bool shouldNotVerify = Ed25519.Verify(sig1, 0, pk, 0, m, 0, mLen);
 
-                Assert.IsFalse(shouldNotVerify, "Ed25519 consistent verification failure #" + i);
+                {
+                    bool shouldNotVerify = Ed25519.Verify(sig1, 0, pk, 0, m, 0, mLen);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed25519 consistent verification failure #" + i);
+                }
+                {
+                    bool shouldNotVerify = Ed25519.Verify(sig1, 0, publicPoint, m, 0, mLen);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed25519 consistent verification failure #" + i);
+                }
             }
         }
 
@@ -60,6 +83,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
         {
             byte[] sk = new byte[Ed25519.SecretKeySize];
             byte[] pk = new byte[Ed25519.PublicKeySize];
+            byte[] pk2 = new byte[Ed25519.PublicKeySize];
             byte[] ctx = new byte[Random.NextInt() & 7];
             byte[] m = new byte[255];
             byte[] sig1 = new byte[Ed25519.SignatureSize];
@@ -71,7 +95,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             for (int i = 0; i < 10; ++i)
             {
                 Random.NextBytes(sk);
-                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+                Ed25519.GeneratePublicKey(sk, 0, out var publicPoint);
+                Ed25519.EncodePublicPoint(publicPoint, pk, 0);
+
+                {
+                    Ed25519.GeneratePublicKey(sk, 0, pk2, 0);
+
+                    Assert.IsTrue(Arrays.AreEqual(pk, pk2), "Ed25519 consistent generation #" + i);
+                }
 
                 int mLen = Random.NextInt() & 255;
 
@@ -80,14 +111,29 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
 
                 Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed25519ctx consistent signatures #" + i);
 
-                bool shouldVerify = Ed25519.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
+                {
+                    bool shouldVerify = Ed25519.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
 
-                Assert.IsTrue(shouldVerify, "Ed25519ctx consistent sign/verify #" + i);
+                    Assert.IsTrue(shouldVerify, "Ed25519ctx consistent sign/verify #" + i);
+                }
+                {
+                    bool shouldVerify = Ed25519.Verify(sig1, 0, publicPoint, ctx, m, 0, mLen);
+
+                    Assert.IsTrue(shouldVerify, "Ed25519ctx consistent sign/verify #" + i);
+                }
 
                 sig1[Ed25519.PublicKeySize - 1] ^= 0x80;
-                bool shouldNotVerify = Ed25519.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
 
-                Assert.IsFalse(shouldNotVerify, "Ed25519ctx consistent verification failure #" + i);
+                {
+                    bool shouldNotVerify = Ed25519.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed25519ctx consistent verification failure #" + i);
+                }
+                {
+                    bool shouldNotVerify = Ed25519.Verify(sig1, 0, publicPoint, ctx, m, 0, mLen);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed25519ctx consistent verification failure #" + i);
+                }
             }
         }
 
@@ -96,6 +142,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
         {
             byte[] sk = new byte[Ed25519.SecretKeySize];
             byte[] pk = new byte[Ed25519.PublicKeySize];
+            byte[] pk2 = new byte[Ed25519.PublicKeySize];
             byte[] ctx = new byte[Random.NextInt() & 7];
             byte[] m = new byte[255];
             byte[] ph = new byte[Ed25519.PrehashSize];
@@ -108,7 +155,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             for (int i = 0; i < 10; ++i)
             {
                 Random.NextBytes(sk);
-                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+                Ed25519.GeneratePublicKey(sk, 0, out var publicPoint);
+                Ed25519.EncodePublicPoint(publicPoint, pk, 0);
+
+                {
+                    Ed25519.GeneratePublicKey(sk, 0, pk2, 0);
+
+                    Assert.IsTrue(Arrays.AreEqual(pk, pk2), "Ed25519 consistent generation #" + i);
+                }
 
                 int mLen = Random.NextInt() & 255;
 
@@ -121,14 +175,29 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
 
                 Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed25519ph consistent signatures #" + i);
 
-                bool shouldVerify = Ed25519.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
+                {
+                    bool shouldVerify = Ed25519.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
+
+                    Assert.IsTrue(shouldVerify, "Ed25519ph consistent sign/verify #" + i);
+                }
+                {
+                    bool shouldVerify = Ed25519.VerifyPrehash(sig1, 0, publicPoint, ctx, ph, 0);
 
-                Assert.IsTrue(shouldVerify, "Ed25519ph consistent sign/verify #" + i);
+                    Assert.IsTrue(shouldVerify, "Ed25519ph consistent sign/verify #" + i);
+                }
 
                 sig1[Ed25519.PublicKeySize - 1] ^= 0x80;
-                bool shouldNotVerify = Ed25519.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
 
-                Assert.IsFalse(shouldNotVerify, "Ed25519ph consistent verification failure #" + i);
+                {
+                    bool shouldNotVerify = Ed25519.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed25519ph consistent verification failure #" + i);
+                }
+                {
+                    bool shouldNotVerify = Ed25519.VerifyPrehash(sig1, 0, publicPoint, ctx, ph, 0);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed25519ph consistent verification failure #" + i);
+                }
             }
         }
 
diff --git a/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs b/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
index a26ef5579..38cde3b01 100644
--- a/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
+++ b/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
@@ -1,6 +1,4 @@
-using System;
-
-using NUnit.Framework;
+using NUnit.Framework;
 
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
@@ -25,6 +23,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
         {
             byte[] sk = new byte[Ed448.SecretKeySize];
             byte[] pk = new byte[Ed448.PublicKeySize];
+            byte[] pk2 = new byte[Ed448.PublicKeySize];
             byte[] ctx = new byte[Random.NextInt() & 7];
             byte[] m = new byte[255];
             byte[] sig1 = new byte[Ed448.SignatureSize];
@@ -36,7 +35,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             for (int i = 0; i < 10; ++i)
             {
                 Random.NextBytes(sk);
-                Ed448.GeneratePublicKey(sk, 0, pk, 0);
+                Ed448.GeneratePublicKey(sk, 0, out var publicPoint);
+                Ed448.EncodePublicPoint(publicPoint, pk, 0);
+
+                {
+                    Ed448.GeneratePublicKey(sk, 0, pk2, 0);
+
+                    Assert.IsTrue(Arrays.AreEqual(pk, pk2), "Ed448 consistent generation #" + i);
+                }
 
                 int mLen = Random.NextInt() & 255;
 
@@ -45,14 +51,29 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
 
                 Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed448 consistent signatures #" + i);
 
-                bool shouldVerify = Ed448.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
+                {
+                    bool shouldVerify = Ed448.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
 
-                Assert.IsTrue(shouldVerify, "Ed448 consistent sign/verify #" + i);
+                    Assert.IsTrue(shouldVerify, "Ed448 consistent sign/verify #" + i);
+                }
+                {
+                    bool shouldVerify = Ed448.Verify(sig1, 0, publicPoint, ctx, m, 0, mLen);
+
+                    Assert.IsTrue(shouldVerify, "Ed448 consistent sign/verify #" + i);
+                }
 
                 sig1[Ed448.PublicKeySize - 1] ^= 0x80;
-                bool shouldNotVerify = Ed448.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
 
-                Assert.IsFalse(shouldNotVerify, "Ed448 consistent verification failure #" + i);
+                {
+                    bool shouldNotVerify = Ed448.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed448 consistent verification failure #" + i);
+                }
+                {
+                    bool shouldNotVerify = Ed448.Verify(sig1, 0, publicPoint, ctx, m, 0, mLen);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed448 consistent verification failure #" + i);
+                }
             }
         }
 
@@ -61,6 +82,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
         {
             byte[] sk = new byte[Ed448.SecretKeySize];
             byte[] pk = new byte[Ed448.PublicKeySize];
+            byte[] pk2 = new byte[Ed448.PublicKeySize];
             byte[] ctx = new byte[Random.NextInt() & 7];
             byte[] m = new byte[255];
             byte[] ph = new byte[Ed448.PrehashSize];
@@ -73,7 +95,14 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             for (int i = 0; i < 10; ++i)
             {
                 Random.NextBytes(sk);
-                Ed448.GeneratePublicKey(sk, 0, pk, 0);
+                Ed448.GeneratePublicKey(sk, 0, out var publicPoint);
+                Ed448.EncodePublicPoint(publicPoint, pk, 0);
+
+                {
+                    Ed448.GeneratePublicKey(sk, 0, pk2, 0);
+
+                    Assert.IsTrue(Arrays.AreEqual(pk, pk2), "Ed448 consistent generation #" + i);
+                }
 
                 int mLen = Random.NextInt() & 255;
 
@@ -86,14 +115,29 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
 
                 Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed448ph consistent signatures #" + i);
 
-                bool shouldVerify = Ed448.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
+                {
+                    bool shouldVerify = Ed448.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
 
-                Assert.IsTrue(shouldVerify, "Ed448ph consistent sign/verify #" + i);
+                    Assert.IsTrue(shouldVerify, "Ed448ph consistent sign/verify #" + i);
+                }
+                {
+                    bool shouldVerify = Ed448.VerifyPrehash(sig1, 0, publicPoint, ctx, ph, 0);
+
+                    Assert.IsTrue(shouldVerify, "Ed448ph consistent sign/verify #" + i);
+                }
 
                 sig1[Ed448.PublicKeySize - 1] ^= 0x80;
-                bool shouldNotVerify = Ed448.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
 
-                Assert.IsFalse(shouldNotVerify, "Ed448ph consistent verification failure #" + i);
+                {
+                    bool shouldNotVerify = Ed448.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed448ph consistent verification failure #" + i);
+                }
+                {
+                    bool shouldNotVerify = Ed448.VerifyPrehash(sig1, 0, publicPoint, ctx, ph, 0);
+
+                    Assert.IsFalse(shouldNotVerify, "Ed448ph consistent verification failure #" + i);
+                }
             }
         }