summary refs log tree commit diff
path: root/crypto/src/openpgp
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-11-05 15:40:09 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-11-05 15:40:09 +0700
commit7f43ba84bd30b99d0e957920aa660a45f998c522 (patch)
tree7c6f4738d69ab3def1416c397752a3b63b66738f /crypto/src/openpgp
parentUpdate package icon (diff)
downloadBouncyCastle.NET-ed25519-7f43ba84bd30b99d0e957920aa660a45f998c522.tar.xz
Port OpenPGP support for XDH, EdDSA from bc-java
- see https://github.com/bcgit/bc-csharp/issues/345
Diffstat (limited to 'crypto/src/openpgp')
-rw-r--r--crypto/src/openpgp/EdDsaSigner.cs72
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataGenerator.cs129
-rw-r--r--crypto/src/openpgp/PgpKdfParameters.cs21
-rw-r--r--crypto/src/openpgp/PgpObjectFactory.cs5
-rw-r--r--crypto/src/openpgp/PgpOnePassSignature.cs78
-rw-r--r--crypto/src/openpgp/PgpPbeEncryptedData.cs5
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs311
-rw-r--r--crypto/src/openpgp/PgpPublicKeyEncryptedData.cs156
-rw-r--r--crypto/src/openpgp/PgpPublicKeyRing.cs26
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs105
-rw-r--r--crypto/src/openpgp/PgpSecretKeyRing.cs47
-rw-r--r--crypto/src/openpgp/PgpSignature.cs151
-rw-r--r--crypto/src/openpgp/PgpSignatureGenerator.cs193
-rw-r--r--crypto/src/openpgp/PgpUtilities.cs57
-rw-r--r--crypto/src/openpgp/PgpV3SignatureGenerator.cs105
-rw-r--r--crypto/src/openpgp/Rfc6637Utilities.cs14
16 files changed, 1012 insertions, 463 deletions
diff --git a/crypto/src/openpgp/EdDsaSigner.cs b/crypto/src/openpgp/EdDsaSigner.cs
new file mode 100644
index 000000000..0e15ac609
--- /dev/null
+++ b/crypto/src/openpgp/EdDsaSigner.cs
@@ -0,0 +1,72 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    internal sealed class EdDsaSigner
+        : ISigner
+    {
+        private readonly ISigner m_signer;
+        private readonly IDigest m_digest;
+        private readonly byte[] m_digBuf;
+
+        internal EdDsaSigner(ISigner signer, IDigest digest)
+        {
+            m_signer = signer;
+            m_digest = digest;
+            m_digBuf = new byte[digest.GetDigestSize()];
+        }
+
+        public string AlgorithmName => m_signer.AlgorithmName;
+
+        public void Init(bool forSigning, ICipherParameters cipherParameters)
+        {
+            m_signer.Init(forSigning, cipherParameters);
+            m_digest.Reset();
+        }
+
+        public void Update(byte b)
+        {
+            m_digest.Update(b);
+        }
+
+        public void BlockUpdate(byte[] input, int inOff, int inLen)
+        {
+            m_digest.BlockUpdate(input, inOff, inLen);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            m_digest.BlockUpdate(input);
+        }
+#endif
+
+        public byte[] GenerateSignature()
+        {
+            m_digest.DoFinal(m_digBuf, 0);
+
+            m_signer.BlockUpdate(m_digBuf, 0, m_digBuf.Length);
+
+            return m_signer.GenerateSignature();
+        }
+
+        public bool VerifySignature(byte[] signature)
+        {
+            m_digest.DoFinal(m_digBuf, 0);
+
+            m_signer.BlockUpdate(m_digBuf, 0, m_digBuf.Length);
+
+            return m_signer.VerifySignature(signature);
+        }
+
+        public void Reset()
+        {
+            Arrays.Clear(m_digBuf);
+            m_signer.Reset();
+            m_digest.Reset();
+        }
+    }
+}
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index 589895522..69e0d5dbc 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -2,17 +2,21 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
+using Org.BouncyCastle.Asn1.EdEC;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.IO;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>Generator for encrypted objects.</remarks>
+    /// <remarks>Generator for encrypted objects.</remarks>
     public class PgpEncryptedDataGenerator
 		: IStreamGenerator
     {
@@ -99,54 +103,108 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
             private byte[] EncryptSessionInfo(byte[] sessionInfo, SecureRandom random)
             {
+                var cryptoPublicKey = pubKey.GetKey();
+
                 if (pubKey.Algorithm != PublicKeyAlgorithmTag.ECDH)
                 {
                     IBufferedCipher c;
 				    switch (pubKey.Algorithm)
                     {
-                        case PublicKeyAlgorithmTag.RsaEncrypt:
-                        case PublicKeyAlgorithmTag.RsaGeneral:
-                            c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
-                            break;
-                        case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                        case PublicKeyAlgorithmTag.ElGamalGeneral:
-                            c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
-                            break;
-                        case PublicKeyAlgorithmTag.Dsa:
-                            throw new PgpException("Can't use DSA for encryption.");
-                        case PublicKeyAlgorithmTag.ECDsa:
-                            throw new PgpException("Can't use ECDSA for encryption.");
-                        default:
-                            throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+                        c = CipherUtilities.GetCipher("RSA//PKCS1Padding");
+                        break;
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+                        c = CipherUtilities.GetCipher("ElGamal/ECB/PKCS1Padding");
+                        break;
+                    case PublicKeyAlgorithmTag.Dsa:
+                        throw new PgpException("Can't use DSA for encryption.");
+                    case PublicKeyAlgorithmTag.ECDsa:
+                        throw new PgpException("Can't use ECDSA for encryption.");
+                    case PublicKeyAlgorithmTag.EdDsa:
+                        throw new PgpException("Can't use EdDSA for encryption.");
+                    default:
+                        throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
                     }
 
-                    AsymmetricKeyParameter akp = pubKey.GetKey();
-				    c.Init(true, new ParametersWithRandom(akp, random));
+				    c.Init(true, new ParametersWithRandom(cryptoPublicKey, random));
                     return c.DoFinal(sessionInfo);
                 }
 
-                ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+                ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)pubKey.PublicKeyPacket.Key;
+                var curveOid = ecPubKey.CurveOid;
+
+                if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                    CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+                {
+                    X25519KeyPairGenerator gen = new X25519KeyPairGenerator();
+                    gen.Init(new X25519KeyGenerationParameters(random));
+
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
+
+                    X25519Agreement agreement = new X25519Agreement();
+                    agreement.Init(ephKp.Private);
+
+                    byte[] secret = new byte[agreement.AgreementSize];
+                    agreement.CalculateAgreement(cryptoPublicKey, secret, 0);
+
+                    byte[] ephPubEncoding = new byte[1 + X25519PublicKeyParameters.KeySize];
+                    ephPubEncoding[0] = 0x40;
+                    ((X25519PublicKeyParameters)ephKp.Public).Encode(ephPubEncoding, 1);
+
+                    return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random);
+                }
+                else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                {
+                    X448KeyPairGenerator gen = new X448KeyPairGenerator();
+                    gen.Init(new X448KeyGenerationParameters(random));
+
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
+
+                    X448Agreement agreement = new X448Agreement();
+                    agreement.Init(ephKp.Private);
+
+                    byte[] secret = new byte[agreement.AgreementSize];
+                    agreement.CalculateAgreement(cryptoPublicKey, secret, 0);
 
-                // Generate the ephemeral key pair
-                IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
-                gen.Init(new ECKeyGenerationParameters(ecKey.CurveOid, random));
+                    byte[] ephPubEncoding = new byte[1 + X448PublicKeyParameters.KeySize];
+                    ephPubEncoding[0] = 0x40;
+                    ((X448PublicKeyParameters)ephKp.Public).Encode(ephPubEncoding, 1);
 
-                AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
-                ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.Private;
-                ECPublicKeyParameters ephPub = (ECPublicKeyParameters)ephKp.Public;
+                    return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random);
+                }
+                else
+                {
+                    // Generate the ephemeral key pair
+                    ECDomainParameters ecParams = ((ECPublicKeyParameters)cryptoPublicKey).Parameters;
+                    ECKeyPairGenerator gen = new ECKeyPairGenerator();
+                    gen.Init(new ECKeyGenerationParameters(ecParams, random));
 
-                ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey.GetKey();
-                ECPoint S = pub.Q.Multiply(ephPriv.D).Normalize();
+                    AsymmetricCipherKeyPair ephKp = gen.GenerateKeyPair();
 
-                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, S));
+                    ECDHBasicAgreement agreement = new ECDHBasicAgreement();
+                    agreement.Init(ephKp.Private);
+                    BigInteger S = agreement.CalculateAgreement(cryptoPublicKey);
+                    byte[] secret = BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), S);
+
+                    byte[] ephPubEncoding = ((ECPublicKeyParameters)ephKp.Public).Q.GetEncoded(false);
+                    return EncryptSessionInfo(ecPubKey, sessionInfo, secret, ephPubEncoding, random);
+                }
+            }
+
+            private byte[] EncryptSessionInfo(ECDHPublicBcpgKey ecPubKey, byte[] sessionInfo, byte[] secret,
+                byte[] ephPubEncoding, SecureRandom random)
+            {
+                var key = new KeyParameter(Rfc6637Utilities.CreateKey(pubKey.PublicKeyPacket, secret));
 
-                IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
+                IWrapper w = PgpUtilities.CreateWrapper(ecPubKey.SymmetricKeyAlgorithm);
                 w.Init(true, new ParametersWithRandom(key, random));
 
                 byte[] paddedSessionData = PgpPad.PadSessionData(sessionInfo, sessionKeyObfuscation);
 
                 byte[] C = w.Wrap(paddedSessionData, 0, paddedSessionData.Length);
-                byte[] VB = new MPInteger(MPInteger.ToMpiBigInteger(ephPub.Q)).GetEncoded();
+                byte[] VB = new MPInteger(new BigInteger(1, ephPubEncoding)).GetEncoded();
 
                 byte[] rv = new byte[VB.Length + 1 + C.Length];
 
@@ -165,7 +223,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                 case PublicKeyAlgorithmTag.RsaEncrypt:
                 case PublicKeyAlgorithmTag.RsaGeneral:
-                    data = new byte[][] { ConvertToEncodedMpi(encryptedSessionInfo) };
+                    data = new byte[1][] { ConvertToEncodedMpi(encryptedSessionInfo) };
                     break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
@@ -176,13 +234,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     Array.Copy(encryptedSessionInfo, 0, b1, 0, halfLength);
                     Array.Copy(encryptedSessionInfo, halfLength, b2, 0, halfLength);
 
-                    data = new byte[][] {
+                    data = new byte[2][] {
                         ConvertToEncodedMpi(b1),
                         ConvertToEncodedMpi(b2),
                     };
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
-                    data = new byte[][]{ encryptedSessionInfo };
+                    data = new byte[1][]{ encryptedSessionInfo };
                     break;
                 default:
                     throw new PgpException("unknown asymmetric algorithm: " + pubKey.Algorithm);
@@ -489,8 +547,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 				if (withIntegrityPacket)
                 {
-					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
-					IDigest digest = DigestUtilities.GetDigest(digestName);
+                    IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 					myOut = digestOut = new DigestStream(myOut, null, digest);
                 }
 
diff --git a/crypto/src/openpgp/PgpKdfParameters.cs b/crypto/src/openpgp/PgpKdfParameters.cs
new file mode 100644
index 000000000..c78448939
--- /dev/null
+++ b/crypto/src/openpgp/PgpKdfParameters.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp
+{
+    internal sealed class PgpKdfParameters
+        //: IPgpAlgorithmParameters
+    {
+        private readonly HashAlgorithmTag m_hashAlgorithm;
+        private readonly SymmetricKeyAlgorithmTag m_symmetricWrapAlgorithm;
+
+        public PgpKdfParameters(HashAlgorithmTag hashAlgorithm, SymmetricKeyAlgorithmTag symmetricWrapAlgorithm)
+        {
+            m_hashAlgorithm = hashAlgorithm;
+            m_symmetricWrapAlgorithm = symmetricWrapAlgorithm;
+        }
+
+        public HashAlgorithmTag HashAlgorithm => m_hashAlgorithm;
+
+        public SymmetricKeyAlgorithmTag SymmetricWrapAlgorithm => m_symmetricWrapAlgorithm;
+    }
+}
diff --git a/crypto/src/openpgp/PgpObjectFactory.cs b/crypto/src/openpgp/PgpObjectFactory.cs
index f7bf89507..068b85154 100644
--- a/crypto/src/openpgp/PgpObjectFactory.cs
+++ b/crypto/src/openpgp/PgpObjectFactory.cs
@@ -72,9 +72,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             case PacketTag.PublicKey:
                 return new PgpPublicKeyRing(bcpgIn);
-			// TODO Make PgpPublicKey a PgpObject or return a PgpPublicKeyRing
-			//case PacketTag.PublicSubkey:
-			//	return PgpPublicKeyRing.ReadSubkey(bcpgIn);
+			case PacketTag.PublicSubkey:
+				return PgpPublicKeyRing.ReadSubkey(bcpgIn);
             case PacketTag.CompressedData:
                 return new PgpCompressedData(bcpgIn);
             case PacketTag.LiteralData:
diff --git a/crypto/src/openpgp/PgpOnePassSignature.cs b/crypto/src/openpgp/PgpOnePassSignature.cs
index 2fab5137e..c14e72bf7 100644
--- a/crypto/src/openpgp/PgpOnePassSignature.cs
+++ b/crypto/src/openpgp/PgpOnePassSignature.cs
@@ -2,7 +2,9 @@ using System;
 using System.IO;
 
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
@@ -11,10 +13,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
     {
         private static OnePassSignaturePacket Cast(Packet packet)
         {
-            if (!(packet is OnePassSignaturePacket))
-                throw new IOException("unexpected packet in stream: " + packet);
+            if (packet is OnePassSignaturePacket onePassSignaturePacket)
+                return onePassSignaturePacket;
 
-            return (OnePassSignaturePacket)packet;
+            throw new IOException("unexpected packet in stream: " + packet);
         }
 
         private readonly OnePassSignaturePacket sigPack;
@@ -36,15 +38,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         }
 
 		/// <summary>Initialise the signature object for verification.</summary>
-        public void InitVerify(
-            PgpPublicKey pubKey)
+        public void InitVerify(PgpPublicKey pubKey)
         {
 			lastb = 0;
+            AsymmetricKeyParameter key = pubKey.GetKey();
 
-			try
+            try
 			{
-				sig = SignerUtilities.GetSigner(
-					PgpUtilities.GetSignatureName(sigPack.KeyAlgorithm, sigPack.HashAlgorithm));
+				sig = PgpUtilities.CreateSigner(sigPack.KeyAlgorithm, sigPack.HashAlgorithm, key);
 			}
 			catch (Exception e)
 			{
@@ -53,7 +54,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			try
             {
-                sig.Init(false, pubKey.GetKey());
+                sig.Init(false, key);
             }
 			catch (InvalidKeyException e)
             {
@@ -61,12 +62,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		public void Update(
-            byte b)
+		public void Update(byte b)
         {
 			if (signatureType == PgpSignature.CanonicalTextDocument)
 			{
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
 			}
 			else
 			{
@@ -74,18 +74,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			}
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
@@ -96,51 +95,57 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
 			sig.Update((byte)'\r');
 			sig.Update((byte)'\n');
 		}
 
-		public void Update(
-            byte[] bytes)
+		public void Update(params byte[] bytes)
+        {
+            Update(bytes, 0, bytes.Length);
+        }
+
+        public void Update(byte[] bytes, int off, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(bytes.AsSpan(off, length));
+#else
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                for (int i = 0; i != bytes.Length; i++)
+                int finish = off + length;
+
+                for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(bytes[i]);
+                    DoCanonicalUpdateByte(bytes[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(bytes, 0, bytes.Length);
+                sig.BlockUpdate(bytes, off, length);
             }
+#endif
         }
 
-        public void Update(
-            byte[]  bytes,
-            int     off,
-            int     length)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                int finish = off + length;
-
-                for (int i = off; i != finish; i++)
+                for (int i = 0; i < input.Length; ++i)
                 {
-                    doCanonicalUpdateByte(bytes[i]);
+                    DoCanonicalUpdateByte(input[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(bytes, off, length);
+                sig.BlockUpdate(input);
             }
         }
+#endif
 
-		/// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
-        public bool Verify(
-            PgpSignature pgpSig)
+        /// <summary>Verify the calculated signature against the passed in PgpSignature.</summary>
+        public bool Verify(PgpSignature pgpSig)
         {
             byte[] trailer = pgpSig.GetSignatureTrailer();
 
@@ -171,15 +176,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 		public byte[] GetEncoded()
         {
-            MemoryStream bOut = new MemoryStream();
+            var bOut = new MemoryStream();
 
             Encode(bOut);
 
             return bOut.ToArray();
         }
 
-		public void Encode(
-            Stream outStr)
+		public void Encode(Stream outStr)
         {
             BcpgOutputStream.Wrap(outStr).WritePacket(sigPack);
         }
diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs
index f43f2f512..7920f54ea 100644
--- a/crypto/src/openpgp/PgpPbeEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs
@@ -97,10 +97,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				{
 					truncStream = new TruncatedStream(encStream);
 
-					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
-					IDigest digest = DigestUtilities.GetDigest(digestName);
+                    IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 
-					encStream = new DigestStream(truncStream, digest, null);
+                    encStream = new DigestStream(truncStream, digest, null);
 				}
 
 				if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index cdb8efd36..400cda071 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -3,13 +3,18 @@ using System.Collections.Generic;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
+using Org.BouncyCastle.Asn1.EdEC;
 using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Math.EC.Rfc7748;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Collections;
@@ -18,7 +23,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
     /// <remarks>General class to handle a PGP public key object.</remarks>
     public class PgpPublicKey
+        : PgpObject
     {
+        // We default to these as they are specified as mandatory in RFC 6631.
+        private static readonly PgpKdfParameters DefaultKdfParameters = new PgpKdfParameters(HashAlgorithmTag.Sha256,
+            SymmetricKeyAlgorithmTag.Aes128);
+
         public static byte[] CalculateFingerprint(PublicKeyPacket publicPk)
         {
             IBcpgKey key = publicPk.Key;
@@ -30,7 +40,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 try
                 {
-                    digest = DigestUtilities.GetDigest("MD5");
+                    digest = PgpUtilities.CreateDigest(HashAlgorithmTag.MD5);
+
                     UpdateDigest(digest, rK.Modulus);
                     UpdateDigest(digest, rK.PublicExponent);
                 }
@@ -45,7 +56,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     byte[] kBytes = publicPk.GetEncodedContents();
 
-                    digest = DigestUtilities.GetDigest("SHA1");
+                    digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 
                     digest.Update(0x99);
                     digest.Update((byte)(kBytes.Length >> 8));
@@ -124,27 +135,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength;
                 }
-                else if (key is ECPublicBcpgKey)
+                else if (key is EdDsaPublicBcpgKey eddsaK)
                 {
-                    DerObjectIdentifier curveOid = ((ECPublicBcpgKey)key).CurveOid;
-                    if (GnuObjectIdentifiers.Ed25519.Equals(curveOid)
-                        //|| CryptlibObjectIdentifiers.curvey25519.Equals(curveOid)
-                        )
+                    var curveOid = eddsaK.CurveOid;
+                    if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) ||
+                        GnuObjectIdentifiers.Ed25519.Equals(curveOid) ||
+                        EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                        CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
                     {
                         this.keyStrength = 256;
                     }
+                    else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid) ||
+                        EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                    {
+                        this.keyStrength = 448;
+                    }
                     else
                     {
-                        X9ECParametersHolder ecParameters = ECKeyPairGenerator.FindECCurveByOidLazy(curveOid);
-
-                        if (ecParameters != null)
-                        {
-                            this.keyStrength = ecParameters.Curve.FieldSize;
-                        }
-                        else
-                        {
-                            this.keyStrength = -1; // unknown
-                        }
+                        this.keyStrength = -1; // unknown
+                    }
+                }
+                else if (key is ECPublicBcpgKey ecK)
+                {
+                    var curveOid = ecK.CurveOid;
+                    X9ECParametersHolder ecParameters = ECKeyPairGenerator.FindECCurveByOidLazy(curveOid);
+
+                    if (ecParameters != null)
+                    {
+                        this.keyStrength = ecParameters.Curve.FieldSize;
+                    }
+                    else
+                    {
+                        this.keyStrength = -1; // unknown
                     }
                 }
             }
@@ -165,7 +187,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public PgpPublicKey(PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time)
         {
             if (pubKey.IsPrivate)
-                throw new ArgumentException("Expected a public key", "pubKey");
+                throw new ArgumentException("Expected a public key", nameof(pubKey));
 
             IBcpgKey bcpgKey;
             if (pubKey is RsaKeyParameters rK)
@@ -178,6 +200,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
             }
+            else if (pubKey is ElGamalPublicKeyParameters eK)
+            {
+                ElGamalParameters eS = eK.Parameters;
+
+                bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+            }
             else if (pubKey is ECPublicKeyParameters ecK)
             {
                 if (algorithm == PublicKeyAlgorithmTag.ECDH)
@@ -194,11 +222,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     throw new PgpException("unknown EC algorithm");
                 }
             }
-            else if (pubKey is ElGamalPublicKeyParameters eK)
+            else if (pubKey is Ed25519PublicKeyParameters ed25519PubKey)
             {
-                ElGamalParameters eS = eK.Parameters;
+                byte[] pointEnc = new byte[1 + Ed25519PublicKeyParameters.KeySize];
+                pointEnc[0] = 0x40;
+                ed25519PubKey.Encode(pointEnc, 1);
+                bcpgKey = new EdDsaPublicBcpgKey(GnuObjectIdentifiers.Ed25519, new BigInteger(1, pointEnc));
+            }
+            else if (pubKey is Ed448PublicKeyParameters ed448PubKey)
+            {
+                byte[] pointEnc = new byte[Ed448PublicKeyParameters.KeySize];
+                ed448PubKey.Encode(pointEnc, 0);
+                bcpgKey = new EdDsaPublicBcpgKey(EdECObjectIdentifiers.id_Ed448, new BigInteger(1, pointEnc));
+            }
+            else if (pubKey is X25519PublicKeyParameters x25519PubKey)
+            {
+                byte[] pointEnc = new byte[1 + X25519PublicKeyParameters.KeySize];
+                pointEnc[0] = 0x40;
+                x25519PubKey.Encode(pointEnc, 1);
 
-                bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+                PgpKdfParameters kdfParams = DefaultKdfParameters;
+
+                bcpgKey = new ECDHPublicBcpgKey(CryptlibObjectIdentifiers.curvey25519, new BigInteger(1, pointEnc),
+                    kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
+            }
+            else if (pubKey is X448PublicKeyParameters x448PubKey)
+            {
+                byte[] pointEnc = new byte[X448PublicKeyParameters.KeySize];
+                x448PubKey.Encode(pointEnc, 0);
+
+                PgpKdfParameters kdfParams = DefaultKdfParameters;
+
+                bcpgKey = new ECDHPublicBcpgKey(EdECObjectIdentifiers.id_X448, new BigInteger(1, pointEnc),
+                    kdfParams.HashAlgorithm, kdfParams.SymmetricWrapAlgorithm);
             }
             else
             {
@@ -473,24 +529,89 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             {
                 switch (publicPk.Algorithm)
                 {
-                    case PublicKeyAlgorithmTag.RsaEncrypt:
-                    case PublicKeyAlgorithmTag.RsaGeneral:
-                    case PublicKeyAlgorithmTag.RsaSign:
-                        RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key;
-                        return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
-                    case PublicKeyAlgorithmTag.Dsa:
-                        DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key;
-                        return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
-                    case PublicKeyAlgorithmTag.ECDsa:
-                        return GetECKey("ECDSA");
-                    case PublicKeyAlgorithmTag.ECDH:
-                        return GetECKey("ECDH");
-                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
-                    case PublicKeyAlgorithmTag.ElGamalGeneral:
-                        ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
-                        return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
-                    default:
-                        throw new PgpException("unknown public key algorithm encountered");
+                case PublicKeyAlgorithmTag.RsaEncrypt:
+                case PublicKeyAlgorithmTag.RsaGeneral:
+                case PublicKeyAlgorithmTag.RsaSign:
+                    RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey)publicPk.Key;
+                    return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
+                case PublicKeyAlgorithmTag.Dsa:
+                    DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey)publicPk.Key;
+                    return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
+                case PublicKeyAlgorithmTag.ECDsa:
+                    ECDsaPublicBcpgKey ecdsaK = (ECDsaPublicBcpgKey)publicPk.Key;
+                    return GetECKey("ECDSA", ecdsaK);
+                case PublicKeyAlgorithmTag.ECDH:
+                {
+                    ECDHPublicBcpgKey ecdhK = (ECDHPublicBcpgKey)publicPk.Key;
+                    var curveOid = ecdhK.CurveOid;
+
+                    if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                        CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + X25519.PointSize, ecdhK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid X25519 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + X448.PointSize, ecdhK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid X448 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else
+                    {
+                        return GetECKey("ECDH", ecdhK);
+                    }
+                }
+                case PublicKeyAlgorithmTag.EdDsa:
+                {
+                    EdDsaPublicBcpgKey eddsaK = (EdDsaPublicBcpgKey)publicPk.Key;
+                    var curveOid = eddsaK.CurveOid;
+
+                    if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) ||
+                        GnuObjectIdentifiers.Ed25519.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + Ed25519.PublicKeySize, eddsaK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid Ed25519 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid))
+                    {
+                        byte[] pEnc = BigIntegers.AsUnsignedByteArray(1 + Ed448.PublicKeySize, eddsaK.EncodedPoint);
+                        if (pEnc[0] != 0x40)
+                            throw new ArgumentException("Invalid Ed448 public key");
+
+                        return PublicKeyFactory.CreateKey(new SubjectPublicKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            // TODO Span variant
+                            Arrays.CopyOfRange(pEnc, 1, pEnc.Length)));
+                    }
+                    else 
+                    {
+                        throw new InvalidOperationException();
+                    }
+                }
+                case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                case PublicKeyAlgorithmTag.ElGamalGeneral:
+                    ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey)publicPk.Key;
+                    return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
+                default:
+                    throw new PgpException("unknown public key algorithm encountered");
                 }
             }
             catch (PgpException e)
