summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2018-09-14 17:52:34 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2018-09-14 17:52:34 +0700
commitca42ede9a2580725453c3b97d3c4b1e84cecf3dd (patch)
tree5b49d2927988be5c0b64d7159b7977d9b7510c26
parentRFC 7748: Export size constants for scalars, points (diff)
downloadBouncyCastle.NET-ed25519-ca42ede9a2580725453c3b97d3c4b1e84cecf3dd.tar.xz
RFC 8032: Implement Ed25519ctx, Ed25519ph, Ed448ph variants
-rw-r--r--crypto/src/math/ec/rfc8032/Ed25519.cs255
-rw-r--r--crypto/src/math/ec/rfc8032/Ed448.cs232
-rw-r--r--crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs281
-rw-r--r--crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs178
4 files changed, 783 insertions, 163 deletions
diff --git a/crypto/src/math/ec/rfc8032/Ed25519.cs b/crypto/src/math/ec/rfc8032/Ed25519.cs
index ff4587cb2..adb56ca9b 100644
--- a/crypto/src/math/ec/rfc8032/Ed25519.cs
+++ b/crypto/src/math/ec/rfc8032/Ed25519.cs
@@ -2,6 +2,7 @@
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
 
+using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Math.EC.Rfc7748;
 using Org.BouncyCastle.Math.Raw;
@@ -18,11 +19,12 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         private const int ScalarUints = 8;
         private const int ScalarBytes = ScalarUints * 4;
 
+        public static readonly int PrehashSize = 64;
         public static readonly int PublicKeySize = PointBytes;
         public static readonly int SecretKeySize = 32;
         public static readonly int SignatureSize = PointBytes + ScalarBytes;
 
-        //private static readonly byte[] Dom2Prefix = Strings.ToByteArray("SigEd25519 no Ed25519 collisions");
+        private static readonly byte[] Dom2Prefix = Strings.ToByteArray("SigEd25519 no Ed25519 collisions");
 
         private static readonly uint[] P = { 0xFFFFFFEDU, 0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU, 0x7FFFFFFFU };
         private static readonly uint[] L = { 0x5CF5D3EDU, 0x5812631AU, 0xA2F79CD6U, 0x14DEF9DEU, 0x00000000U, 0x00000000U, 0x00000000U, 0x10000000U };
@@ -96,6 +98,12 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ReduceScalar(result);
         }
 
+        private static bool CheckContextVar(byte[] ctx, byte phflag)
+        {
+            return ctx == null && phflag == 0x00
+                || ctx != null && ctx.Length < 256;
+        }
+
         private static bool CheckPointVar(byte[] p)
         {
             uint[] t = new uint[8];
@@ -111,6 +119,16 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return !Nat256.Gte(n, L);
         }
 
