summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2021-02-10 16:53:47 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2021-02-10 16:53:47 +0700
commit332484150c8bcc94dad95b5948d20835d948e831 (patch)
tree08c6c76ebdc3855524afc71c082558763b89b614
parentRefactor nonce generator init (diff)
downloadBouncyCastle.NET-ed25519-332484150c8bcc94dad95b5948d20835d948e831.tar.xz
EdDSA public key validation
- per NIST SP 800-186
-rw-r--r--crypto/src/math/ec/rfc7748/X25519Field.cs34
-rw-r--r--crypto/src/math/ec/rfc7748/X448Field.cs34
-rw-r--r--crypto/src/math/ec/rfc8032/Ed25519.cs81
-rw-r--r--crypto/src/math/ec/rfc8032/Ed448.cs74
-rw-r--r--crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs92
-rw-r--r--crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs92
6 files changed, 399 insertions, 8 deletions
diff --git a/crypto/src/math/ec/rfc7748/X25519Field.cs b/crypto/src/math/ec/rfc7748/X25519Field.cs
index ffede563b..d0b835226 100644
--- a/crypto/src/math/ec/rfc7748/X25519Field.cs
+++ b/crypto/src/math/ec/rfc7748/X25519Field.cs
@@ -48,6 +48,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             }
         }
 
+        public static int AreEqual(int[] x, int[] y)
+        {
+            int d = 0;
+            for (int i = 0; i < Size; ++i)
+            {
+                d |= x[i] ^ y[i];
+            }
+            d |= d >> 16;
+            d &= 0xFFFF;
+            return (d - 1) >> 31;
+        }
+
+        public static bool AreEqualVar(int[] x, int[] y)
+        {
+            return 0 != AreEqual(x, y);
+        }
+
         public static void Carry(int[] z)
         {
             int z0 = z[0], z1 = z[1], z2 = z[2], z3 = z[3], z4 = z[4];
@@ -258,6 +275,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Decode(u, 0, z);
         }
 