@@ -503,9 +624,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        private ECPublicKeyParameters GetECKey(string algorithm)
+        private ECPublicKeyParameters GetECKey(string algorithm, ECPublicBcpgKey ecK)
         {
-            ECPublicBcpgKey ecK = (ECPublicBcpgKey)publicPk.Key;
             X9ECParameters x9 = ECKeyPairGenerator.FindECCurveByOid(ecK.CurveOid);
             BigInteger encodedPoint = ecK.EncodedPoint;
 
@@ -563,7 +683,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public IEnumerable<PgpSignature> GetSignaturesForId(string id)
         {
             if (id == null)
-                throw new ArgumentNullException("id");
+                throw new ArgumentNullException(nameof(id));
 
             var result = new List<PgpSignature>();
             bool userIdFound = false;
@@ -580,13 +700,48 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return userIdFound ? CollectionUtilities.Proxy(result) : null;
         }
 
+        private IEnumerable<PgpSignature> GetSignaturesForID(UserIdPacket id)
+        {
+            var signatures = new List<PgpSignature>();
+            bool userIdFound = false;
+
+            for (int i = 0; i != ids.Count; i++)
+            {
+                if (id.Equals(ids[i]))
+                {
+                    userIdFound = true;
+                    signatures.AddRange(idSigs[i]);
+                }
+            }
+
+            return userIdFound ? signatures : null;
+        }
+
+        /// <summary>Return any signatures associated with the passed in key identifier keyID.</summary>
+        /// <param name="keyID">the key id to be matched.</param>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects issued by the key with keyID.</returns>
+        public IEnumerable<PgpSignature> GetSignaturesForKeyID(long keyID)
+        {
+            var sigs = new List<PgpSignature>();
+
+            foreach (var sig in GetSignatures())
+            {
+                if (sig.KeyId == keyID)
+                {
+                    sigs.Add(sig);
+                }
+            }
+
+            return CollectionUtilities.Proxy(sigs);
+        }
+
         /// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary>
         /// <param name="userAttributes">The vector of user attributes to be matched.</param>
         /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
         public IEnumerable<PgpSignature> GetSignaturesForUserAttribute(PgpUserAttributeSubpacketVector userAttributes)
         {
             if (userAttributes == null)
-                throw new ArgumentNullException("userAttributes");
+                throw new ArgumentNullException(nameof(userAttributes));
 
             var result = new List<PgpSignature>();
             bool attributeFound = false;
@@ -648,11 +803,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
          */
         public IEnumerable<PgpSignature> GetKeySignatures()
         {
-            var result = subSigs;
-            if (result == null)
-            {
-                result = new List<PgpSignature>(keySigs);
-            }
+            var result = subSigs ?? new List<PgpSignature>(keySigs);
 
             return CollectionUtilities.Proxy(result);
         }
@@ -947,56 +1098,38 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// <param name="key">The key the certifications are to be removed from.</param>
         /// <param name="certification">The certfication to be removed.</param>
         /// <returns>The modified key, null if the certification was not found.</returns>
-        public static PgpPublicKey RemoveCertification(
-            PgpPublicKey	key,
-            PgpSignature	certification)
+        public static PgpPublicKey RemoveCertification(PgpPublicKey	key, PgpSignature certification)
         {
-            PgpPublicKey returnKey = new PgpPublicKey(key);
-            var sigs = returnKey.subSigs != null
-                ?	returnKey.subSigs
-                :	returnKey.keySigs;
+            var returnKey = new PgpPublicKey(key);
+            var sigs = returnKey.subSigs ?? returnKey.keySigs;
 
-//			bool found = sigs.Remove(certification);
-            int pos = sigs.IndexOf(certification);
-            bool found = pos >= 0;
+            if (sigs.Remove(certification))
+                return returnKey;
 
-            if (found)
+            // TODO Java uses getRawUserIDs
+            foreach (string id in key.GetUserIds())
             {
-                sigs.RemoveAt(pos);
+                if (ContainsSignature(key.GetSignaturesForId(id), certification))
+                    return RemoveCertification(returnKey, id, certification);
             }
-            else
-            {
-                foreach (string id in key.GetUserIds())
-                {
-                    foreach (object sig in key.GetSignaturesForId(id))
-                    {
-                        // TODO Is this the right type of equality test?
-                        if (certification == sig)
-                        {
-                            found = true;
-                            returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
-                        }
-                    }
-                }
 
-                if (!found)
-                {
-                    foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
-                    {
-                        foreach (object sig in key.GetSignaturesForUserAttribute(id))
-                        {
-                            // TODO Is this the right type of equality test?
-                            if (certification == sig)
-                            {
-                                found = true;
-                                returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
-                            }
-                        }
-                    }
-                }
+            foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
+            {
+                if (ContainsSignature(key.GetSignaturesForUserAttribute(id), certification))
+                    return RemoveCertification(returnKey, id, certification);
             }
 
             return returnKey;
         }
+
+        private static bool ContainsSignature(IEnumerable<PgpSignature> signatures, PgpSignature signature)
+        {
+            foreach (PgpSignature candidate in signatures)
+            {
+                if (signature == candidate)
+                    return true;
+            }
+            return false;
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
index 04fe3ad37..8c6fcda53 100644
--- a/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
+++ b/crypto/src/openpgp/PgpPublicKeyEncryptedData.cs
@@ -1,19 +1,20 @@
 using System;
 using System.IO;
 
-using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Asn1.Cryptlib;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.IO;
-using Org.BouncyCastle.Crypto.Generators;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Math.EC;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Asn1.EdEC;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>A public key encrypted data object.</remarks>
+    /// <remarks>A public key encrypted data object.</remarks>
     public class PgpPublicKeyEncryptedData
         : PgpEncryptedData
     {
@@ -139,10 +140,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     truncStream = new TruncatedStream(encStream);
 
-					string digestName = PgpUtilities.GetDigestName(HashAlgorithmTag.Sha1);
-					IDigest digest = DigestUtilities.GetDigest(digestName);
+                    IDigest digest = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
 
-					encStream = new DigestStream(truncStream, digest, null);
+                    encStream = new DigestStream(truncStream, digest, null);
                 }
 
 				if (Streams.ReadFully(encStream, iv, 0, iv.Length) < iv.Length)
@@ -189,76 +189,114 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		{
             byte[][] secKeyData = keyData.GetEncSessionKey();
 
-            if (keyData.Algorithm == PublicKeyAlgorithmTag.ECDH)
+            if (keyData.Algorithm != PublicKeyAlgorithmTag.ECDH)
             {
-                ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
-                X9ECParameters x9Params = ECKeyPairGenerator.FindECCurveByOid(ecKey.CurveOid);
+                IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
+
+                try
+			    {
+                    cipher.Init(false, privKey.Key);
+			    }
+			    catch (InvalidKeyException e)
+			    {
+				    throw new PgpException("error setting asymmetric cipher", e);
+			    }
+
+                if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
+				    || keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
+			    {
+                    byte[] bi = secKeyData[0];
+
+                    cipher.ProcessBytes(bi, 2, bi.Length - 2);
+			    }
+			    else
+			    {
+				    ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
+				    int size = (k.Parameters.P.BitLength + 7) / 8;
+
+                    ProcessEncodedMpi(cipher, size, secKeyData[0]);
+                    ProcessEncodedMpi(cipher, size, secKeyData[1]);
+			    }
+
+                try
+			    {
+                    return cipher.DoFinal();
+			    }
+			    catch (Exception e)
+			    {
+				    throw new PgpException("exception decrypting secret key", e);
+			    }
+            }
 
-                byte[] enc = secKeyData[0];
+            ECDHPublicBcpgKey ecPubKey = (ECDHPublicBcpgKey)privKey.PublicKeyPacket.Key;
+            byte[] enc = secKeyData[0];
 
-                int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
-                if ((2 + pLen + 1) > enc.Length) 
-                    throw new PgpException("encoded length out of range");
+            int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8;
+            if ((2 + pLen + 1) > enc.Length) 
+                throw new PgpException("encoded length out of range");
 
-                byte[] pEnc = new byte[pLen];
-                Array.Copy(enc, 2, pEnc, 0, pLen);
+            byte[] pEnc = new byte[pLen];
+            Array.Copy(enc, 2, pEnc, 0, pLen);
 
-                int keyLen = enc[pLen + 2];
-                if ((2 + pLen + 1 + keyLen) > enc.Length)
-                    throw new PgpException("encoded length out of range");
+            int keyLen = enc[pLen + 2];
+            if ((2 + pLen + 1 + keyLen) > enc.Length)
+                throw new PgpException("encoded length out of range");
 
-                byte[] keyEnc = new byte[keyLen];
-                Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
+            byte[] keyEnc = new byte[keyLen];
+            Array.Copy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.Length);
 
-                ECPoint publicPoint = x9Params.Curve.DecodePoint(pEnc);
+            var curveOid = ecPubKey.CurveOid;
+            byte[] secret;
 
-                ECPrivateKeyParameters privKeyParams = (ECPrivateKeyParameters)privKey.Key;
-                ECPoint S = publicPoint.Multiply(privKeyParams.D).Normalize();
+            if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+            {
+                // skip the 0x40 header byte.
+                if (pEnc.Length != (1 + X25519PublicKeyParameters.KeySize) || 0x40 != pEnc[0])
+                    throw new ArgumentException("Invalid X25519 public key");
 
-                KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, S));
+                X25519PublicKeyParameters ephPub = new X25519PublicKeyParameters(pEnc, 1);
 
-                IWrapper w = PgpUtilities.CreateWrapper(ecKey.SymmetricKeyAlgorithm);
-                w.Init(false, key);
+                X25519Agreement agreement = new X25519Agreement();
+                agreement.Init(privKey.Key);
 
-                return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length));
+                secret = new byte[agreement.AgreementSize];
+                agreement.CalculateAgreement(ephPub, secret, 0);
             }