+        private static IDigest CreateDigest()
+        {
+            return new Sha512Digest();
+        }
+
+        public static IDigest CreatePrehash()
+        {
+            return CreateDigest();
+        }
+
         private static uint Decode24(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -140,9 +158,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             byte[] py = Arrays.CopyOfRange(p, pOff, pOff + PointBytes);
             if (!CheckPointVar(py))
-            {
                 return false;
-            }
 
             int x_0 = (py[PointBytes - 1] & 0x80) >> 7;
             py[PointBytes - 1] &= 0x7F;
@@ -158,15 +174,11 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             X25519Field.AddOne(v);
 
             if (!X25519Field.SqrtRatioVar(u, v, r.x))
-            {
                 return false;
-            }
 
             X25519Field.Normalize(r.x);
             if (x_0 == 1 && X25519Field.IsZeroVar(r.x))
-            {
                 return false;
-            }
 
             if (negate ^ (x_0 != (r.x[0] & 1)))
             {
@@ -182,6 +194,17 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Decode32(k, kOff, n, 0, ScalarUints);
         }
 
+        private static void Dom2(IDigest d, byte phflag, byte[] ctx)
+        {
+            if (ctx != null)
+            {
+                d.BlockUpdate(Dom2Prefix, 0, Dom2Prefix.Length);
+                d.Update(phflag);
+                d.Update((byte)ctx.Length);
+                d.BlockUpdate(ctx, 0, ctx.Length);
+            }
+        }
+
         private static void Encode24(uint n, byte[] bs, int off)
         {
             bs[off] = (byte)(n);
@@ -220,7 +243,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static void GeneratePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff)
         {
-            Sha512Digest d = new Sha512Digest();
+            IDigest d = CreateDigest();
             byte[] h = new byte[d.GetDigestSize()];
 
             d.BlockUpdate(sk, skOff, SecretKeySize);
@@ -286,8 +309,10 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ws;
         }
 
-        private static void ImplSign(Sha512Digest d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        private static void ImplSign(IDigest d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
         {
+            Dom2(d, phflag, ctx);
             d.BlockUpdate(h, ScalarBytes, ScalarBytes);
             d.BlockUpdate(m, mOff, mLen);
             d.DoFinal(h, 0);
@@ -296,6 +321,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             byte[] R = new byte[PointBytes];
             ScalarMultBaseEncoded(r, R, 0);
 
+            Dom2(d, phflag, ctx);
             d.BlockUpdate(R, 0, PointBytes);
             d.BlockUpdate(pk, 0, PointBytes);
             d.BlockUpdate(m, mOff, mLen);
@@ -308,6 +334,90 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Array.Copy(S, 0, sig, sigOff + PointBytes, ScalarBytes);
         }
 
+        private static void ImplSign(byte[] sk, int skOff, byte[] ctx, byte phflag, byte[] m, int mOff, int mLen,
+            byte[] sig, int sigOff)
+        {
+            if (!CheckContextVar(ctx, phflag))
+                throw new ArgumentException("ctx");
+
+            IDigest d = CreateDigest();
+            byte[] h = new byte[d.GetDigestSize()];
+
+            d.BlockUpdate(sk, skOff, SecretKeySize);
+            d.DoFinal(h, 0);
+
+            byte[] s = new byte[ScalarBytes];
+            PruneScalar(h, 0, s);
+
+            byte[] pk = new byte[PointBytes];
+            ScalarMultBaseEncoded(s, pk, 0);
+
+            ImplSign(d, h, s, pk, 0, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
+
+        private static void ImplSign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        {
+            if (!CheckContextVar(ctx, phflag))
+                throw new ArgumentException("ctx");
+
+            IDigest d = CreateDigest();
+            byte[] h = new byte[d.GetDigestSize()];
+
+            d.BlockUpdate(sk, skOff, SecretKeySize);
+            d.DoFinal(h, 0);
+
+            byte[] s = new byte[ScalarBytes];
+            PruneScalar(h, 0, s);
+
+            ImplSign(d, h, s, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
+
+        private static bool ImplVerify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen)
+        {
+            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);
+
+            if (!CheckPointVar(R))
+                return false;
+
+            if (!CheckScalarVar(S))
+                return false;
+
+            PointExt pA = new PointExt();
+            if (!DecodePointVar(pk, pkOff, true, pA))
+                return false;
+
+            IDigest d = CreateDigest();
+            byte[] h = new byte[d.GetDigestSize()];
+
+            Dom2(d, phflag, ctx);
+            d.BlockUpdate(R, 0, PointBytes);
+            d.BlockUpdate(pk, pkOff, PointBytes);
+            d.BlockUpdate(m, mOff, mLen);
+            d.DoFinal(h, 0);
+
+            byte[] k = ReduceScalar(h);
+
+            uint[] nS = new uint[ScalarUints];
+            DecodeScalar(S, 0, nS);
+
+            uint[] nA = new uint[ScalarUints];
+            DecodeScalar(k, 0, nA);
+
+            PointAccum pR = new PointAccum();
+            ScalarMultStraussVar(nS, nA, pA, pR);
+
+            byte[] check = new byte[PointBytes];
+            EncodePoint(pR, check, 0);
+
+            return Arrays.AreEqual(check, R);
+        }
+
         private static void PointAddVar(bool negate, PointExt p, PointAccum r)
         {
             int[] A = X25519Field.Create();
@@ -518,9 +628,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static void Precompute()
         {
             if (precompBase != null)
-            {
                 return;
-            }
 
             // Precomputed table for the base point in verification ladder
             {
@@ -795,9 +903,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 }
 
                 if ((cOff -= PrecompTeeth) < 0)
-                {
                     break;
-                }
 
                 PointDouble(r);
             }
@@ -850,9 +956,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 }
 
                 if (--bit < 0)
-                {
                     break;
-                }
 
                 PointDouble(r);
             }
@@ -860,78 +964,101 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static void Sign(byte[] sk, int skOff, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
         {
-            Sha512Digest d = new Sha512Digest();
-            byte[] h = new byte[d.GetDigestSize()];
+            byte[] ctx = null;
+            byte phflag = 0x00;
 
-            d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0);
+            ImplSign(sk, skOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
 
-            byte[] s = new byte[ScalarBytes];
-            PruneScalar(h, 0, s);
+        public static void Sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        {
+            byte[] ctx = null;
+            byte phflag = 0x00;
 
-            byte[] pk = new byte[PointBytes];
-            ScalarMultBaseEncoded(s, pk, 0);
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
 
-            ImplSign(d, h, s, pk, 0, m, mOff, mLen, sig, sigOff);
+        public static void Sign(byte[] sk, int skOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        {
+            byte phflag = 0x00;
+
+            ImplSign(sk, skOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
         }
 
-        public static void Sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        public static void Sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
         {
-            Sha512Digest d = new Sha512Digest();
-            byte[] h = new byte[d.GetDigestSize()];
+            byte phflag = 0x00;
 
-            d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0);
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
 
-            byte[] s = new byte[ScalarBytes];
-            PruneScalar(h, 0, s);
+        public static void SignPrehash(byte[] sk, int skOff, byte[] ctx, byte[] ph, int phOff, byte[] sig, int sigOff)
+        {
+            byte phflag = 0x01;
 
-            ImplSign(d, h, s, pk, pkOff, m, mOff, mLen, sig, sigOff);
+            ImplSign(sk, skOff, ctx, phflag, ph, phOff, PrehashSize, sig, sigOff);
         }
 
-        public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen)
+        public static void SignPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff, byte[] sig, int sigOff)
         {
-            byte[] R = Arrays.CopyOfRange(sig, sigOff, sigOff + PointBytes);
-            byte[] S = Arrays.CopyOfRange(sig, sigOff + PointBytes, sigOff + SignatureSize);
+            byte phflag = 0x01;
 
-            if (!CheckPointVar(R))
-            {
-                return false;
-            }
-            if (!CheckScalarVar(S))
-            {
-                return false;
-            }
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, ph, phOff, PrehashSize, sig, sigOff);
+        }
 
-            PointExt pA = new PointExt();
-            if (!DecodePointVar(pk, pkOff, true, pA))
-            {
-                return false;
-            }
+        public static void SignPrehash(byte[] sk, int skOff, byte[] ctx, IDigest ph, byte[] sig, int sigOff)
+        {
+            byte[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.DoFinal(m, 0))
+                throw new ArgumentException("ph");
 
-            Sha512Digest d = new Sha512Digest();
-            byte[] h = new byte[d.GetDigestSize()];
+            byte phflag = 0x01;
 
-            d.BlockUpdate(R, 0, PointBytes);
-            d.BlockUpdate(pk, pkOff, PointBytes);
-            d.BlockUpdate(m, mOff, mLen);
-            d.DoFinal(h, 0);
+            ImplSign(sk, skOff, ctx, phflag, m, 0, m.Length, sig, sigOff);
+        }
 
-            byte[] k = ReduceScalar(h);
+        public static void SignPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, IDigest ph, byte[] sig, int sigOff)
+        {
+            byte[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.DoFinal(m, 0))
+                throw new ArgumentException("ph");
 
-            uint[] nS = new uint[ScalarUints];
-            DecodeScalar(S, 0, nS);
+            byte phflag = 0x01;
 
-            uint[] nA = new uint[ScalarUints];
-            DecodeScalar(k, 0, nA);
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, 0, m.Length, sig, sigOff);
+        }
 
-            PointAccum pR = new PointAccum();
-            ScalarMultStraussVar(nS, nA, pA, pR);
+        public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen)
+        {
+            byte[] ctx = null;
+            byte phflag = 0x00;
 
-            byte[] check = new byte[PointBytes];
-            EncodePoint(pR, check, 0);
+            return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen);
+        }
 
-            return Arrays.AreEqual(check, R);
+        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 VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff)
+        {
+            byte phflag = 0x01;
+
+            return ImplVerify(sig, sigOff, pk, pkOff, 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];
+            if (PrehashSize != ph.DoFinal(m, 0))
+                throw new ArgumentException("ph");
+
+            byte phflag = 0x01;
+
+            return ImplVerify(sig, sigOff, pk, pkOff, 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 52c215160..0e56b12a8 100644
--- a/crypto/src/math/ec/rfc8032/Ed448.cs
+++ b/crypto/src/math/ec/rfc8032/Ed448.cs
@@ -2,6 +2,7 @@
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
 
+using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Math.EC.Rfc7748;
 using Org.BouncyCastle.Math.Raw;
@@ -18,6 +19,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         private const int ScalarUints = 14;
         private const int ScalarBytes = ScalarUints * 4 + 1;
 
+        public static readonly int PrehashSize = 64;
         public static readonly int PublicKeySize = PointBytes;
         public static readonly int SecretKeySize = 57;
         public static readonly int SignatureSize = PointBytes + ScalarBytes;
@@ -103,9 +105,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         private static bool CheckPointVar(byte[] p)
         {
             if ((p[PointBytes - 1] & 0x7F) != 0x00)
-            {
                 return false;
-            }
 
             uint[] t = new uint[14];
             Decode32(p, 0, t, 0, 14);
@@ -115,15 +115,23 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         private static bool CheckScalarVar(byte[] s)
         {
             if (s[ScalarBytes - 1] != 0x00)
-            {
                 return false;
-            }
 
             uint[] n = new uint[ScalarUints];
             DecodeScalar(s, 0, n);
             return !Nat.Gte(ScalarUints, n, L);
         }
 
+        public static IXof CreatePrehash()
+        {
+            return CreateXof();
+        }
+
+        private static IXof CreateXof()
+        {
+            return new ShakeDigest(256);
+        }
+
         private static uint Decode16(byte[] bs, int off)
         {
             uint n = bs[off];
@@ -160,9 +168,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         {
             byte[] py = Arrays.CopyOfRange(p, pOff, pOff + PointBytes);
             if (!CheckPointVar(py))
-            {
                 return false;
-            }
 
             int x_0 = (py[PointBytes - 1] & 0x80) >> 7;
             py[PointBytes - 1] &= 0x7F;
@@ -179,15 +185,11 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             X448Field.AddOne(v);
 
             if (!X448Field.SqrtRatioVar(u, v, r.x))
-            {
                 return false;
-            }
 
             X448Field.Normalize(r.x);
             if (x_0 == 1 && X448Field.IsZeroVar(r.x))
-            {
                 return false;
-            }
 
             if (negate ^ (x_0 != (r.x[0] & 1)))
             {
@@ -205,7 +207,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Decode32(k, kOff, n, 0, ScalarUints);
         }
 
-        private static void Dom4(ShakeDigest d, byte x, byte[] y)
+        private static void Dom4(IXof d, byte x, byte[] y)
         {
             d.BlockUpdate(Dom4Prefix, 0, Dom4Prefix.Length);
             d.Update(x);
@@ -251,7 +253,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static void GeneratePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff)
         {
-            ShakeDigest d = new ShakeDigest(256);
+            IXof d = CreateXof();
             byte[] h = new byte[ScalarBytes * 2];
 
             d.BlockUpdate(sk, skOff, SecretKeySize);
@@ -317,10 +319,9 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             return ws;
         }
 
-        private static void ImplSign(ShakeDigest d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        private static void ImplSign(IXof d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
         {
-            byte phflag = 0x00;
-
             Dom4(d, phflag, ctx);
             d.BlockUpdate(h, ScalarBytes, ScalarBytes);
             d.BlockUpdate(m, mOff, mLen);
@@ -343,6 +344,90 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
             Array.Copy(S, 0, sig, sigOff + PointBytes, ScalarBytes);
         }
 
+        private static void ImplSign(byte[] sk, int skOff, byte[] ctx, byte phflag, byte[] m, int mOff, int mLen,
+            byte[] sig, int sigOff)
+        {
+            if (!CheckContextVar(ctx))
+                throw new ArgumentException("ctx");
+
+            IXof d = CreateXof();
+            byte[] h = new byte[ScalarBytes * 2];
+
+            d.BlockUpdate(sk, skOff, SecretKeySize);
+            d.DoFinal(h, 0, h.Length);
+
+            byte[] s = new byte[ScalarBytes];
+            PruneScalar(h, 0, s);
+
+            byte[] pk = new byte[PointBytes];
+            ScalarMultBaseEncoded(s, pk, 0);
+
+            ImplSign(d, h, s, pk, 0, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
+
+        private static void ImplSign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        {
+            if (!CheckContextVar(ctx))
+                throw new ArgumentException("ctx");
+
+            IXof d = CreateXof();
+            byte[] h = new byte[ScalarBytes * 2];
+
+            d.BlockUpdate(sk, skOff, SecretKeySize);
+            d.DoFinal(h, 0, h.Length);
+
+            byte[] s = new byte[ScalarBytes];
+            PruneScalar(h, 0, s);
+
+            ImplSign(d, h, s, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
+
+        private static bool ImplVerify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte phflag,
+            byte[] m, int mOff, int mLen)
+        {
+            if (!CheckContextVar(ctx))
+                throw new ArgumentException("ctx");
+
+            byte[] R = Arrays.CopyOfRange(sig, sigOff, sigOff + PointBytes);
+            byte[] S = Arrays.CopyOfRange(sig, sigOff + PointBytes, sigOff + SignatureSize);
+
+            if (!CheckPointVar(R))
+                return false;
+
+            if (!CheckScalarVar(S))
+                return false;
+
+            PointExt pA = new PointExt();
+            if (!DecodePointVar(pk, pkOff, true, pA))
+                return false;
+
+            IXof d = CreateXof();
+            byte[] h = new byte[ScalarBytes * 2];
+
+            Dom4(d, phflag, ctx);
+            d.BlockUpdate(R, 0, PointBytes);
+            d.BlockUpdate(pk, pkOff, PointBytes);
+            d.BlockUpdate(m, mOff, mLen);
+            d.DoFinal(h, 0, h.Length);
+
+            byte[] k = ReduceScalar(h);
+
+            uint[] nS = new uint[ScalarUints];
+            DecodeScalar(S, 0, nS);
+
+            uint[] nA = new uint[ScalarUints];
+            DecodeScalar(k, 0, nA);
+
+            PointExt pR = new PointExt();
+            ScalarMultStraussVar(nS, nA, pA, pR);
+
+            byte[] check = new byte[PointBytes];
+            EncodePoint(pR, check, 0);
+
+            return Arrays.AreEqual(check, R);
+        }
+
         private static void PointAddVar(bool negate, PointExt p, PointExt r)
         {
             uint[] A = X448Field.Create();
@@ -505,9 +590,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
         public static void Precompute()
         {
             if (precompBase != null)
-            {
                 return;
-            }
 
             PointExt p = new PointExt();
             X448Field.Copy(B_x, 0, p.x, 0);
@@ -907,9 +990,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 }
 
                 if (--cOff < 0)
-                {
                     break;
-                }
 
                 PointDouble(r);
             }
@@ -962,9 +1043,7 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
                 }
 
                 if (--bit < 0)
-                {
                     break;
-                }
 
                 PointDouble(r);
             }
@@ -972,96 +1051,77 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032
 
         public static void Sign(byte[] sk, int skOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
         {
-            if (!CheckContextVar(ctx))
-            {
-                throw new ArgumentException("ctx");
-            }
+            byte phflag = 0x00;
 
-            ShakeDigest d = new ShakeDigest(256);
-            byte[] h = new byte[ScalarBytes * 2];
+            ImplSign(sk, skOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
 
-            d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0, h.Length);
+        public static void Sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        {
+            byte phflag = 0x00;
 
-            byte[] s = new byte[ScalarBytes];
-            PruneScalar(h, 0, s);
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
+        }
 
-            byte[] pk = new byte[PointBytes];
-            ScalarMultBaseEncoded(s, pk, 0);
+        public static void SignPrehash(byte[] sk, int skOff, byte[] ctx, byte[] ph, int phOff, byte[] sig, int sigOff)
+        {
+            byte phflag = 0x01;
 
-            ImplSign(d, h, s, pk, 0, ctx, m, mOff, mLen, sig, sigOff);
+            ImplSign(sk, skOff, ctx, phflag, ph, phOff, PrehashSize, sig, sigOff);
         }
 
-        public static void Sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+        public static void SignPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff, byte[] sig, int sigOff)
         {
-            if (!CheckContextVar(ctx))
-            {
-                throw new ArgumentException("ctx");
-            }
+            byte phflag = 0x01;
 
-            ShakeDigest d = new ShakeDigest(256);
-            byte[] h = new byte[ScalarBytes * 2];
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, ph, phOff, PrehashSize, sig, sigOff);
+        }
 
-            d.BlockUpdate(sk, skOff, SecretKeySize);
-            d.DoFinal(h, 0, h.Length);
+        public static void SignPrehash(byte[] sk, int skOff, byte[] ctx, IXof ph, byte[] sig, int sigOff)
+        {
+            byte[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.DoFinal(m, 0, PrehashSize))
+                throw new ArgumentException("ph");
 
-            byte[] s = new byte[ScalarBytes];
-            PruneScalar(h, 0, s);
+            byte phflag = 0x01;
 
-            ImplSign(d, h, s, pk, pkOff, ctx, m, mOff, mLen, sig, sigOff);
+            ImplSign(sk, skOff, ctx, phflag, m, 0, m.Length, sig, sigOff);
         }
 
-        public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen)
+        public static void SignPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, IXof ph, byte[] sig, int sigOff)
         {
-            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[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.DoFinal(m, 0, PrehashSize))
+                throw new ArgumentException("ph");
 
-            if (!CheckPointVar(R))
-            {
-                return false;
-            }
-            if (!CheckScalarVar(S))
-            {
-                return false;
-            }
+            byte phflag = 0x01;
 
-            PointExt pA = new PointExt();
-            if (!DecodePointVar(pk, pkOff, true, pA))
-            {
-                return false;
-            }
+            ImplSign(sk, skOff, pk, pkOff, ctx, phflag, m, 0, m.Length, sig, sigOff);
+        }
 
+        public static bool Verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen)
+        {
             byte phflag = 0x00;
 
-            ShakeDigest d = new ShakeDigest(256);
-            byte[] h = new byte[ScalarBytes * 2];
-
-            Dom4(d, phflag, ctx);
-            d.BlockUpdate(R, 0, PointBytes);
-            d.BlockUpdate(pk, pkOff, PointBytes);
-            d.BlockUpdate(m, mOff, mLen);
-            d.DoFinal(h, 0, h.Length);
-
-            byte[] k = ReduceScalar(h);
+            return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen);
+        }
 
-            uint[] nS = new uint[ScalarUints];
-            DecodeScalar(S, 0, nS);
+        public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff)
+        {
+            byte phflag = 0x01;
 
-            uint[] nA = new uint[ScalarUints];
-            DecodeScalar(k, 0, nA);
+            return ImplVerify(sig, sigOff, pk, pkOff, ctx, phflag, ph, phOff, PrehashSize);
+        }
 
-            PointExt pR = new PointExt();
-            ScalarMultStraussVar(nS, nA, pA, pR);
+        public static bool VerifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, IXof ph)
+        {
+            byte[] m = new byte[PrehashSize];
+            if (PrehashSize != ph.DoFinal(m, 0, PrehashSize))
+                throw new ArgumentException("ph");
 
-            byte[] check = new byte[PointBytes];
-            EncodePoint(pR, check, 0);
+            byte phflag = 0x01;
 
-            return Arrays.AreEqual(check, R);
+            return ImplVerify(sig, sigOff, pk, pkOff, 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 43ea23988..5a42daeae 100644
--- a/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
+++ b/crypto/test/src/math/ec/rfc8032/test/Ed25519Test.cs
@@ -2,6 +2,7 @@
 
 using NUnit.Framework;
 
+using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
@@ -40,16 +41,93 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
                 Ed25519.Sign(sk, 0, m, 0, mLen, sig1, 0);
                 Ed25519.Sign(sk, 0, pk, 0, m, 0, mLen, sig2, 0);
 
-                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Consistent signatures #" + i);
+                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed25519 consistent signatures #" + i);
 
                 bool shouldVerify = Ed25519.Verify(sig1, 0, pk, 0, m, 0, mLen);
 
-                Assert.IsTrue(shouldVerify, "Consistent sign/verify #" + i);
+                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, "Consistent verification failure #" + i);
+                Assert.IsFalse(shouldNotVerify, "Ed25519 consistent verification failure #" + i);
+            }
+        }
+
+        [Test]
+        public void TestEd25519ctxConsistency()
+        {
+            byte[] sk = new byte[Ed25519.SecretKeySize];
+            byte[] pk = new byte[Ed25519.PublicKeySize];
+            byte[] ctx = new byte[Random.Next() & 7];
+            byte[] m = new byte[255];
+            byte[] sig1 = new byte[Ed25519.SignatureSize];
+            byte[] sig2 = new byte[Ed25519.SignatureSize];
+
+            Random.NextBytes(ctx);
+            Random.NextBytes(m);
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+
+                int mLen = Random.Next() & 255;
+
+                Ed25519.Sign(sk, 0, ctx, m, 0, mLen, sig1, 0);
+                Ed25519.Sign(sk, 0, pk, 0, ctx, m, 0, mLen, sig2, 0);
+
+                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed25519ctx consistent signatures #" + i);
+
+                bool shouldVerify = Ed25519.Verify(sig1, 0, pk, 0, 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);
+            }
+        }
+
+        [Test]
+        public void TestEd25519phConsistency()
+        {
+            byte[] sk = new byte[Ed25519.SecretKeySize];
+            byte[] pk = new byte[Ed25519.PublicKeySize];
+            byte[] ctx = new byte[Random.Next() & 7];
+            byte[] m = new byte[255];
+            byte[] ph = new byte[Ed25519.PrehashSize];
+            byte[] sig1 = new byte[Ed25519.SignatureSize];
+            byte[] sig2 = new byte[Ed25519.SignatureSize];
+
+            Random.NextBytes(ctx);
+            Random.NextBytes(m);
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed25519.GeneratePublicKey(sk, 0, pk, 0);
+
+                int mLen = Random.Next() & 255;
+
+                IDigest prehash = Ed25519.CreatePrehash();
+                prehash.BlockUpdate(m, 0, mLen);
+                prehash.DoFinal(ph, 0);
+
+                Ed25519.SignPrehash(sk, 0, ctx, ph, 0, sig1, 0);
+                Ed25519.SignPrehash(sk, 0, pk, 0, ctx, ph, 0, sig2, 0);
+
+                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed25519ph consistent signatures #" + i);
+
+                bool shouldVerify = Ed25519.VerifyPrehash(sig1, 0, pk, 0, ctx, ph, 0);
+
+                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);
             }
         }
 