+        public static int IsOne(int[] x)
+        {
+            int d = x[0] ^ 1;
+            for (int i = 1; i < Size; ++i)
+            {
+                d |= x[i];
+            }
+            d |= d >> 16;
+            d &= 0xFFFF;
+            return (d - 1) >> 31;
+        }
+
+        public static bool IsOneVar(int[] x)
+        {
+            return 0 != IsOne(x);
+        }
+
         public static int IsZero(int[] x)
         {
             int d = 0;
diff --git a/crypto/src/math/ec/rfc7748/X448Field.cs b/crypto/src/math/ec/rfc7748/X448Field.cs
index ef4fd4627..6d8c60e78 100644
--- a/crypto/src/math/ec/rfc7748/X448Field.cs
+++ b/crypto/src/math/ec/rfc7748/X448Field.cs
@@ -46,6 +46,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
         //    }
         //}
 
+        public static int AreEqual(uint[] x, uint[] y)
+        {
+            uint d = 0;
+            for (int i = 0; i < Size; ++i)
+            {
+                d |= x[i] ^ y[i];
+            }
+            d |= d >> 16;
+            d &= 0xFFFF;
+            return ((int)d - 1) >> 31;
+        }
+
+        public static bool AreEqualVar(uint[] x, uint[] y)
+        {
+            return 0 != AreEqual(x, y);
+        }
+
         public static void Carry(uint[] z)
         {
             uint z0 = z[0], z1 = z[1], z2 = z[2], z3 = z[3], z4 = z[4], z5 = z[5], z6 = z[6], z7 = z[7];
@@ -285,6 +302,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc7748
             Decode(u, 0, z);
         }
 
+        public static int IsOne(uint[] x)
+        {
+            uint d = x[0] ^ 1;
+            for (int i = 1; i < Size; ++i)
+            {
+                d |= x[i];
+            }
+            d |= d >> 16;
+            d &= 0xFFFF;
+            return ((int)d - 1) >> 31;
+        }
+
+        public static bool IsOneVar(uint[] x)
+        {
+            return 0 != IsOne(x);
+        }
+
         public static int IsZero(uint[] x)
         {
             uint d = 0;
diff --git a/crypto/src/math/ec/rfc8032/Ed25519.cs b/crypto/src/math/ec/rfc8032/Ed25519.cs
index bdbe47e50..cc351bc45 100644
--- a/crypto/src/math/ec/rfc8032/Ed25519.cs
+++ b/crypto/src/math/ec/rfc8032/Ed25519.cs
@@ -183,6 +183,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return !Nat256.Gte(n, L);
         }
 
+        private static byte[] Copy(byte[] buf, int off, int len)
+        {
+            byte[] result = new byte[len];
+            Array.Copy(buf, off, result, 0, len);
+            return result;
+        }
+
         private static IDigest CreateDigest()
         {
             return new Sha512Digest();
@@ -220,7 +227,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static bool DecodePointVar(byte[] p, int pOff, bool negate, PointAffine r)
         {
-            byte[] py = Arrays.CopyOfRange(p, pOff, pOff + PointBytes);
+            byte[] py = Copy(p, pOff, PointBytes);
             if (!CheckPointVar(py))
                 return false;
 
@@ -461,8 +468,8 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!CheckContextVar(ctx, phflag))
                 throw new ArgumentException("ctx");
 
-            byte[] R = Arrays.CopyOfRange(sig, sigOff, sigOff + PointBytes);
-            byte[] S = Arrays.CopyOfRange(sig, sigOff + PointBytes, sigOff + SignatureSize);
+            byte[] R = Copy(sig, sigOff, PointBytes);
+            byte[] S = Copy(sig, sigOff + PointBytes, ScalarBytes);
 
             if (!CheckPointVar(R))
                 return false;
@@ -498,6 +505,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return 0 != EncodePoint(pR, check, 0) && Arrays.AreEqual(check, R);
         }
 
+        private static bool IsNeutralElementVar(int[] x, int[] y)
+        {
+            return F.IsZeroVar(x) && F.IsOneVar(y);
+        }
+
+        private static bool IsNeutralElementVar(int[] x, int[] y, int[] z)
+        {
+            return F.IsZeroVar(x) && F.AreEqualVar(y, z);
+        }
+
         private static void PointAdd(PointExt p, PointAccum r)
         {
             int[] a = F.Create();
@@ -1234,6 +1251,36 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.Copy(p.z, 0, z, 0);
         }
 
+        private static void ScalarMultOrder(PointAffine p, PointAccum r)
+        {
+            uint[] n = new uint[ScalarUints];
+            Nat.ShiftDownBit(ScalarUints, L, 0, n);
+            n[ScalarUints - 1] |= 1U << 28;
+
+            int[] table = PointPrecompute(p, 8);
+            PointExt q = new PointExt();
+
+            // Replace first 4 doublings (2^4 * P) with 1 addition (P + 15 * P)
+            PointCopy(p, r);
+            PointLookup(table, 7, q);
+            PointAdd(q, r);
+
+            int w = 62;
+            for (;;)
+            {
+                PointLookup(n, w, table, q);
+                PointAdd(q, r);
+
+                if (--w < 0)
+                    break;
+
+                for (int i = 0; i < 4; ++i)
+                {
+                    PointDouble(r);
+                }
+            }
+        }
+
         private static void ScalarMultStrausVar(uint[] nb, uint[] np, PointAffine p, PointAccum r)
         {
             Precompute();
@@ -1340,6 +1387,34 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, 0, m.Length, sig, sigOff);
         }
 
+        public static bool ValidatePublicKeyFull(byte[] pk, int pkOff)
+        {
+            PointAffine p = new PointAffine();
+            if (!DecodePointVar(pk, pkOff, false, p))
+                return false;
+
+            F.Normalize(p.x);
+            F.Normalize(p.y);
+
+            if (IsNeutralElementVar(p.x, p.y))
+                return false;
+
+            PointAccum r = new PointAccum();
+            ScalarMultOrder(p, r);
+
+            F.Normalize(r.x);
+            F.Normalize(r.y);
+            F.Normalize(r.z);
+
+            return IsNeutralElementVar(r.x, r.y, r.z);
+        }
+
+        public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff)
+        {
+            PointAffine p = new PointAffine();
+            return DecodePointVar(pk, pkOff, false, p);
+        }
+
         public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen)
         {
             byte[] ctx = null;
diff --git a/crypto/src/math/ec/rfc8032/Ed448.cs b/crypto/src/math/ec/rfc8032/Ed448.cs
index bbe1bb444..28ee546d1 100644
--- a/crypto/src/math/ec/rfc8032/Ed448.cs
+++ b/crypto/src/math/ec/rfc8032/Ed448.cs
@@ -173,6 +173,13 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return !Nat.Gte(ScalarUints, n, L);
         }
 