+            else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+            {
+                // skip the 0x40 header byte.
+                if (pEnc.Length != (1 + X448PublicKeyParameters.KeySize) || 0x40 != pEnc[0])
+                    throw new ArgumentException("Invalid X448 public key");
 
-            IBufferedCipher cipher = GetKeyCipher(keyData.Algorithm);
+                X448PublicKeyParameters ephPub = new X448PublicKeyParameters(pEnc, 1);
 
-            try
-			{
-                cipher.Init(false, privKey.Key);
-			}
-			catch (InvalidKeyException e)
-			{
-				throw new PgpException("error setting asymmetric cipher", e);
-			}
+                X448Agreement agreement = new X448Agreement();
+                agreement.Init(privKey.Key);
 
-            if (keyData.Algorithm == PublicKeyAlgorithmTag.RsaEncrypt
-				|| keyData.Algorithm == PublicKeyAlgorithmTag.RsaGeneral)
-			{
-                byte[] bi = secKeyData[0];
+                secret = new byte[agreement.AgreementSize];
+                agreement.CalculateAgreement(ephPub, secret, 0);
+            }
+            else
+            {
+                ECDomainParameters ecParameters = ((ECPrivateKeyParameters)privKey.Key).Parameters;
 
-                cipher.ProcessBytes(bi, 2, bi.Length - 2);
-			}
-			else
-			{
-				ElGamalPrivateKeyParameters k = (ElGamalPrivateKeyParameters)privKey.Key;
-				int size = (k.Parameters.P.BitLength + 7) / 8;
+                ECPublicKeyParameters ephPub = new ECPublicKeyParameters(ecParameters.Curve.DecodePoint(pEnc),
+                    ecParameters);
 
-                ProcessEncodedMpi(cipher, size, secKeyData[0]);
-                ProcessEncodedMpi(cipher, size, secKeyData[1]);
-			}
+                ECDHBasicAgreement agreement = new ECDHBasicAgreement();
+                agreement.Init(privKey.Key);
+                BigInteger S = agreement.CalculateAgreement(ephPub);
+                secret = BigIntegers.AsUnsignedByteArray(agreement.GetFieldSize(), S);
+            }
 