@@ -201,18 +279,107 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
                 + "3dca179c138ac17ad9bef1177331a704"),
                 "Ed25519 Vector SHA(abc)");
         }
-      
+
+        [Test]
+        public void TestEd25519ctxVector1()
+        {
+            CheckEd25519ctxVector(
+                ("0305334e381af78f141cb666f6199f57"
+                + "bc3495335a256a95bd2a55bf546663f6"),
+                ("dfc9425e4f968f7f0c29f0259cf5f9ae"
+                + "d6851c2bb4ad8bfb860cfee0ab248292"),
+                "f726936d19c800494e3fdaff20b276a8",
+                "666f6f",
+                ("55a4cc2f70a54e04288c5f4cd1e45a7b"
+                + "b520b36292911876cada7323198dd87a"
+                + "8b36950b95130022907a7fb7c4e9b2d5"
+                + "f6cca685a587b4b21f4b888e4e7edb0d"),
+                "Ed25519ctx Vector #1");
+        }
+
+        [Test]
+        public void TestEd25519ctxVector2()
+        {
+            CheckEd25519ctxVector(
+                ("0305334e381af78f141cb666f6199f57"
+                + "bc3495335a256a95bd2a55bf546663f6"),
+                ("dfc9425e4f968f7f0c29f0259cf5f9ae"
+                + "d6851c2bb4ad8bfb860cfee0ab248292"),
+                "f726936d19c800494e3fdaff20b276a8",
+                "626172",
+                ("fc60d5872fc46b3aa69f8b5b4351d580"
+                + "8f92bcc044606db097abab6dbcb1aee3"
+                + "216c48e8b3b66431b5b186d1d28f8ee1"
+                + "5a5ca2df6668346291c2043d4eb3e90d"),
+                "Ed25519ctx Vector #2");
+        }
+
+        [Test]
+        public void TestEd25519ctxVector3()
+        {
+            CheckEd25519ctxVector(
+                ("0305334e381af78f141cb666f6199f57"
+                + "bc3495335a256a95bd2a55bf546663f6"),
+                ("dfc9425e4f968f7f0c29f0259cf5f9ae"
+                + "d6851c2bb4ad8bfb860cfee0ab248292"),
+                "508e9e6882b979fea900f62adceaca35",
+                "666f6f",
+                ("8b70c1cc8310e1de20ac53ce28ae6e72"
+                + "07f33c3295e03bb5c0732a1d20dc6490"
+                + "8922a8b052cf99b7c4fe107a5abb5b2c"
+                + "4085ae75890d02df26269d8945f84b0b"),
+                "Ed25519ctx Vector #3");
+        }
+
+        [Test]
+        public void TestEd25519ctxVector4()
+        {
+            CheckEd25519ctxVector(
+                ("ab9c2853ce297ddab85c993b3ae14bca"
+                + "d39b2c682beabc27d6d4eb20711d6560"),
+                ("0f1d1274943b91415889152e893d80e9"
+                + "3275a1fc0b65fd71b4b0dda10ad7d772"),
+                "f726936d19c800494e3fdaff20b276a8",
+                "666f6f",
+                ("21655b5f1aa965996b3f97b3c849eafb"
+                + "a922a0a62992f73b3d1b73106a84ad85"
+                + "e9b86a7b6005ea868337ff2d20a7f5fb"
+                + "d4cd10b0be49a68da2b2e0dc0ad8960f"),
+                "Ed25519ctx Vector #4");
+        }
+
+        [Test]
+        public void TestEd25519phVector1()
+        {
+            CheckEd25519phVector(
+                ("833fe62409237b9d62ec77587520911e"
+                + "9a759cec1d19755b7da901b96dca3d42"),
+                ("ec172b93ad5e563bf4932c70e1245034"
+                + "c35467ef2efd4d64ebf819683467e2bf"),
+                "616263",
+                "",
+                ("98a70222f0b8121aa9d30f813d683f80"
+                + "9e462b469c7ff87639499bb94e6dae41"
+                + "31f85042463c2a355a2003d062adf5aa"
+                + "a10b8c61e636062aaad11c2a26083406"),
+                "Ed25519ph Vector #1");
+        }
+
         private static void CheckEd25519Vector(string sSK, string sPK, string sM, string sSig, string text)
         {
             byte[] sk = Hex.Decode(sSK);
-
             byte[] pk = Hex.Decode(sPK);
+
             byte[] pkGen = new byte[Ed25519.PublicKeySize];
             Ed25519.GeneratePublicKey(sk, 0, pkGen, 0);
             Assert.IsTrue(Arrays.AreEqual(pk, pkGen), text);
          
 			byte[] m = Hex.Decode(sM);
             byte[] sig = Hex.Decode(sSig);
+
+            byte[] badsig = Arrays.Clone(sig);
+            badsig[Ed25519.SignatureSize - 1] ^= 0x80;
+
             byte[] sigGen = new byte[Ed25519.SignatureSize];
             Ed25519.Sign(sk, 0, m, 0, m.Length, sigGen, 0);
             Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
@@ -223,9 +390,109 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             bool shouldVerify = Ed25519.Verify(sig, 0, pk, 0, m, 0, m.Length);
             Assert.IsTrue(shouldVerify, text);
 
-			sig[Ed25519.SignatureSize - 1] ^= 0x80;
-            bool shouldNotVerify = Ed25519.Verify(sig, 0, pk, 0, m, 0, m.Length);
+            bool shouldNotVerify = Ed25519.Verify(badsig, 0, pk, 0, m, 0, m.Length);
+            Assert.IsFalse(shouldNotVerify, text);
+        }
+
+        private static void CheckEd25519ctxVector(string sSK, string sPK, string sM, string sCTX, string sSig, string text)
+        {
+            byte[] sk = Hex.Decode(sSK);
+            byte[] pk = Hex.Decode(sPK);
+
+            byte[] pkGen = new byte[Ed25519.PublicKeySize];
+            Ed25519.GeneratePublicKey(sk, 0, pkGen, 0);
+            Assert.IsTrue(Arrays.AreEqual(pk, pkGen), text);
+
+            byte[] m = Hex.Decode(sM);
+            byte[] ctx = Hex.Decode(sCTX);
+            byte[] sig = Hex.Decode(sSig);
+
+            byte[] badsig = Arrays.Clone(sig);
+            badsig[Ed25519.SignatureSize - 1] ^= 0x80;
+
+            byte[] sigGen = new byte[Ed25519.SignatureSize];
+            Ed25519.Sign(sk, 0, ctx, m, 0, m.Length, sigGen, 0);
+            Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+
+            Ed25519.Sign(sk, 0, pk, 0, ctx, m, 0, m.Length, sigGen, 0);
+            Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+
+            bool shouldVerify = Ed25519.Verify(sig, 0, pk, 0, ctx, m, 0, m.Length);
+            Assert.IsTrue(shouldVerify, text);
+
+            bool shouldNotVerify = Ed25519.Verify(badsig, 0, pk, 0, ctx, m, 0, m.Length);
             Assert.IsFalse(shouldNotVerify, text);
         }