+        private static byte[] Copy(byte[] buf, int off, int len)
+        {
+            byte[] result = new byte[len];
+            Array.Copy(buf, off, result, 0, len);
+            return result;
+        }
+
         public static IXof CreatePrehash()
         {
             return CreateXof();
@@ -217,7 +224,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         private static bool DecodePointVar(byte[] p, int pOff, bool negate, PointExt r)
         {
-            byte[] py = Arrays.CopyOfRange(p, pOff, pOff + PointBytes);
+            byte[] py = Copy(p, pOff, PointBytes);
             if (!CheckPointVar(py))
                 return false;
 
@@ -459,8 +466,8 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             if (!CheckContextVar(ctx))
                 throw new ArgumentException("ctx");
 
-            byte[] R = Arrays.CopyOfRange(sig, sigOff, sigOff + PointBytes);
-            byte[] S = Arrays.CopyOfRange(sig, sigOff + PointBytes, sigOff + SignatureSize);
+            byte[] R = Copy(sig, sigOff, PointBytes);
+            byte[] S = Copy(sig, sigOff + PointBytes, ScalarBytes);
 
             if (!CheckPointVar(R))
                 return false;
@@ -496,6 +503,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return 0 != EncodePoint(pR, check, 0) && Arrays.AreEqual(check, R);
         }
 
+        private static bool IsNeutralElementVar(uint[] x, uint[] y)
+        {
+            return F.IsZeroVar(x) && F.IsOneVar(y);
+        }
+
+        private static bool IsNeutralElementVar(uint[] x, uint[] y, uint[] z)
+        {
+            return F.IsZeroVar(x) && F.AreEqualVar(y, z);
+        }
+
         private static void PointAdd(PointExt p, PointExt r)
         {
             uint[] a = F.Create();
@@ -1255,6 +1272,28 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             F.Copy(p.y, 0, y, 0);
         }
 
+        private static void ScalarMultOrder(PointExt p, PointExt r)
+        {
+            uint[] n = new uint[ScalarUints];
+            Nat.ShiftDownBit(ScalarUints, L, 1, n);
+
+            uint[] table = PointPrecompute(p, 8);
+            PointExt q = new PointExt();
+
+            PointLookup(n, 111, table, r);
+
+            for (int w = 110; w >= 0; --w)
+            {
+                for (int i = 0; i < 4; ++i)
+                {
+                    PointDouble(r);
+                }
+
+                PointLookup(n, w, table, q);
+                PointAdd(q, r);
+            }
+        }
+
         private static void ScalarMultStrausVar(uint[] nb, uint[] np, PointExt p, PointExt r)
         {
             Precompute();
@@ -1345,6 +1384,35 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, 0, m.Length, sig, sigOff);
         }
 