-            try
-			{
-                return cipher.DoFinal();
-			}
-			catch (Exception e)
-			{
-				throw new PgpException("exception decrypting secret key", e);
-			}
-		}
+            KeyParameter key = new KeyParameter(Rfc6637Utilities.CreateKey(privKey.PublicKeyPacket, secret));
+
+            IWrapper w = PgpUtilities.CreateWrapper(ecPubKey.SymmetricKeyAlgorithm);
+            w.Init(false, key);
+
+            return PgpPad.UnpadSessionData(w.Unwrap(keyEnc, 0, keyEnc.Length));
+        }
 
         private static void ProcessEncodedMpi(IBufferedCipher cipher, int size, byte[] mpiEnc)
         {
diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs
index 4aa15384c..ebbb95634 100644
--- a/crypto/src/openpgp/PgpPublicKeyRing.cs
+++ b/crypto/src/openpgp/PgpPublicKeyRing.cs
@@ -68,19 +68,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// <summary>Return the first public key in the ring.</summary>
         public virtual PgpPublicKey GetPublicKey()
         {
-            return (PgpPublicKey) keys[0];
+            return keys[0];
         }
 
         /// <summary>Return the public key referred to by the passed in key ID if it is present.</summary>
-        public virtual PgpPublicKey GetPublicKey(
-            long keyId)
+        public virtual PgpPublicKey GetPublicKey(long keyId)
         {
             foreach (PgpPublicKey k in keys)
             {
                 if (keyId == k.KeyId)
-                {
                     return k;
-                }
             }
 
             return null;
@@ -168,23 +165,24 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         /// <returns>A new <c>PgpPublicKeyRing</c>, or null if pubKey is not found.</returns>
         public static PgpPublicKeyRing RemovePublicKey(PgpPublicKeyRing pubRing, PgpPublicKey pubKey)
         {
-            var keys = new List<PgpPublicKey>(pubRing.keys);
+            int count = pubRing.keys.Count;
+            long keyID = pubKey.KeyId;
+
+            var result = new List<PgpPublicKey>(count);
             bool found = false;
 
-            // TODO Is there supposed to be at most a single match?
-            int pos = keys.Count;
-            while (--pos >= 0)
+            foreach (var key in pubRing.keys)
             {
-                PgpPublicKey key = keys[pos];
-
-                if (key.KeyId == pubKey.KeyId)
+                if (key.KeyId == keyID)
                 {
                     found = true;
-                    keys.RemoveAt(pos);
+                    continue;
                 }
+
+                result.Add(key);
             }
 
-            return found ? new PgpPublicKeyRing(keys) : null;
+            return found ? new PgpPublicKeyRing(result) : null;
         }
 
         internal static PublicKeyPacket ReadPublicKeyPacket(BcpgInputStream bcpgInput)
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 51a45703a..0103cc187 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -2,10 +2,17 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cryptlib;
+using Org.BouncyCastle.Asn1.EdEC;
+using Org.BouncyCastle.Asn1.Gnu;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
@@ -13,6 +20,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
     /// <remarks>General class to handle a PGP secret key object.</remarks>
     public class PgpSecretKey
+        : PgpObject
     {
         private readonly SecretKeyPacket	secret;
         private readonly PgpPublicKey		pub;
@@ -52,10 +60,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     secKey = new DsaSecretBcpgKey(dsK.X);
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
+                {
+                    if (privKey.Key is ECPrivateKeyParameters ecdhK)
+                    {
+                        secKey = new ECSecretBcpgKey(ecdhK.D);
+                    }
+                    else
+                    {
+                        // 'reverse' because the native format for X25519 private keys is little-endian
+                        X25519PrivateKeyParameters xK = (X25519PrivateKeyParameters)privKey.Key;
+                        secKey = new ECSecretBcpgKey(new BigInteger(1, Arrays.ReverseInPlace(xK.GetEncoded())));
+                    }
+                    break;
+                }
                 case PublicKeyAlgorithmTag.ECDsa:
                     ECPrivateKeyParameters ecK = (ECPrivateKeyParameters)privKey.Key;
                     secKey = new ECSecretBcpgKey(ecK.D);
                     break;
+                case PublicKeyAlgorithmTag.EdDsa:
+                {
+                    if (privKey.Key is Ed25519PrivateKeyParameters ed25519K)
+                    {
+                        secKey = new EdSecretBcpgKey(new BigInteger(1, ed25519K.GetEncoded()));
+                    }
+                    else if (privKey.Key is Ed448PrivateKeyParameters ed448K)
+                    {
+                        secKey = new EdSecretBcpgKey(new BigInteger(1, ed448K.GetEncoded()));
+                    }
+                    else
+                    {
+                        throw new PgpException("unknown EdDSA key class");
+                    }
+                    break;
+                }
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
@@ -625,6 +662,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 return null;
 
             PublicKeyPacket pubPk = secret.PublicKeyPacket;
+
             try
             {
                 byte[] data = ExtractKeyData(rawPassPhrase, clearPassPhrase);
@@ -655,11 +693,65 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     privateKey = new DsaPrivateKeyParameters(dsaPriv.X, dsaParams);
                     break;
                 case PublicKeyAlgorithmTag.ECDH:
-                    privateKey = GetECKey("ECDH", bcpgIn);
+                {
+                    ECDHPublicBcpgKey ecdhPub = (ECDHPublicBcpgKey)pubPk.Key;
+                    ECSecretBcpgKey ecdhPriv = new ECSecretBcpgKey(bcpgIn);
+                    var curveOid = ecdhPub.CurveOid;
+
+                    if (EdECObjectIdentifiers.id_X25519.Equals(curveOid) ||
+                        CryptlibObjectIdentifiers.curvey25519.Equals(curveOid))
+                    {
+                        // 'reverse' because the native format for X25519 private keys is little-endian
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(Arrays.ReverseInPlace(BigIntegers.AsUnsignedByteArray(ecdhPriv.X)))));
+                    }
+                    else if (EdECObjectIdentifiers.id_X448.Equals(curveOid))
+                    {
+                        // 'reverse' because the native format for X448 private keys is little-endian
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(Arrays.ReverseInPlace(BigIntegers.AsUnsignedByteArray(ecdhPriv.X)))));
+                    }
+                    else
+                    {
+                        privateKey = new ECPrivateKeyParameters("ECDH", ecdhPriv.X, ecdhPub.CurveOid);
+                    }
                     break;
+                }
                 case PublicKeyAlgorithmTag.ECDsa:
-                    privateKey = GetECKey("ECDSA", bcpgIn);
+                {
+                    ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)pubPk.Key;
+                    ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn);
+
+                    privateKey = new ECPrivateKeyParameters("ECDSA", ecdsaPriv.X, ecdsaPub.CurveOid);
                     break;
+                }
+                case PublicKeyAlgorithmTag.EdDsa:
+                {
+                    EdDsaPublicBcpgKey eddsaPub = (EdDsaPublicBcpgKey)pubPk.Key;
+                    EdSecretBcpgKey ecdsaPriv = new EdSecretBcpgKey(bcpgIn);
+
+                    var curveOid = eddsaPub.CurveOid;
+                    if (EdECObjectIdentifiers.id_Ed25519.Equals(curveOid) ||
+                        GnuObjectIdentifiers.Ed25519.Equals(curveOid))
+                    {
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(BigIntegers.AsUnsignedByteArray(Ed25519.SecretKeySize, ecdsaPriv.X))));
+                    }
+                    else if (EdECObjectIdentifiers.id_Ed448.Equals(curveOid))
+                    {
+                        privateKey = PrivateKeyFactory.CreateKey(new PrivateKeyInfo(
+                            new AlgorithmIdentifier(curveOid),
+                            new DerOctetString(BigIntegers.AsUnsignedByteArray(Ed448.SecretKeySize, ecdsaPriv.X))));
+                    }
+                    else 
+                    {
+                        throw new InvalidOperationException();
+                    }
+                    break;
+                }
                 case PublicKeyAlgorithmTag.ElGamalEncrypt:
                 case PublicKeyAlgorithmTag.ElGamalGeneral:
                     ElGamalPublicBcpgKey elPub = (ElGamalPublicBcpgKey)pubPk.Key;