+
+        private static void CheckEd25519phVector(string sSK, string sPK, string sM, string sCTX, string sSig, string text)
+        {
+            byte[] sk = Hex.Decode(sSK);
+            byte[] pk = Hex.Decode(sPK);
+
+            byte[] pkGen = new byte[Ed25519.PublicKeySize];
+            Ed25519.GeneratePublicKey(sk, 0, pkGen, 0);
+            Assert.IsTrue(Arrays.AreEqual(pk, pkGen), text);
+
+            byte[] m = Hex.Decode(sM);
+            byte[] ctx = Hex.Decode(sCTX);
+            byte[] sig = Hex.Decode(sSig);
+
+            byte[] badsig = Arrays.Clone(sig);
+            badsig[Ed25519.SignatureSize - 1] ^= 0x80;
+
+            byte[] sigGen = new byte[Ed25519.SignatureSize];
+
+            {
+                IDigest prehash = Ed25519.CreatePrehash();
+                prehash.BlockUpdate(m, 0, m.Length);
+
+                byte[] ph = new byte[Ed25519.PrehashSize];
+                prehash.DoFinal(ph, 0);
+
+                Ed25519.SignPrehash(sk, 0, ctx, ph, 0, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+
+                Ed25519.SignPrehash(sk, 0, pk, 0, ctx, ph, 0, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+
+                bool shouldVerify = Ed25519.VerifyPrehash(sig, 0, pk, 0, ctx, ph, 0);
+                Assert.IsTrue(shouldVerify, text);
+
+                bool shouldNotVerify = Ed25519.VerifyPrehash(badsig, 0, pk, 0, ctx, ph, 0);
+                Assert.IsFalse(shouldNotVerify, text);
+            }
+
+            {
+                IDigest ph = Ed25519.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                Ed25519.SignPrehash(sk, 0, ctx, ph, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+            }
+
+            {
+                IDigest ph = Ed25519.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                Ed25519.SignPrehash(sk, 0, pk, 0, ctx, ph, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+            }
+
+            {
+                IDigest ph = Ed25519.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                bool shouldVerify = Ed25519.VerifyPrehash(sig, 0, pk, 0, ctx, ph);
+                Assert.IsTrue(shouldVerify, text);
+            }
+
+            {
+                IDigest ph = Ed25519.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                bool shouldNotVerify = Ed25519.VerifyPrehash(badsig, 0, pk, 0, ctx, ph);
+                Assert.IsFalse(shouldNotVerify, text);
+            }
+        }
     }
 }
diff --git a/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs b/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
index 98c487c09..826f76345 100644
--- a/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
+++ b/crypto/test/src/math/ec/rfc8032/test/Ed448Test.cs
@@ -2,6 +2,7 @@
 
 using NUnit.Framework;
 
+using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
@@ -42,16 +43,57 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
                 Ed448.Sign(sk, 0, ctx, m, 0, mLen, sig1, 0);
                 Ed448.Sign(sk, 0, pk, 0, ctx, m, 0, mLen, sig2, 0);
 
-                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Consistent signatures #" + i);
+                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed448 consistent signatures #" + i);
 
                 bool shouldVerify = Ed448.Verify(sig1, 0, pk, 0, ctx, m, 0, mLen);
 
-                Assert.IsTrue(shouldVerify, "Consistent sign/verify #" + i);
+                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, "Consistent verification failure #" + i);
+                Assert.IsFalse(shouldNotVerify, "Ed448 consistent verification failure #" + i);
+            }
+        }
+
+        [Test]
+        public void TestEd448phConsistency()
+        {
+            byte[] sk = new byte[Ed448.SecretKeySize];
+            byte[] pk = new byte[Ed448.PublicKeySize];
+            byte[] ctx = new byte[Random.Next() & 7];
+            byte[] m = new byte[255];
+            byte[] ph = new byte[Ed448.PrehashSize];
+            byte[] sig1 = new byte[Ed448.SignatureSize];
+            byte[] sig2 = new byte[Ed448.SignatureSize];
+
+            Random.NextBytes(ctx);
+            Random.NextBytes(m);
+
+            for (int i = 0; i < 10; ++i)
+            {
+                Random.NextBytes(sk);
+                Ed448.GeneratePublicKey(sk, 0, pk, 0);
+
+                int mLen = Random.Next() & 255;
+
+                IXof prehash = Ed448.CreatePrehash();
+                prehash.BlockUpdate(m, 0, mLen);
+                prehash.DoFinal(ph, 0, ph.Length);
+
+                Ed448.SignPrehash(sk, 0, ctx, ph, 0, sig1, 0);
+                Ed448.SignPrehash(sk, 0, pk, 0, ctx, ph, 0, sig2, 0);
+
+                Assert.IsTrue(Arrays.AreEqual(sig1, sig2), "Ed448ph consistent signatures #" + i);
+
+                bool shouldVerify = Ed448.VerifyPrehash(sig1, 0, pk, 0, 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);
             }
         }
 