+        public static bool ValidatePublicKeyFull(byte[] pk, int pkOff)
+        {
+            PointExt p = new PointExt();
+            if (!DecodePointVar(pk, pkOff, false, p))
+                return false;
+
+            F.Normalize(p.x);
+            F.Normalize(p.y);
+            F.Normalize(p.z);
+
+            if (IsNeutralElementVar(p.x, p.y, p.z))
+                return false;
+
+            PointExt r = new PointExt();
+            ScalarMultOrder(p, r);
+
+            F.Normalize(r.x);
+            F.Normalize(r.y);
+            F.Normalize(r.z);
+
+            return IsNeutralElementVar(r.x, r.y, r.z);
+        }
+
+        public static bool ValidatePublicKeyPartial(byte[] pk, int pkOff)
+        {
+            PointExt p = new PointExt();
+            return DecodePointVar(pk, pkOff, false, p);
+        }
+
         public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen)
         {
             byte phflag = 0x00;
diff --git a/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs b/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
index 8a61609af..29ff67191 100644
--- a/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
+++ b/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
@@ -13,8 +13,10 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
     public class Ed25519Test
     {
 		private static readonly SecureRandom Random = new SecureRandom();
-      
-		[SetUp]
+
+        private static readonly byte[] Neutral = Hex.DecodeStrict("0100000000000000000000000000000000000000000000000000000000000000");
+
+        [SetUp]
         public void SetUp()
         {
             Ed25519.Precompute();
@@ -365,6 +367,92 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
                 "Ed25519ph Vector #1");
         }
 
+        [Test]
+        public void TestPublicKeyValidationFull()
+        {
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Neutral, 0));
+
+            byte[] sk = new byte[Ed25519.SecretKeySize];
+            byte[] pk = new byte[Ed25519.PublicKeySize];
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+                Assert.IsTrue(Ed25519.ValidatePublicKeyFull(pk, 0));
+            }
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("0100000000000000000000000000000000000000000000000000000000000080"), 0));
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 0));
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("D73D6044821BD0DF4068AE1792F0851170F53062150AA70A87E2A58A05A26115"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("F9D557BE0F3C700571CD8AD9CFDE0A2C67F88EE71830073C7756A0599311AD94"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("7A772BBC08D53BF381B150D8411B9AF134BBF24B90A038EFD8DA4A17B32606A1"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("DC6EF81316C08B91209A73FE8E208DD319F56C6A47956A03AF7D6D826A88AC87"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("6EEDF105177868C9AD48DAF2C36EE3B169D892A02A3BF83101B1D50D86BFB19E"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("4BAAB5711F22FF7479E6D9BD2C5BC4DCD3CFC9F36921971496907B1F2B62C6BA"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("D96A46432581A80085F978F7FC0977E228C5A3FD2E64D588BB5F5E5A84E4ABAE"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("10C326AE15FA5BA89EDDAB89C860797385298F4C7750BAEB94A5AAC9A876B538"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("7808F3F6EB858E9BBD2570F20A9F7502175F312FA2DBE4C96EB5C683B384AA60"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("0DE943C51E91AA3ED9FFA82D39A9813D94F59246452F6A7780D067BC61342FE1"), 0));
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("10026DBFB4C55628716BB0EF979A10DD5AC7AA970C229B5E68DD993E2C20E7D5"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("68EC52D16C1DB4483AA8679277C34E0DC56EB7D064D302B9749F0D31A901D484"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("6E54C8F00669422D5697E09C0575AE1E699841ACF1690A5DFAA25E3160F3A2EF"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("CA66B62D361F790AA9658161BA0FFDC3CE60624151258C7301926DFE0C67EE64"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("88D912C322AE3D0907B38ED08727FBF06D51C5D1DE622B5BC24DAB30078AE9FF"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("F24683E044CE3F14BCA24F1356AE7767509E17EFA2606438BA275860819E14B8"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("B2865F02E6D19A94CE6147B574095733B3628A2FBE2C84022262D88F7D6C4F7D"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("FA4DA03321816C1C9066BD250982DDD1B4349C43C5E124D2B39F8DDA4E5364F8"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("FCADF40DE51A943F3B7847DBEBA0627B33D020D81DFFABF2B3701BD9B746952A"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyFull(Hex.DecodeStrict("379B071E6F7E2479D5A8588AB708137808D63F689127D4A228E2C1681873C55E"), 0));
+        }
+
+        [Test]
+        public void TestPublicKeyValidationPartial()
+        {
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Neutral, 0));
+
+            byte[] sk = new byte[Ed25519.SecretKeySize];
+            byte[] pk = new byte[Ed25519.PublicKeySize];
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+                Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(pk, 0));
+            }
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("0100000000000000000000000000000000000000000000000000000000000080"), 0));
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 0));
+
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("D73D6044821BD0DF4068AE1792F0851170F53062150AA70A87E2A58A05A26115"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("F9D557BE0F3C700571CD8AD9CFDE0A2C67F88EE71830073C7756A0599311AD94"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("7A772BBC08D53BF381B150D8411B9AF134BBF24B90A038EFD8DA4A17B32606A1"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("DC6EF81316C08B91209A73FE8E208DD319F56C6A47956A03AF7D6D826A88AC87"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("6EEDF105177868C9AD48DAF2C36EE3B169D892A02A3BF83101B1D50D86BFB19E"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("4BAAB5711F22FF7479E6D9BD2C5BC4DCD3CFC9F36921971496907B1F2B62C6BA"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("D96A46432581A80085F978F7FC0977E228C5A3FD2E64D588BB5F5E5A84E4ABAE"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("10C326AE15FA5BA89EDDAB89C860797385298F4C7750BAEB94A5AAC9A876B538"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("7808F3F6EB858E9BBD2570F20A9F7502175F312FA2DBE4C96EB5C683B384AA60"), 0));
+            Assert.IsFalse(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("0DE943C51E91AA3ED9FFA82D39A9813D94F59246452F6A7780D067BC61342FE1"), 0));
+
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("10026DBFB4C55628716BB0EF979A10DD5AC7AA970C229B5E68DD993E2C20E7D5"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("68EC52D16C1DB4483AA8679277C34E0DC56EB7D064D302B9749F0D31A901D484"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("6E54C8F00669422D5697E09C0575AE1E699841ACF1690A5DFAA25E3160F3A2EF"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("CA66B62D361F790AA9658161BA0FFDC3CE60624151258C7301926DFE0C67EE64"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("88D912C322AE3D0907B38ED08727FBF06D51C5D1DE622B5BC24DAB30078AE9FF"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("F24683E044CE3F14BCA24F1356AE7767509E17EFA2606438BA275860819E14B8"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("B2865F02E6D19A94CE6147B574095733B3628A2FBE2C84022262D88F7D6C4F7D"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("FA4DA03321816C1C9066BD250982DDD1B4349C43C5E124D2B39F8DDA4E5364F8"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("FCADF40DE51A943F3B7847DBEBA0627B33D020D81DFFABF2B3701BD9B746952A"), 0));
+            Assert.IsTrue(Ed25519.ValidatePublicKeyPartial(Hex.DecodeStrict("379B071E6F7E2479D5A8588AB708137808D63F689127D4A228E2C1681873C55E"), 0));
+        }
+
         private static void CheckEd25519Vector(string sSK, string sPK, string sM, string sSig, string text)
         {
             byte[] sk = Hex.Decode(sSK);
diff --git a/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs b/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
index cc8e82de0..40d28cc97 100644
--- a/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
+++ b/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
@@ -14,6 +14,8 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
     {
         private static readonly SecureRandom Random = new SecureRandom();
 
+        private static readonly byte[] Neutral = Hex.DecodeStrict("010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
+
         [SetUp]
         public void SetUp()
         {
@@ -462,6 +464,96 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
                 "Ed448ph Vector #2");
         }
 
+        [Test]
+        public void TestPublicKeyValidationFull()
+        {
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Neutral, 0));
+
+            byte[] sk = new byte[Ed448.SecretKeySize];
+            byte[] pk = new byte[Ed448.PublicKeySize];
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed448.GeneratePublicKey(sk, 0, pk, 0);
+                Assert.IsTrue(Ed448.ValidatePublicKeyFull(pk, 0));
+            }
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080"), 0));
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081"), 0));
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("C784B7238BDDDB84C44FB80936FB103FCF39C1F74EE83163A57DB4AD3946FDC81BF0504D6EC1DBABABDB750997BCA465D5FCD3A45F8E183D00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("149578BCA53F7D199B472D6D367D22A35942BBCA2051F833122D4DC12FE758A16D672A54D5F8C390C44C2F8B32F21121DA69E9DE8FF9675780"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("9E9F6E7A8576E8D7C286C493FE76559419012B164589DF764E735CFFDE21BFCAF4D7553F9B37178A2F20C77473E4195E3E1E327F3174C14500"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("1979BDCBE0CEC16602B87257114059029605C720D5AFD2A90EF4B06655B34B561EBA6C1034452C3D8D1DA41C57340B0C9A95297E712CA75C00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("E2B8507036D478F262A7009734CDD383734002CE32397FEA22BFEDEE0CEBB0064D176FB45A05AF19F8B18B07EE20D6E2320D075E95DAF15200"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("3C2FFFFCF504A0EBD8051B3962546C39410464A9C44DC3E82FA9437F2450F0F93C892F28E2ABDF7EA84B051E5536CCA6B44762D0941C5D0700"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("997FE46037CF6207B27B6D0BB9D7D97A038D5BFCF898D07EE6ED07953F0889CC4745D1E018EB7A894EFE88871004452E99C6A344362DA6E080"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("F67C319B8EDCE2E85D450BE46E1671183EB499CE8ABE56BCF666C13A99C5ECBC89FCE9B3B578E2A5D061D3590506BC27614DB6B0C682971B80"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("A7776DDD0BD52EC4D017478E38700395F9F4C45A3BEFFE4EA9994EA1A9E92D8D1CC56539BF57FF88401BBDC764904BC0E3635AEE1721FD3380"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("9F914CD9920D2B75ADBF34F758DA39BB35D1C81B5C480571A7A8B2CAAD7BA0F32D13AF9C69B0BECE5775B324DC49C063354EA2F6F231A23800"), 0));
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("2E0ACAB5953BC2F22C557C75E6B86225BE9CB3E82E78FC886EB57B628229C0C9548CD82630483C03D0E5DC02B2C3B1BD0E5DEE0B8DE4A88000"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("0F89D2E413A36A33031329169EFAAE88D8F84E90E741C3DFBB01C32544D995FF6EB354B6C5C29E62ACC124E806540C46CB0C0ED71931B39D00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("AB553809FE1B027328EC7DBE71929C1F3A435B74CC0C06BEA831BC5E287EF2ACF4E831EC8E0C964A80BF85B966D32CADAE8E17E12EAFD3AF80"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("3043631378826937A822BDB8878DC33B9174BEC3530B2A8A4048F6B06B378DBC450E34ED623E47B1449E7636DAFB72F584605EF3BE01647F00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("8A03959C8BD20BA7B1DA92A4C5591BC846F4CD8BE07387B325E179BB12245E17BDDE1AC82E9F7CAB2A79DDDBE68F8BA8DB7F03F91156C24A00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("46299C8D31BAFEBBCF1719A851123CC4722E5BE9D93D8F98C215D34082AF658C570B4CFD44079993CBC19B0EAD3BFD0DFB2B67EBABB119B780"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("81A58C03708DD60BD68237622EC9934E8DE27FE7997F74A501B06C60C8F9D68856E7D12B88F1507E29EB0C30531B5AA353F154F2551AF5E580"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("DC5028F5AA0B9217B40C7FE00E10503C37B6611BA87CDD70F01536E87AD659711BA1265E679F94EC8D5ED87476CE031D14B2C7E46268F11A80"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("910DDFE36AAB6DCB7D5B72E6F2DF0769AD86665262232C487F722FEA85429DE247D1EBBC5C579A01D04672894E2B0F3FBDF22B43EA191DF700"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyFull(Hex.DecodeStrict("43482D0750D4830AAAF578346288050EAE8ADF96DF66F243E73252114E432B448730517FD8726871508CAD7ECECFDB33120CA5558788B6C800"), 0));
+        }
+
+        [Test]
+        public void TestPublicKeyValidationPartial()
+        {
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Neutral, 0));
+
+            byte[] sk = new byte[Ed448.SecretKeySize];
+            byte[] pk = new byte[Ed448.PublicKeySize];
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed448.GeneratePublicKey(sk, 0, pk, 0);
+                Assert.IsTrue(Ed448.ValidatePublicKeyPartial(pk, 0));
+            }
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080"), 0));
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081"), 0));
+
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("C784B7238BDDDB84C44FB80936FB103FCF39C1F74EE83163A57DB4AD3946FDC81BF0504D6EC1DBABABDB750997BCA465D5FCD3A45F8E183D00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("149578BCA53F7D199B472D6D367D22A35942BBCA2051F833122D4DC12FE758A16D672A54D5F8C390C44C2F8B32F21121DA69E9DE8FF9675780"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("9E9F6E7A8576E8D7C286C493FE76559419012B164589DF764E735CFFDE21BFCAF4D7553F9B37178A2F20C77473E4195E3E1E327F3174C14500"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("1979BDCBE0CEC16602B87257114059029605C720D5AFD2A90EF4B06655B34B561EBA6C1034452C3D8D1DA41C57340B0C9A95297E712CA75C00"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("E2B8507036D478F262A7009734CDD383734002CE32397FEA22BFEDEE0CEBB0064D176FB45A05AF19F8B18B07EE20D6E2320D075E95DAF15200"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("3C2FFFFCF504A0EBD8051B3962546C39410464A9C44DC3E82FA9437F2450F0F93C892F28E2ABDF7EA84B051E5536CCA6B44762D0941C5D0700"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("997FE46037CF6207B27B6D0BB9D7D97A038D5BFCF898D07EE6ED07953F0889CC4745D1E018EB7A894EFE88871004452E99C6A344362DA6E080"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("F67C319B8EDCE2E85D450BE46E1671183EB499CE8ABE56BCF666C13A99C5ECBC89FCE9B3B578E2A5D061D3590506BC27614DB6B0C682971B80"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("A7776DDD0BD52EC4D017478E38700395F9F4C45A3BEFFE4EA9994EA1A9E92D8D1CC56539BF57FF88401BBDC764904BC0E3635AEE1721FD3380"), 0));
+            Assert.IsFalse(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("9F914CD9920D2B75ADBF34F758DA39BB35D1C81B5C480571A7A8B2CAAD7BA0F32D13AF9C69B0BECE5775B324DC49C063354EA2F6F231A23800"), 0));
+
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("2E0ACAB5953BC2F22C557C75E6B86225BE9CB3E82E78FC886EB57B628229C0C9548CD82630483C03D0E5DC02B2C3B1BD0E5DEE0B8DE4A88000"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("0F89D2E413A36A33031329169EFAAE88D8F84E90E741C3DFBB01C32544D995FF6EB354B6C5C29E62ACC124E806540C46CB0C0ED71931B39D00"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("AB553809FE1B027328EC7DBE71929C1F3A435B74CC0C06BEA831BC5E287EF2ACF4E831EC8E0C964A80BF85B966D32CADAE8E17E12EAFD3AF80"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("3043631378826937A822BDB8878DC33B9174BEC3530B2A8A4048F6B06B378DBC450E34ED623E47B1449E7636DAFB72F584605EF3BE01647F00"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("8A03959C8BD20BA7B1DA92A4C5591BC846F4CD8BE07387B325E179BB12245E17BDDE1AC82E9F7CAB2A79DDDBE68F8BA8DB7F03F91156C24A00"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("46299C8D31BAFEBBCF1719A851123CC4722E5BE9D93D8F98C215D34082AF658C570B4CFD44079993CBC19B0EAD3BFD0DFB2B67EBABB119B780"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("81A58C03708DD60BD68237622EC9934E8DE27FE7997F74A501B06C60C8F9D68856E7D12B88F1507E29EB0C30531B5AA353F154F2551AF5E580"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("DC5028F5AA0B9217B40C7FE00E10503C37B6611BA87CDD70F01536E87AD659711BA1265E679F94EC8D5ED87476CE031D14B2C7E46268F11A80"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("910DDFE36AAB6DCB7D5B72E6F2DF0769AD86665262232C487F722FEA85429DE247D1EBBC5C579A01D04672894E2B0F3FBDF22B43EA191DF700"), 0));
+            Assert.IsTrue(Ed448.ValidatePublicKeyPartial(Hex.DecodeStrict("43482D0750D4830AAAF578346288050EAE8ADF96DF66F243E73252114E432B448730517FD8726871508CAD7ECECFDB33120CA5558788B6C800"), 0));
+        }
+
         private static void CheckEd448Vector(string sSK, string sPK, string sM, string sCTX, string sSig, string text)
         {
             byte[] sk = Hex.Decode(sSK);