@@ -683,13 +775,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        private ECPrivateKeyParameters GetECKey(string algorithm, BcpgInputStream bcpgIn)
-        {
-            ECPublicBcpgKey ecdsaPub = (ECPublicBcpgKey)secret.PublicKeyPacket.Key;
-            ECSecretBcpgKey ecdsaPriv = new ECSecretBcpgKey(bcpgIn);
-            return new ECPrivateKeyParameters(algorithm, ecdsaPriv.X, ecdsaPub.CurveOid);
-        }
-
         private static byte[] Checksum(
             bool	useSha1,
             byte[]	bytes,
@@ -699,7 +784,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             {
                 try
                 {
-                    IDigest dig = DigestUtilities.GetDigest("SHA1");
+                    IDigest dig = PgpUtilities.CreateDigest(HashAlgorithmTag.Sha1);
                     dig.BlockUpdate(bytes, 0, length);
                     return DigestUtilities.DoFinal(dig);
                 }
diff --git a/crypto/src/openpgp/PgpSecretKeyRing.cs b/crypto/src/openpgp/PgpSecretKeyRing.cs
index 637cb45f8..a070aa132 100644
--- a/crypto/src/openpgp/PgpSecretKeyRing.cs
+++ b/crypto/src/openpgp/PgpSecretKeyRing.cs
@@ -71,11 +71,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             // revocation and direct signatures
             var keySigs = ReadSignaturesAndTrust(bcpgInput);
 
-            IList<object> ids;
-            IList<TrustPacket> idTrusts;
-            IList<IList<PgpSignature>> idSigs;
-
-            ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+            ReadUserIDs(bcpgInput, out var ids, out var idTrusts, out var idSigs);
 
             keys.Add(new PgpSecretKey(secret,
                 new PgpPublicKey(secret.PublicKeyPacket, trust, keySigs, ids, idTrusts, idSigs)));
@@ -119,6 +115,43 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return keys[0].PublicKey;
         }
 
+        /**
+         * Return any keys carrying a signature issued by the key represented by keyID.
+         *
+         * @param keyID the key id to be matched against.
+         * @return an iterator (possibly empty) of PGPPublicKey objects carrying signatures from keyID.
+         */
+        public IEnumerable<PgpPublicKey> GetKeysWithSignaturesBy(long keyID)
+        {
+            var keysWithSigs = new List<PgpPublicKey>();
+
+            foreach (var k in GetPublicKeys())
+            {
+                var sigIt = k.GetSignaturesForKeyID(keyID).GetEnumerator();
+
+                if (sigIt.MoveNext())
+                {
+                    keysWithSigs.Add(k);
+                }
+            }
+
+            return CollectionUtilities.Proxy(keysWithSigs);
+        }
+
+        public IEnumerable<PgpPublicKey> GetPublicKeys()
+        {
+            var pubKeys = new List<PgpPublicKey>();
+
+            foreach (var secretKey in keys)
+            {
+                pubKeys.Add(secretKey.PublicKey);
+            }
+
+            pubKeys.AddRange(extraPubKeys);
+
+            return CollectionUtilities.Proxy(pubKeys);
+        }
+
         /// <summary>Return the master private key.</summary>
         public PgpSecretKey GetSecretKey()
         {
@@ -156,7 +189,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
         public byte[] GetEncoded()
         {
-            MemoryStream bOut = new MemoryStream();
+            var bOut = new MemoryStream();
             Encode(bOut);
             return bOut.ToArray();
         }
@@ -165,7 +198,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             Stream outStr)
         {
             if (outStr == null)
-                throw new ArgumentNullException("outStr");
+                throw new ArgumentNullException(nameof(outStr));
 
             foreach (PgpSecretKey key in keys)
             {
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index da00d43eb..9b596f279 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -3,6 +3,8 @@ using System.IO;
 
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Date;
@@ -14,10 +16,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
     {
         private static SignaturePacket Cast(Packet packet)
         {
-            if (!(packet is SignaturePacket))
-                throw new IOException("unexpected packet in stream: " + packet);
+			if (packet is SignaturePacket signaturePacket)
+				return signaturePacket;
 
-            return (SignaturePacket)packet;
+            throw new IOException("unexpected packet in stream: " + packet);
         }
 
         public const int BinaryDocument = 0x00;
@@ -56,24 +58,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
         }
 
-        internal PgpSignature(
-            SignaturePacket	sigPacket,
-            TrustPacket		trustPacket)
+        internal PgpSignature(SignaturePacket sigPacket, TrustPacket trustPacket)
         {
-			if (sigPacket == null)
-				throw new ArgumentNullException("sigPacket");
-
-			this.sigPck = sigPacket;
+			this.sigPck = sigPacket ?? throw new ArgumentNullException(nameof(sigPacket));
 			this.signatureType = sigPck.SignatureType;
 			this.trustPck = trustPacket;
         }
 
-		private void GetSig()
-        {
-            this.sig = SignerUtilities.GetSigner(
-				PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
-        }
-
 		/// <summary>The OpenPGP version number for this signature.</summary>
 		public int Version
 		{
@@ -98,17 +89,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return IsCertification(SignatureType);
         }
 
-		public void InitVerify(
-            PgpPublicKey pubKey)
+		public void InitVerify(PgpPublicKey pubKey)
         {
 			lastb = 0;
+			AsymmetricKeyParameter key = pubKey.GetKey();
+
             if (sig == null)
-            {
-                GetSig();
+			{
+                this.sig = PgpUtilities.CreateSigner(sigPck.KeyAlgorithm, sigPck.HashAlgorithm, key);
             }
+
             try
             {
-                sig.Init(false, pubKey.GetKey());
+                sig.Init(false, key);
             }
             catch (InvalidKeyException e)
             {
@@ -116,12 +109,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        public void Update(
-            byte b)
+        public void Update(byte b)
         {
             if (signatureType == CanonicalTextDocument)
             {
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
             }
             else
             {
@@ -129,18 +121,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
@@ -151,39 +142,56 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
 			sig.Update((byte)'\r');
 			sig.Update((byte)'\n');
 		}
 
-		public void Update(
-            params byte[] bytes)
+		public void Update(params byte[] bytes)
         {
 			Update(bytes, 0, bytes.Length);
         }
 
-		public void Update(
-            byte[]	bytes,
-            int		off,
-            int		length)
+		public void Update(byte[] bytes, int off, int length)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(bytes.AsSpan(off, length));
+#else
             if (signatureType == CanonicalTextDocument)
             {
                 int finish = off + length;
 
 				for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(bytes[i]);
+                    DoCanonicalUpdateByte(bytes[i]);
                 }
             }
             else
             {
                 sig.BlockUpdate(bytes, off, length);
             }
+#endif
         }
 
-		public bool Verify()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            if (signatureType == CanonicalTextDocument)
+            {
+                for (int i = 0; i < input.Length; ++i)
+                {
+                    DoCanonicalUpdateByte(input[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(input);
+            }
+        }
+#endif
+
+        public bool Verify()
         {
             byte[] trailer = GetSignatureTrailer();
             sig.BlockUpdate(trailer, 0, trailer.Length);
@@ -234,7 +242,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			//
 			try
 			{
-				MemoryStream bOut = new MemoryStream();
+				var bOut = new MemoryStream();
 				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
 				{
 					packet.Encode(bOut);
@@ -248,7 +256,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			this.Update(sigPck.GetSignatureTrailer());
 
-			return sig.VerifySignature(this.GetSignature());
+			return sig.VerifySignature(GetSignature());
 		}
 
 		/// <summary>
@@ -345,15 +353,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 		public PgpSignatureSubpacketVector GetHashedSubPackets()
         {
-            return createSubpacketVector(sigPck.GetHashedSubPackets());
+            return CreateSubpacketVector(sigPck.GetHashedSubPackets());
         }
 
 		public PgpSignatureSubpacketVector GetUnhashedSubPackets()
         {
-            return createSubpacketVector(sigPck.GetUnhashedSubPackets());
+            return CreateSubpacketVector(sigPck.GetUnhashedSubPackets());
         }
 
-		private PgpSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks)
+		private static PgpSignatureSubpacketVector CreateSubpacketVector(SignatureSubpacket[] pcks)
 		{
 			return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
 		}
@@ -369,10 +377,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				{
 					signature = sigValues[0].Value.ToByteArrayUnsigned();
 				}
-				else
-				{
-					try
+                else if (KeyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
+                {
+					if (sigValues.Length != 2)
+						throw new InvalidOperationException();
+
+					BigInteger v0 = sigValues[0].Value;
+                    BigInteger v1 = sigValues[1].Value;
+
+					if (v0.BitLength == 918 &&
+                        v1.Equals(BigInteger.Zero) &&
+						v0.ShiftRight(912).Equals(BigInteger.ValueOf(0x40)))
+					{
+						signature = new byte[Ed448.SignatureSize];
+						BigIntegers.AsUnsignedByteArray(v0.ClearBit(918), signature, 0, signature.Length);
+					}
+					else if (v0.BitLength <= 256 && v1.BitLength <= 256)
+					{
+                        signature = new byte[Ed25519.SignatureSize];
+                        BigIntegers.AsUnsignedByteArray(sigValues[0].Value, signature,  0, 32);
+                        BigIntegers.AsUnsignedByteArray(sigValues[1].Value, signature, 32, 32);
+                    }
+                    else
 					{
+                        throw new InvalidOperationException();
+                    }
+                }
+                else
+                {
+                    if (sigValues.Length != 2)
+                        throw new InvalidOperationException();
+
+                    try
+                    {
 						signature = new DerSequence(
 							new DerInteger(sigValues[0].Value),
 							new DerInteger(sigValues[1].Value)).GetEncoded();
@@ -394,17 +431,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		// TODO Handle the encoding stuff by subclassing BcpgObject?
 		public byte[] GetEncoded()
         {
-            MemoryStream bOut = new MemoryStream();
+            var bOut = new MemoryStream();
 
 			Encode(bOut);
 
 			return bOut.ToArray();
         }
 
-		public void Encode(
-            Stream outStream)
+		public void Encode(Stream outStream)
         {
-            BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);
+            var bcpgOut = BcpgOutputStream.Wrap(outStream);
 
 			bcpgOut.WritePacket(sigPck);
 
@@ -414,8 +450,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		private byte[] GetEncodedPublicKey(
-			PgpPublicKey pubKey) 
+		private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey) 
 		{
 			try
 			{
@@ -436,13 +471,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
             switch (signatureType)
             {
-                case DefaultCertification:
-                case NoCertification:
-                case CasualCertification:
-                case PositiveCertification:
-                    return true;
-                default:
-                    return false;
+            case DefaultCertification:
+            case NoCertification:
+            case CasualCertification:
+            case PositiveCertification:
+                return true;
+            default:
+                return false;
             }
         }
     }
diff --git a/crypto/src/openpgp/PgpSignatureGenerator.cs b/crypto/src/openpgp/PgpSignatureGenerator.cs
index c5309689f..12edf9f89 100644
--- a/crypto/src/openpgp/PgpSignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureGenerator.cs
@@ -5,19 +5,20 @@ using Org.BouncyCastle.Bcpg.Sig;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC.Rfc8032;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
 	/// <remarks>Generator for PGP signatures.</remarks>
-	// TODO Should be able to implement ISigner?
     public class PgpSignatureGenerator
     {
 		private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
 
-		private PublicKeyAlgorithmTag	keyAlgorithm;
-        private HashAlgorithmTag		hashAlgorithm;
+		private readonly PublicKeyAlgorithmTag keyAlgorithm;
+        private readonly HashAlgorithmTag hashAlgorithm;
+
         private PgpPrivateKey			privKey;
         private ISigner					sig;
         private IDigest					dig;
@@ -35,33 +36,39 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             this.keyAlgorithm = keyAlgorithm;
             this.hashAlgorithm = hashAlgorithm;
 
-			dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
-            sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+			dig = PgpUtilities.CreateDigest(hashAlgorithm);
         }
 
 		/// <summary>Initialise the generator for signing.</summary>
         public void InitSign(
             int				sigType,
-            PgpPrivateKey	key)
+            PgpPrivateKey	privKey)
         {
-			InitSign(sigType, key, null);
+			InitSign(sigType, privKey, null);
         }
 
 		/// <summary>Initialise the generator for signing.</summary>
 		public void InitSign(
 			int				sigType,
-			PgpPrivateKey	key,
+			PgpPrivateKey privKey,
 			SecureRandom	random)
 		{
-			this.privKey = key;
+			this.privKey = privKey;
 			this.signatureType = sigType;
 
-			try
+			AsymmetricKeyParameter key = privKey.Key;
+
+			if (sig == null)
 			{
-				ICipherParameters cp = key.Key;
+                this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key);
+            }
+
+            try
+			{
+				ICipherParameters cp = key;
 				if (random != null)
 				{
-					cp = new ParametersWithRandom(key.Key, random);
+					cp = new ParametersWithRandom(cp, random);
 				}
 
 				sig.Init(true, cp);
@@ -75,72 +82,68 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			lastb = 0;
 		}
 
-		public void Update(
-            byte b)
+		public void Update(byte b)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
             }
             else
             {
-				doUpdateByte(b);
+				DoUpdateByte(b);
             }
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
 			{
-				doUpdateByte(b);
+				DoUpdateByte(b);
 			}
 
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
-			doUpdateByte((byte)'\r');
-			doUpdateByte((byte)'\n');
+			DoUpdateByte((byte)'\r');
+			DoUpdateByte((byte)'\n');
 		}
 
-		private void doUpdateByte(
-			byte b)
+		private void DoUpdateByte(byte b)
 		{
 			sig.Update(b);
 			dig.Update(b);
 		}
 
-		public void Update(
-            params byte[] b)
+		public void Update(params byte[] b)
         {
 			Update(b, 0, b.Length);
         }
 
-		public void Update(
-            byte[]	b,
-            int		off,
-            int		len)
+		public void Update(byte[] b, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(b.AsSpan(off, len));
+#else
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
                 int finish = off + len;
 
 				for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(b[i]);
+                    DoCanonicalUpdateByte(b[i]);
                 }
             }
             else
@@ -148,9 +151,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 sig.BlockUpdate(b, off, len);
                 dig.BlockUpdate(b, off, len);
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            if (signatureType == PgpSignature.CanonicalTextDocument)
+            {
+                for (int i = 0; i < input.Length; ++i)
+                {
+                    DoCanonicalUpdateByte(input[i]);
+                }
+            }
+            else
+            {
+                sig.BlockUpdate(input);
+                dig.BlockUpdate(input);
+            }
         }
+#endif
 
-		public void SetHashedSubpackets(
+        public void SetHashedSubpackets(
             PgpSignatureSubpacketVector hashedPackets)
         {
 			hashed = hashedPackets == null
@@ -180,15 +202,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
 			SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed;
 
-			if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime))
+			if (!IsPacketPresent(hashed, SignatureSubpacketTag.CreationTime))
 			{
-				hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
+				hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
 			}
 
-			if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
-				&& !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
+			if (!IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
+				&& !IsPacketPresent(unhashed, SignatureSubpacketTag.IssuerKeyId))
 			{
-				unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
+				unhPkts = InsertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId));
 			}
 
 			int version = 4;
@@ -239,17 +261,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			byte[] sigBytes = sig.GenerateSignature();
 			byte[] digest = DigestUtilities.DoFinal(dig);
-			byte[] fingerPrint = new byte[] { digest[0], digest[1] };
+			byte[] fingerPrint = new byte[2]{ digest[0], digest[1] };
 
-			// an RSA signature
-			bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign
-				|| keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral;
-
-			MPInteger[] sigValues = isRsa
-				?	PgpUtilities.RsaSigToMpi(sigBytes)
-				:	PgpUtilities.DsaSigToMpi(sigBytes);
+			MPInteger[] sigValues;
+            if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
+            {
+                int sigLen = sigBytes.Length;
+                if (sigLen == Ed25519.SignatureSize)
+				{
+					sigValues = new MPInteger[2]{
+						new MPInteger(new BigInteger(1, sigBytes,  0, 32)),
+						new MPInteger(new BigInteger(1, sigBytes, 32, 32))
+					};
+				}
+                else if (sigLen == Ed448.SignatureSize)
+                {
+                    sigValues = new MPInteger[2]{
+                        new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
+                        new MPInteger(BigInteger.Zero)
+                    };
+                }
+                else
+				{
+					throw new InvalidOperationException();
+				}
+            }
+			else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
+			{
+                sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
+            }
+			else
+			{
+                sigValues = PgpUtilities.DsaSigToMpi(sigBytes);
+            }
 
-			return new PgpSignature(
+            return new PgpSignature(
 				new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm,
 					hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues));
         }
@@ -258,9 +304,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="id">The ID we are certifying against the public key.</param>
 		/// <param name="pubKey">The key we are certifying against the ID.</param>
 		/// <returns>The certification.</returns>
-        public PgpSignature GenerateCertification(
-            string			id,
-            PgpPublicKey	pubKey)
+        public PgpSignature GenerateCertification(string id, PgpPublicKey pubKey)
         {
 			UpdateWithPublicKey(pubKey);
 
@@ -276,9 +320,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="userAttributes">The ID we are certifying against the public key.</param>
 		/// <param name="pubKey">The key we are certifying against the ID.</param>
 		/// <returns>The certification.</returns>
-		public PgpSignature GenerateCertification(
-			PgpUserAttributeSubpacketVector	userAttributes,
-			PgpPublicKey					pubKey)
+		public PgpSignature GenerateCertification(PgpUserAttributeSubpacketVector userAttributes, PgpPublicKey pubKey)
 		{
 			UpdateWithPublicKey(pubKey);
 
@@ -287,7 +329,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			//
 			try
 			{
-				MemoryStream bOut = new MemoryStream();
+				var bOut = new MemoryStream();
 				foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray())
 				{
 					packet.Encode(bOut);
@@ -299,16 +341,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				throw new PgpException("cannot encode subpacket array", e);
 			}
 
-			return this.Generate();
+			return Generate();
 		}
 
 		/// <summary>Generate a certification for the passed in key against the passed in master key.</summary>
 		/// <param name="masterKey">The key we are certifying against.</param>
 		/// <param name="pubKey">The key we are certifying.</param>
 		/// <returns>The certification.</returns>
-        public PgpSignature GenerateCertification(
-            PgpPublicKey	masterKey,
-            PgpPublicKey	pubKey)
+        public PgpSignature GenerateCertification(PgpPublicKey masterKey, PgpPublicKey pubKey)
         {
 			UpdateWithPublicKey(masterKey);
 			UpdateWithPublicKey(pubKey);
@@ -319,16 +359,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <summary>Generate a certification, such as a revocation, for the passed in key.</summary>
 		/// <param name="pubKey">The key we are certifying.</param>
 		/// <returns>The certification.</returns>
-        public PgpSignature GenerateCertification(
-            PgpPublicKey pubKey)
+        public PgpSignature GenerateCertification(PgpPublicKey pubKey)
         {
 			UpdateWithPublicKey(pubKey);
 
 			return Generate();
         }
 
-		private byte[] GetEncodedPublicKey(
-			PgpPublicKey pubKey) 
+		private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey) 
 		{
 			try
 			{
@@ -340,42 +378,31 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			}
 		}
 
-		private bool packetPresent(
-			SignatureSubpacket[]	packets,
-			SignatureSubpacketTag	type)
+		private static bool IsPacketPresent(SignatureSubpacket[] packets, SignatureSubpacketTag type)
 		{
 			for (int i = 0; i != packets.Length; i++)
 			{
 				if (packets[i].SubpacketType == type)
-				{
 					return true;
-				}
 			}
 
 			return false;
 		}
 
-		private SignatureSubpacket[] insertSubpacket(
-			SignatureSubpacket[]	packets,
-			SignatureSubpacket		subpacket)
+		private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
 		{
-			SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1];
-			tmp[0] = subpacket;
-			packets.CopyTo(tmp, 1);
-			return tmp;
+			return Arrays.Prepend(packets, subpacket);
 		}
 
-		private void UpdateWithIdData(
-			int		header,
-			byte[]	idBytes)
+		private void UpdateWithIdData(int header, byte[] idBytes)
 		{
-			this.Update(
+			Update(
 				(byte) header,
 				(byte)(idBytes.Length >> 24),
 				(byte)(idBytes.Length >> 16),
 				(byte)(idBytes.Length >> 8),
 				(byte)(idBytes.Length));
-			this.Update(idBytes);
+			Update(idBytes);
 		}
 
 		private void UpdateWithPublicKey(
@@ -383,11 +410,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		{
 			byte[] keyBytes = GetEncodedPublicKey(key);
 
-			this.Update(
-				(byte) 0x99,
+			Update(
+				0x99,
 				(byte)(keyBytes.Length >> 8),
 				(byte)(keyBytes.Length));
-			this.Update(keyBytes);
+			Update(keyBytes);
 		}
 	}
 }
diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs
index f33969ea8..2642f3497 100644
--- a/crypto/src/openpgp/PgpUtilities.cs
+++ b/crypto/src/openpgp/PgpUtilities.cs
@@ -9,7 +9,9 @@ using Org.BouncyCastle.Asn1.Sec;
 using Org.BouncyCastle.Asn1.X9;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
 using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Pqc.Crypto.SphincsPlus;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
@@ -114,7 +116,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             if (NameToHashID.TryGetValue(name, out var hashAlgorithmTag))
                 return (int)hashAlgorithmTag;
 
-            throw new ArgumentException("unable to map " + name + " to a hash id", "name");
+            throw new ArgumentException("unable to map " + name + " to a hash id", nameof(name));
         }
 
         /**
@@ -152,6 +154,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 case PublicKeyAlgorithmTag.ECDsa:
                     encAlg = "ECDSA";
                     break;
+                case PublicKeyAlgorithmTag.EdDsa:
+                    encAlg = "EdDSA";
+                    break;
                 case PublicKeyAlgorithmTag.ElGamalEncrypt: // in some malformed cases.
 				case PublicKeyAlgorithmTag.ElGamalGeneral:
 					encAlg = "ElGamal";
@@ -163,7 +168,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return GetDigestName(hashAlgorithm) + "with" + encAlg;
         }
 
-	public static string GetSymmetricCipherName(
+	    public static string GetSymmetricCipherName(
             SymmetricKeyAlgorithmTag algorithm)
         {
             switch (algorithm)
@@ -301,11 +306,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				IDigest digest;
 				if (s2k != null)
                 {
-					string digestName = GetDigestName(s2k.HashAlgorithm);
-
                     try
                     {
-						digest = DigestUtilities.GetDigest(digestName);
+                        digest = CreateDigest(s2k.HashAlgorithm);
                     }
                     catch (Exception e)
                     {
@@ -368,7 +371,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 {
                     try
                     {
-                        digest = DigestUtilities.GetDigest("MD5");
+                        digest = CreateDigest(HashAlgorithmTag.MD5);
 
 						for (int i = 0; i != loopCount; i++)
                         {
@@ -407,7 +410,6 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return MakeKey(algorithm, keyBytes);
         }
 
-#if !PORTABLE || DOTNET
         /// <summary>Write out the passed in file as a literal data packet.</summary>
         public static void WriteFileToLiteralData(
             Stream		output,
@@ -452,12 +454,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 Platform.Dispose(inputStream);
             }
         }
-#endif
 
 		private const int ReadAhead = 60;
 
-		private static bool IsPossiblyBase64(
-            int ch)
+		private static bool IsPossiblyBase64(int ch)
         {
             return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
                     || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/')
@@ -473,7 +473,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
 			// TODO Remove this restriction?
 			if (!inputStream.CanSeek)
-				throw new ArgumentException("inputStream must be seek-able", "inputStream");
+				throw new ArgumentException("inputStream must be seek-able", nameof(inputStream));
 
 			long markedPos = inputStream.Position;
 
@@ -552,6 +552,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
+        internal static IDigest CreateDigest(HashAlgorithmTag hashAlgorithm)
+        {
+            return DigestUtilities.GetDigest(GetDigestName(hashAlgorithm));
+        }
+
+        internal static ISigner CreateSigner(PublicKeyAlgorithmTag publicKeyAlgorithm, HashAlgorithmTag hashAlgorithm,
+            AsymmetricKeyParameter key)
+        {
+            switch (publicKeyAlgorithm)
+            {
+            case PublicKeyAlgorithmTag.EdDsa:
+            {
+                ISigner signer;
+                if (key is Ed25519PrivateKeyParameters || key is Ed25519PublicKeyParameters)
+                {
+                    signer = new Ed25519Signer();
+                }
+                else if (key is Ed448PrivateKeyParameters || key is Ed448PublicKeyParameters)
+                {
+                    signer = new Ed448Signer(Arrays.EmptyBytes);
+                }
+                else
+                {
+                    throw new InvalidOperationException();
+                }
+
+                return new EdDsaSigner(signer, CreateDigest(hashAlgorithm));
+            }
+            default:
+            {
+                return SignerUtilities.GetSigner(GetSignatureName(publicKeyAlgorithm, hashAlgorithm));
+            }
+            }
+        }
+
         internal static IWrapper CreateWrapper(SymmetricKeyAlgorithmTag encAlgorithm)
         {
             switch (encAlgorithm)
diff --git a/crypto/src/openpgp/PgpV3SignatureGenerator.cs b/crypto/src/openpgp/PgpV3SignatureGenerator.cs
index c7113e0ae..324dbd768 100644
--- a/crypto/src/openpgp/PgpV3SignatureGenerator.cs
+++ b/crypto/src/openpgp/PgpV3SignatureGenerator.cs
@@ -1,3 +1,5 @@
+using System;
+
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
@@ -6,11 +8,11 @@ using Org.BouncyCastle.Utilities.Date;
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
 	/// <remarks>Generator for old style PGP V3 Signatures.</remarks>
-	// TODO Should be able to implement ISigner?
 	public class PgpV3SignatureGenerator
     {
-        private PublicKeyAlgorithmTag keyAlgorithm;
-        private HashAlgorithmTag hashAlgorithm;
+        private readonly PublicKeyAlgorithmTag keyAlgorithm;
+        private readonly HashAlgorithmTag hashAlgorithm;
+
         private PgpPrivateKey privKey;
         private ISigner sig;
         private IDigest    dig;
@@ -22,36 +24,40 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             PublicKeyAlgorithmTag	keyAlgorithm,
             HashAlgorithmTag		hashAlgorithm)
         {
+            if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
+                throw new ArgumentException("Invalid algorithm for V3 signature", nameof(keyAlgorithm));
+
             this.keyAlgorithm = keyAlgorithm;
             this.hashAlgorithm = hashAlgorithm;
 
-            dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm));
-            sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm));
+            dig = PgpUtilities.CreateDigest(hashAlgorithm);
         }
 
 		/// <summary>Initialise the generator for signing.</summary>
-		public void InitSign(
-			int				sigType,
-			PgpPrivateKey	key)
+		public void InitSign(int sigType, PgpPrivateKey privKey)
 		{
-			InitSign(sigType, key, null);
+			InitSign(sigType, privKey, null);
 		}
 
 		/// <summary>Initialise the generator for signing.</summary>
-        public void InitSign(
-            int				sigType,
-            PgpPrivateKey	key,
-			SecureRandom	random)
+        public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random)
         {
-            this.privKey = key;
+            this.privKey = privKey;
             this.signatureType = sigType;
 
-			try
+            AsymmetricKeyParameter key = privKey.Key;
+
+            if (sig == null)
             {
-				ICipherParameters cp = key.Key;
+                this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key);
+            }
+
+            try
+            {
+				ICipherParameters cp = key;
 				if (random != null)
 				{
-					cp = new ParametersWithRandom(key.Key, random);
+					cp = new ParametersWithRandom(cp, random);
 				}
 
 				sig.Init(true, cp);
@@ -65,93 +71,98 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             lastb = 0;
         }
 
-		public void Update(
-            byte b)
+		public void Update(byte b)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-				doCanonicalUpdateByte(b);
+				DoCanonicalUpdateByte(b);
             }
             else
             {
-				doUpdateByte(b);
+				DoUpdateByte(b);
             }
         }
 
-		private void doCanonicalUpdateByte(
-			byte b)
+		private void DoCanonicalUpdateByte(byte b)
 		{
 			if (b == '\r')
 			{
-				doUpdateCRLF();
+				DoUpdateCRLF();
 			}
 			else if (b == '\n')
 			{
 				if (lastb != '\r')
 				{
-					doUpdateCRLF();
+					DoUpdateCRLF();
 				}
 			}
 			else
 			{
-				doUpdateByte(b);
+				DoUpdateByte(b);
 			}
 
 			lastb = b;
 		}
 
-		private void doUpdateCRLF()
+		private void DoUpdateCRLF()
 		{
-			doUpdateByte((byte)'\r');
-			doUpdateByte((byte)'\n');
+			DoUpdateByte((byte)'\r');
+			DoUpdateByte((byte)'\n');
 		}
 
-		private void doUpdateByte(
+		private void DoUpdateByte(
 			byte b)
 		{
 			sig.Update(b);
 			dig.Update(b);
 		}
 
-		public void Update(
-            byte[] b)
+		public void Update(params byte[] b)
+        {
+            Update(b, 0, b.Length);
+        }
+
+		public void Update(byte[] b, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Update(b.AsSpan(off, len));
+#else
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                for (int i = 0; i != b.Length; i++)
+                int finish = off + len;
+
+				for (int i = off; i != finish; i++)
                 {
-                    doCanonicalUpdateByte(b[i]);
+                    DoCanonicalUpdateByte(b[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(b, 0, b.Length);
-                dig.BlockUpdate(b, 0, b.Length);
+                sig.BlockUpdate(b, off, len);
+                dig.BlockUpdate(b, off, len);
             }
+#endif
         }
 
-		public void Update(
-            byte[]	b,
-            int		off,
-            int		len)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
         {
             if (signatureType == PgpSignature.CanonicalTextDocument)
             {
-                int finish = off + len;
-
-				for (int i = off; i != finish; i++)
+                for (int i = 0; i < input.Length; ++i)
                 {
-                    doCanonicalUpdateByte(b[i]);
+                    DoCanonicalUpdateByte(input[i]);
                 }
             }
             else
             {
-                sig.BlockUpdate(b, off, len);
-                dig.BlockUpdate(b, off, len);
+                sig.BlockUpdate(input);
+                dig.BlockUpdate(input);
             }
         }
+#endif
 
-		/// <summary>Return the one pass header associated with the current signature.</summary>
+        /// <summary>Return the one pass header associated with the current signature.</summary>
         public PgpOnePassSignature GenerateOnePassVersion(
             bool isNested)
         {
diff --git a/crypto/src/openpgp/Rfc6637Utilities.cs b/crypto/src/openpgp/Rfc6637Utilities.cs
index 5d992ec51..e1405f481 100644
--- a/crypto/src/openpgp/Rfc6637Utilities.cs
+++ b/crypto/src/openpgp/Rfc6637Utilities.cs
@@ -69,11 +69,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
         public static byte[] CreateKey(PublicKeyPacket pubKeyData, ECPoint s)
         {
+            return CreateKey(pubKeyData, s.AffineXCoord.GetEncoded());
+        }
+
+        public static byte[] CreateKey(PublicKeyPacket pubKeyData, byte[] secret)
+        {
             byte[] userKeyingMaterial = CreateUserKeyingMaterial(pubKeyData);
 
             ECDHPublicBcpgKey ecKey = (ECDHPublicBcpgKey)pubKeyData.Key;
 
-            return Kdf(ecKey.HashAlgorithm, s, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial);
+            return Kdf(ecKey.HashAlgorithm, secret, GetKeyLength(ecKey.SymmetricKeyAlgorithm), userKeyingMaterial);
         }
 
         // RFC 6637 - Section 8
@@ -116,12 +121,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         //         ZB = x;
         //         MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param );
         //   return oBits leftmost bits of MB.
-        private static byte[] Kdf(HashAlgorithmTag digestAlg, ECPoint s, int keyLen, byte[] parameters)
+        private static byte[] Kdf(HashAlgorithmTag digestAlg, byte[] ZB, int keyLen, byte[] parameters)
         {
-            byte[] ZB = s.XCoord.GetEncoded();
-
-            string digestName = PgpUtilities.GetDigestName(digestAlg);
-			IDigest digest = DigestUtilities.GetDigest(digestName);
+            IDigest digest = PgpUtilities.CreateDigest(digestAlg);
 
             digest.Update(0x00);
             digest.Update(0x00);