@@ -370,11 +412,61 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
                 "Ed448 Vector #1023");
         }
 
+        [Test]
+        public void TestEd448phVector1()
+        {
+            CheckEd448phVector(
+                ("833fe62409237b9d62ec77587520911e"
+                + "9a759cec1d19755b7da901b96dca3d42"
+                + "ef7822e0d5104127dc05d6dbefde69e3"
+                + "ab2cec7c867c6e2c49"),
+                ("259b71c19f83ef77a7abd26524cbdb31"
+                + "61b590a48f7d17de3ee0ba9c52beb743"
+                + "c09428a131d6b1b57303d90d8132c276"
+                + "d5ed3d5d01c0f53880"),
+                "616263",
+                "",
+                ("822f6901f7480f3d5f562c592994d969"
+                + "3602875614483256505600bbc281ae38"
+                + "1f54d6bce2ea911574932f52a4e6cadd"
+                + "78769375ec3ffd1b801a0d9b3f4030cd"
+                + "433964b6457ea39476511214f97469b5"
+                + "7dd32dbc560a9a94d00bff07620464a3"
+                + "ad203df7dc7ce360c3cd3696d9d9fab9"
+                + "0f00"),
+                "Ed448ph Vector #1");
+        }
+
+        [Test]
+        public void TestEd448phVector2()
+        {
+            CheckEd448phVector(
+                ("833fe62409237b9d62ec77587520911e"
+                + "9a759cec1d19755b7da901b96dca3d42"
+                + "ef7822e0d5104127dc05d6dbefde69e3"
+                + "ab2cec7c867c6e2c49"),
+                ("259b71c19f83ef77a7abd26524cbdb31"
+                + "61b590a48f7d17de3ee0ba9c52beb743"
+                + "c09428a131d6b1b57303d90d8132c276"
+                + "d5ed3d5d01c0f53880"),
+                "616263",
+                "666f6f",
+                ("c32299d46ec8ff02b54540982814dce9"
+                + "a05812f81962b649d528095916a2aa48"
+                + "1065b1580423ef927ecf0af5888f90da"
+                + "0f6a9a85ad5dc3f280d91224ba9911a3"
+                + "653d00e484e2ce232521481c8658df30"
+                + "4bb7745a73514cdb9bf3e15784ab7128"
+                + "4f8d0704a608c54a6b62d97beb511d13"
+                + "2100"),
+                "Ed448ph Vector #2");
+        }
+
         private static void CheckEd448Vector(string sSK, string sPK, string sM, string sCTX, string sSig, string text)
         {
             byte[] sk = Hex.Decode(sSK);
-
             byte[] pk = Hex.Decode(sPK);
+
             byte[] pkGen = new byte[Ed448.PublicKeySize];
             Ed448.GeneratePublicKey(sk, 0, pkGen, 0);
             Assert.IsTrue(Arrays.AreEqual(pk, pkGen), text);
@@ -383,6 +475,10 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             byte[] ctx = Hex.Decode(sCTX);
             byte[] sig = Hex.Decode(sSig);
             byte[] sigGen = new byte[Ed448.SignatureSize];
+
+            byte[] badsig = Arrays.Clone(sig);
+            badsig[Ed448.SignatureSize - 1] ^= 0x80;
+
             Ed448.Sign(sk, 0, ctx, m, 0, m.Length, sigGen, 0);
             Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
 
@@ -392,9 +488,79 @@ namespace Org.BouncyCastle.Math.EC.Rfc8032.Tests
             bool shouldVerify = Ed448.Verify(sig, 0, pk, 0, ctx, m, 0, m.Length);
             Assert.IsTrue(shouldVerify, text);
 
-            sig[Ed448.SignatureSize - 1] ^= 0x80;
-            bool shouldNotVerify = Ed448.Verify(sig, 0, pk, 0, ctx, m, 0, m.Length);
+            bool shouldNotVerify = Ed448.Verify(badsig, 0, pk, 0, ctx, m, 0, m.Length);
             Assert.IsFalse(shouldNotVerify, text);
         }
+
+        private static void CheckEd448phVector(string sSK, string sPK, string sM, string sCTX, string sSig, string text)
+        {
+            byte[] sk = Hex.Decode(sSK);
+            byte[] pk = Hex.Decode(sPK);
+
+            byte[] pkGen = new byte[Ed448.PublicKeySize];
+            Ed448.GeneratePublicKey(sk, 0, pkGen, 0);
+            Assert.IsTrue(Arrays.AreEqual(pk, pkGen), text);
+
+            byte[] m = Hex.Decode(sM);
+            byte[] ctx = Hex.Decode(sCTX);
+            byte[] sig = Hex.Decode(sSig);
+
+            byte[] badsig = Arrays.Clone(sig);
+            badsig[Ed448.SignatureSize - 1] ^= 0x80;
+
+            byte[] sigGen = new byte[Ed448.SignatureSize];
+
+            {
+                IXof prehash = Ed448.CreatePrehash();
+                prehash.BlockUpdate(m, 0, m.Length);
+
+                byte[] ph = new byte[Ed448.PrehashSize];
+                prehash.DoFinal(ph, 0, ph.Length);
+
+                Ed448.SignPrehash(sk, 0, ctx, ph, 0, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+
+                Ed448.SignPrehash(sk, 0, pk, 0, ctx, ph, 0, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+
+                bool shouldVerify = Ed448.VerifyPrehash(sig, 0, pk, 0, ctx, ph, 0);
+                Assert.IsTrue(shouldVerify, text);
+
+                bool shouldNotVerify = Ed448.VerifyPrehash(badsig, 0, pk, 0, ctx, ph, 0);
+                Assert.IsFalse(shouldNotVerify, text);
+            }
+
+            {
+                IXof ph = Ed448.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                Ed448.SignPrehash(sk, 0, ctx, ph, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+            }
+
+            {
+                IXof ph = Ed448.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                Ed448.SignPrehash(sk, 0, pk, 0, ctx, ph, sigGen, 0);
+                Assert.IsTrue(Arrays.AreEqual(sig, sigGen), text);
+            }
+
+            {
+                IXof ph = Ed448.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                bool shouldVerify = Ed448.VerifyPrehash(sig, 0, pk, 0, ctx, ph);
+                Assert.IsTrue(shouldVerify, text);
+            }
+
+            {
+                IXof ph = Ed448.CreatePrehash();
+                ph.BlockUpdate(m, 0, m.Length);
+
+                bool shouldNotVerify = Ed448.VerifyPrehash(badsig, 0, pk, 0, ctx, ph);
+                Assert.IsFalse(shouldNotVerify, text);
+            }
+        }
     }
 }