Repository: randombit/botan
Pull request #3210 (2023-01-22 17:36:20)

New approach for discrete logarithm keys
Removes the DL_Scheme_PublicKey and DL_Scheme_PrivateKey classes, replaced by inner members

codecov-commenter: # [Codecov](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) Report
Base: **88.17**% // Head: **88.19**% // Increases project coverage by **`+0.01%`** :tada:
> Coverage data is based on head [(`09b970a`)](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) compared to base [(`e3a501c`)](https://codecov.io/gh/randombit/botan/commit/e3a501c4915222f396839cb7844dc14042277580?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).
> Patch coverage: 93.70% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3210      +/-   ##
==========================================
+ Coverage   88.17%   88.19%   +0.01%     
==========================================
  Files         609      609              
  Lines       68159    68256      +97     
  Branches     6792     6791       -1     
==========================================
+ Hits        60101    60197      +96     
- Misses       5226     5228       +2     
+ Partials     2832     2831       -1     
```


| [Impacted Files](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | Coverage Δ | |
|---|---|---|
| [src/lib/tls/tls12/msg\_server\_kex.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTIvbXNnX3NlcnZlcl9rZXguY3Bw) | `85.29% <ø> (-0.15%)` | :arrow_down: |
| [src/lib/tls/tls13/tls\_extensions\_key\_share.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvdGxzX2V4dGVuc2lvbnNfa2V5X3NoYXJlLmNwcA==) | `84.57% <ø> (ø)` | |
| [src/lib/tls/tls\_callbacks.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX2NhbGxiYWNrcy5jcHA=) | `74.68% <ø> (ø)` | |
| [src/lib/pubkey/dl\_group/dl\_group.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvZGxfZ3JvdXAvZGxfZ3JvdXAuY3Bw) | `82.22% <63.63%> (-3.23%)` | :arrow_down: |
| [src/lib/pubkey/elgamal/elgamal.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvZWxnYW1hbC9lbGdhbWFsLmNwcA==) | `88.29% <92.64%> (+1.99%)` | :arrow_up: |
| [src/lib/pubkey/dsa/dsa.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvZHNhL2RzYS5jcHA=) | `88.23% <93.05%> (+4.70%)` | :arrow_up: |
| [src/lib/pubkey/dl\_algo/dl\_scheme.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvZGxfYWxnby9kbF9zY2hlbWUuY3Bw) | `94.36% <94.36%> (ø)` | |
| [src/lib/pubkey/dh/dh.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvZGgvZGguY3Bw) | `97.18% <98.11%> (+6.98%)` | :arrow_up: |
| [src/lib/ffi/ffi\_pkey\_algs.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9mZmkvZmZpX3BrZXlfYWxncy5jcHA=) | `90.31% <100.00%> (-0.10%)` | :arrow_down: |
| [src/lib/pubkey/pk\_algs.cpp](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi9wdWJrZXkvcGtfYWxncy5jcHA=) | `83.22% <100.00%> (+0.11%)` | :arrow_up: |
| ... and [18 more](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/randombit/botan/pull/3210?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).


Patch:
diff --git a/doc/migration_guide.rst b/doc/migration_guide.rst
index e7dce8952..7ba07c68e 100644
--- a/doc/migration_guide.rst
+++ b/doc/migration_guide.rst
@@ -352,3 +352,23 @@ desired hash is not compatible with the algorithm.
 In previous versions, various APIs required that the application specify the
 hash function to be used. In most cases this can now be omitted (passing an
 empty string) and a suitable default will be chosen.
+
+Discrete Logarithm Key Changes
+--------------------------------
+
+Keys based on the discrete logarithm problem no longer derive from the
+DL_Scheme_PrivateKey and DL_Scheme_PublicKey classes; these classes
+have been removed.
+
+Functions to access DL algorithm interal fields (such as the integer value of
+the private key using ``get_x``) have been removed. If you need access to this
+information you can use the new ``get_int_field`` function.
+
+The constructors of the DL scheme private keys have changed. Previously, loading
+and creating a key used the same constructor, namely one taking arguments
+``(DL_Group, RandomNumberGenerator&, BigInt x = 0)`` and then the behavior of
+the constructor depend on if ``x`` was zero (in which case a new key was
+created) or otherwise if ``x`` was non-zero then it was taken as the private
+key. Now there are two constructors, one taking a random number generator and a
+group, which generates a new key, and a second taking a group and an integer,
+which loads an existing key.
diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp
index 2f3426bd1..aa411ddfe 100644
--- a/src/lib/ffi/ffi_pkey_algs.cpp
+++ b/src/lib/ffi/ffi_pkey_algs.cpp
@@ -14,12 +14,12 @@
 #include <botan/internal/ffi_rng.h>
 #include <botan/internal/ffi_mp.h>
 
-#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
-  #include <botan/ecc_key.h>
+#if defined(BOTAN_HAS_DL_GROUP)
+  #include <botan/dl_group.h>
 #endif
 
-#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY)
-  #include <botan/dl_algo.h>
+#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
+  #include <botan/ecc_key.h>
 #endif
 
 #if defined(BOTAN_HAS_RSA)
@@ -346,9 +346,8 @@ int botan_privkey_load_dsa(botan_privkey_t* key,
    *key = nullptr;
 
    return ffi_guard_thunk(__func__, [=]() -> int {
-      Botan::Null_RNG null_rng;
       Botan::DL_Group group(safe_get(p), safe_get(q), safe_get(g));
-      auto dsa = std::make_unique<Botan::DSA_PrivateKey>(null_rng, group, safe_get(x));
+      auto dsa = std::make_unique<Botan::DSA_PrivateKey>(group, safe_get(x));
       *key = new botan_privkey_struct(std::move(dsa));
       return BOTAN_FFI_SUCCESS;
       });
@@ -502,9 +501,8 @@ int botan_privkey_load_elgamal(botan_privkey_t* key,
 #if defined(BOTAN_HAS_ELGAMAL)
    *key = nullptr;
    return ffi_guard_thunk(__func__, [=]() -> int {
-      Botan::Null_RNG null_rng;
       Botan::DL_Group group(safe_get(p), safe_get(g));
-      auto elg = std::make_unique<Botan::ElGamal_PrivateKey>(null_rng, group, safe_get(x));
+      auto elg = std::make_unique<Botan::ElGamal_PrivateKey>(group, safe_get(x));
       *key = new botan_privkey_struct(std::move(elg));
       return BOTAN_FFI_SUCCESS;
       });
@@ -527,9 +525,8 @@ int botan_privkey_load_dh(botan_privkey_t* key,
 #if defined(BOTAN_HAS_DIFFIE_HELLMAN)
    *key = nullptr;
    return ffi_guard_thunk(__func__, [=]() -> int {
-      Botan::Null_RNG null_rng;
       Botan::DL_Group group(safe_get(p), safe_get(g));
-      auto dh = std::make_unique<Botan::DH_PrivateKey>(null_rng, group, safe_get(x));
+      auto dh = std::make_unique<Botan::DH_PrivateKey>(group, safe_get(x));
       *key = new botan_privkey_struct(std::move(dh));
       return BOTAN_FFI_SUCCESS;
       });
diff --git a/src/lib/pubkey/dh/dh.cpp b/src/lib/pubkey/dh/dh.cpp
index 5d6901a08..964f2dd2b 100644
--- a/src/lib/pubkey/dh/dh.cpp
+++ b/src/lib/pubkey/dh/dh.cpp
@@ -1,84 +1,106 @@
 /*
 * Diffie-Hellman
-* (C) 1999-2007,2016,2019 Jack Lloyd
+* (C) 1999-2007,2016,2019,2023 Jack Lloyd
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
 
 #include <botan/dh.h>
+#include <botan/internal/dl_scheme.h>
 #include <botan/internal/pk_ops_impl.h>
-#include <botan/internal/monty_exp.h>
 #include <botan/internal/blinding.h>
 
 namespace Botan {
 
-/*
-* DH_PublicKey Constructor
-*/
-DH_PublicKey::DH_PublicKey(const DL_Group& grp, const BigInt& y1)
+DH_PublicKey::DH_PublicKey(const AlgorithmIdentifier& alg_id,
+                           const std::vector<uint8_t>& key_bits)
    {
-   m_group = grp;
-   m_y = y1;
+   m_public_key = std::make_shared<DL_PublicKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_42);
+   }
+
+DH_PublicKey::DH_PublicKey(const DL_Group& group, const BigInt& y)
+   {
+   m_public_key = std::make_shared<DL_PublicKey>(group, y);
    }
 
-/*
-* Return the public value for key agreement
-*/
 std::vector<uint8_t> DH_PublicKey::public_value() const
    {
-   return unlock(BigInt::encode_1363(m_y, group_p().bytes()));
+   return m_public_key->public_key_as_bytes();
    }
 
-/*
-* Create a DH private key
-*/
-DH_PrivateKey::DH_PrivateKey(RandomNumberGenerator& rng,
-                             const DL_Group& grp,
-                             const BigInt& x_arg)
+size_t DH_PublicKey::estimated_strength() const
+   {
+   return m_public_key->estimated_strength();
+   }
+
+size_t DH_PublicKey::key_length() const
    {
-   m_group = grp;
+   return m_public_key->p_bits();
+   }
 
-   if(x_arg == 0)
-      {
-      const size_t exp_bits = grp.exponent_bits();
-      m_x.randomize(rng, exp_bits);
-      m_y = m_group.power_g_p(m_x, exp_bits);
-      }
-   else
-      {
-      m_x = x_arg;
+const BigInt& DH_PublicKey::get_int_field(const std::string& field) const
+   {
+   return m_public_key->get_int_field(algo_name(), field);
+   }
 
-      if(m_y == 0)
-         m_y = m_group.power_g_p(m_x, grp.p_bits());
-      }
+AlgorithmIdentifier DH_PublicKey::algorithm_identifier() const
+   {
+   return AlgorithmIdentifier(
+      object_identifier(),
+      m_public_key->group().DER_encode(DL_Group_Format::ANSI_X9_42));
+   }
+
+std::vector<uint8_t> DH_PublicKey::public_key_bits() const
+   {
+   return m_public_key->DER_encode();
+   }
+
+bool DH_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const
+   {
+   return m_public_key->check_key(rng, strong);
+   }
+
+DH_PrivateKey::DH_PrivateKey(RandomNumberGenerator& rng,
+                             const DL_Group& group)
+   {
+   m_private_key = std::make_shared<DL_PrivateKey>(group, rng);
+   m_public_key = m_private_key->public_key();
+   }
+
+DH_PrivateKey::DH_PrivateKey(const DL_Group& group,
+                             const BigInt& x)
+   {
+   m_private_key = std::make_shared<DL_PrivateKey>(group, x);
+   m_public_key = m_private_key->public_key();
    }
 
-/*
-* Load a DH private key
-*/
 DH_PrivateKey::DH_PrivateKey(const AlgorithmIdentifier& alg_id,
-                             const secure_vector<uint8_t>& key_bits) :
-   DL_Scheme_PrivateKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_42)
+                             const secure_vector<uint8_t>& key_bits)
    {
-   if(m_y.is_zero())
-      {
-      m_y = m_group.power_g_p(m_x, m_group.p_bits());
-      }
+   m_private_key = std::make_shared<DL_PrivateKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_42);
+   m_public_key = m_private_key->public_key();
    }
 
 std::unique_ptr<Public_Key> DH_PrivateKey::public_key() const
    {
-   return std::make_unique<DH_PublicKey>(get_group(), get_y());
+   return std::unique_ptr<DH_PublicKey>(new DH_PublicKey(m_public_key));
    }
 
-/*
-* Return the public value for key agreement
-*/
 std::vector<uint8_t> DH_PrivateKey::public_value() const
    {
    return DH_PublicKey::public_value();
    }
 
+secure_vector<uint8_t> DH_PrivateKey::private_key_bits() const
+   {
+   return m_private_key->DER_encode();
+   }
+
+const BigInt& DH_PrivateKey::get_int_field(const std::string& field) const
+   {
+   return m_private_key->get_int_field(algo_name(), field);
+   }
+
 namespace {
 
 /**
@@ -88,32 +110,36 @@ class DH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF
    {
    public:
 
-      DH_KA_Operation(const DH_PrivateKey& key, const std::string& kdf, RandomNumberGenerator& rng) :
+      DH_KA_Operation(std::shared_ptr<const DL_PrivateKey> key,
+                      const std::string& kdf,
+                      RandomNumberGenerator& rng) :
          PK_Ops::Key_Agreement_with_KDF(kdf),
-         m_p(key.group_p()),
-         m_x(key.get_x()),
-         m_x_bits(m_x.bits()),
-         m_monty_p(key.get_group().monty_params_p()),
-         m_blinder(m_p,
+         m_key(key),
+         m_blinder(key->group().get_p(),
                    rng,
                    [](const BigInt& k) { return k; },
-                   [this](const BigInt& k) { return powermod_x_p(inverse_mod(k, m_p)); })
+                   [this](const BigInt& k)
+                      {
+                      const BigInt inv_k = inverse_mod(k, group().get_p());
+                      return powermod_x_p(inv_k);
+                      })
          {}
 
-      size_t agreed_value_size() const override { return m_p.bytes(); }
+      size_t agreed_value_size() const override { return group().p_bytes(); }
 
       secure_vector<uint8_t> raw_agree(const uint8_t w[], size_t w_len) override;
    private:
+      const DL_Group& group() const
+         {
+         return m_key->group();
+         }
+
       BigInt powermod_x_p(const BigInt& v) const
          {
-         const size_t powm_window = 4;
-         auto powm_v_p = monty_precompute(m_monty_p, v, powm_window);
-         return monty_execute(*powm_v_p, m_x, m_x_bits);
+         return group().power_b_p(v, m_key->private_key());
          }
 
-      const BigInt& m_p;
-      const BigInt& m_x;
-      const size_t m_x_bits;
+      std::shared_ptr<const DL_PrivateKey> m_key;
       std::shared_ptr<const Montgomery_Params> m_monty_p;
       Blinder m_blinder;
    };
@@ -122,14 +148,14 @@ secure_vector<uint8_t> DH_KA_Operation::raw_agree(const uint8_t w[], size_t w_le
    {
    BigInt v = BigInt::decode(w, w_len);
 
-   if(v <= 1 || v >= m_p - 1)
+   if(v <= 1 || v >= group().get_p())
       throw Invalid_Argument("DH agreement - invalid key provided");
 
    v = m_blinder.blind(v);
    v = powermod_x_p(v);
    v = m_blinder.unblind(v);
 
-   return BigInt::encode_1363(v, m_p.bytes());
+   return BigInt::encode_1363(v, group().p_bytes());
    }
 
 }
@@ -140,7 +166,7 @@ DH_PrivateKey::create_key_agreement_op(RandomNumberGenerator& rng,
                                        const std::string& provider) const
    {
    if(provider == "base" || provider.empty())
-      return std::make_unique<DH_KA_Operation>(*this, params, rng);
+      return std::make_unique<DH_KA_Operation>(this->m_private_key, params, rng);
    throw Provider_Not_Found(algo_name(), provider);
    }
 
diff --git a/src/lib/pubkey/dh/dh.h b/src/lib/pubkey/dh/dh.h
index b2be40ab1..bf2f5ae89 100644
--- a/src/lib/pubkey/dh/dh.h
+++ b/src/lib/pubkey/dh/dh.h
@@ -1,6 +1,6 @@
 /*
 * Diffie-Hellman
-* (C) 1999-2007 Jack Lloyd
+* (C) 1999-2007,2023 Jack Lloyd
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -8,58 +8,77 @@
 #ifndef BOTAN_DIFFIE_HELLMAN_H_
 #define BOTAN_DIFFIE_HELLMAN_H_
 
-#include <botan/dl_algo.h>
+#include <botan/pk_keys.h>
+#include <memory>
 
 namespace Botan {
 
+class BigInt;
+class DL_Group;
+class DL_PublicKey;
+class DL_PrivateKey;
+
 /**
 * This class represents Diffie-Hellman public keys.
 */
-class BOTAN_PUBLIC_API(2,0) DH_PublicKey : public virtual DL_Scheme_PublicKey
+class BOTAN_PUBLIC_API(2,0) DH_PublicKey : public virtual Public_Key
    {
    public:
-      std::string algo_name() const override { return "DH"; }
-
-      std::vector<uint8_t> public_value() const;
-
-      DL_Group_Format group_format() const override { return DL_Group_Format::ANSI_X9_42; }
-
-      bool supports_operation(PublicKeyOperation op) const override
-         {
-         return (op == PublicKeyOperation::KeyAgreement);
-         }
-
       /**
       * Create a public key.
       * @param alg_id the X.509 algorithm identifier
       * @param key_bits DER encoded public key bits
       */
       DH_PublicKey(const AlgorithmIdentifier& alg_id,
-                   const std::vector<uint8_t>& key_bits) :
-         DL_Scheme_PublicKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_42) {}
+                   const std::vector<uint8_t>& key_bits);
 
       /**
       * Construct a public key with the specified parameters.
-      * @param grp the DL group to use in the key
+      * @param group the DL group to use in the key
       * @param y the public value y
       */
-      DH_PublicKey(const DL_Group& grp, const BigInt& y);
-   protected:
+      DH_PublicKey(const DL_Group& group, const BigInt& y);
+
+      AlgorithmIdentifier algorithm_identifier() const override;
+      std::vector<uint8_t> public_key_bits() const override;
+
+      bool check_key(RandomNumberGenerator& rng, bool strong) const override;
+
+      size_t estimated_strength() const override;
+      size_t key_length() const override;
+
+      std::vector<uint8_t> public_value() const;
+
+      std::string algo_name() const override { return "DH"; }
+
+      const BigInt& get_int_field(const std::string& field) const override;
+
+      bool supports_operation(PublicKeyOperation op) const override
+         {
+         return (op == PublicKeyOperation::KeyAgreement);
+         }
+   private:
+      friend class DH_PrivateKey;
+
       DH_PublicKey() = default;
+
+      DH_PublicKey(std::shared_ptr<const DL_PublicKey> key) :
+         m_public_key(key) {}
+
+      std::shared_ptr<const DL_PublicKey> m_public_key;
    };
 
 /**
 * This class represents Diffie-Hellman private keys.
 */
-class BOTAN_PUBLIC_API(2,0) DH_PrivateKey final : public DH_PublicKey,
-                                public PK_Key_Agreement_Key,
-                                public virtual DL_Scheme_PrivateKey
+class BOTAN_PUBLIC_API(2,0) DH_PrivateKey final :
+   public DH_PublicKey,
+   public PK_Key_Agreement_Key,
+   public virtual Private_Key
    {
    public:
-      std::vector<uint8_t> public_value() const override;
-
       /**
-      * Load a private key.
+      * Load a private key from the ASN.1 encoding
       * @param alg_id the X.509 algorithm identifier
       * @param key_bits PKCS #8 structure
       */
@@ -67,20 +86,36 @@ class BOTAN_PUBLIC_API(2,0) DH_PrivateKey final : public DH_PublicKey,
                     const secure_vector<uint8_t>& key_bits);
 
       /**
-      * Create a private key.
-      * @param rng random number generator to use
-      * @param grp the group to be used in the key
-      * @param x the key's secret value (or if zero, generate a new key)
+      * Load a private key from the integer encoding
+      * @param group the underlying DL group
+      * @param private_key the private key
       */
-      DH_PrivateKey(RandomNumberGenerator& rng, const DL_Group& grp,
-                    const BigInt& x = BigInt::zero());
+      DH_PrivateKey(const DL_Group& group,
+                    const BigInt& private_key);
+
+      /**
+      * Create a new private key.
+      * @param group the underlying DL group
+      * @param rng the RNG to use
+      */
+      DH_PrivateKey(RandomNumberGenerator& rng,
+                    const DL_Group& group);
 
       std::unique_ptr<Public_Key> public_key() const override;
 
+      std::vector<uint8_t> public_value() const override;
+
+      secure_vector<uint8_t> private_key_bits() const override;
+
+      const BigInt& get_int_field(const std::string& field) const override;
+
       std::unique_ptr<PK_Ops::Key_Agreement>
          create_key_agreement_op(RandomNumberGenerator& rng,
                                  const std::string& params,
                                  const std::string& provider) const override;
+
+   private:
+      std::shared_ptr<const DL_PrivateKey> m_private_key;
    };
 
 }
diff --git a/src/lib/pubkey/dl_algo/dl_algo.cpp b/src/lib/pubkey/dl_algo/dl_algo.cpp
deleted file mode 100644
index 41b0f169a..000000000
--- a/src/lib/pubkey/dl_algo/dl_algo.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
-* DL Scheme
-* (C) 1999-2007 Jack Lloyd
-*
-* Botan is released under the Simplified BSD License (see license.txt)
-*/
-
-#include <botan/dl_algo.h>
-#include <botan/numthry.h>
-#include <botan/der_enc.h>
-#include <botan/ber_dec.h>
-
-namespace Botan {
-
-size_t DL_Scheme_PublicKey::key_length() const
-   {
-   return m_group.p_bits();
-   }
-
-size_t DL_Scheme_PublicKey::estimated_strength() const
-   {
-   return m_group.estimated_strength();
-   }
-
-AlgorithmIdentifier DL_Scheme_PublicKey::algorithm_identifier() const
-   {
-   return AlgorithmIdentifier(object_identifier(),
-                              m_group.DER_encode(group_format()));
-   }
-
-std::vector<uint8_t> DL_Scheme_PublicKey::public_key_bits() const
-   {
-   std::vector<uint8_t> output;
-   DER_Encoder(output).encode(m_y);
-   return output;
-   }
-
-DL_Scheme_PublicKey::DL_Scheme_PublicKey(const DL_Group& group, const BigInt& y) :
-   m_y(y),
-   m_group(group)
-   {
-   }
-
-DL_Scheme_PublicKey::DL_Scheme_PublicKey(const AlgorithmIdentifier& alg_id,
-                                         const std::vector<uint8_t>& key_bits,
-                                         DL_Group_Format format) :
-   m_group(alg_id.parameters(), format)
-   {
-   BER_Decoder(key_bits).decode(m_y);
-   }
-
-secure_vector<uint8_t> DL_Scheme_PrivateKey::private_key_bits() const
-   {
-   return DER_Encoder().encode(m_x).get_contents();
-   }
-
-DL_Scheme_PrivateKey::DL_Scheme_PrivateKey(const AlgorithmIdentifier& alg_id,
-                                           const secure_vector<uint8_t>& key_bits,
-                                           DL_Group_Format format)
-   {
-   m_group.BER_decode(alg_id.parameters(), format);
-
-   BER_Decoder(key_bits).decode(m_x);
-   }
-
-/*
-* Check Public DL Parameters
-*/
-bool DL_Scheme_PublicKey::check_key(RandomNumberGenerator& rng,
-                                    bool strong) const
-   {
-   if(!m_group.verify_public_element(m_y))
-      return false;
-
-   return m_group.verify_group(rng, strong);
-   }
-
-/*
-* Check DL Scheme Private Parameters
-*/
-bool DL_Scheme_PrivateKey::check_key(RandomNumberGenerator& rng,
-                                     bool strong) const
-   {
-   return m_group.verify_group(rng, strong) && m_group.verify_element_pair(m_y, m_x);
-   }
-
-const BigInt& DL_Scheme_PublicKey::get_int_field(const std::string& field) const
-   {
-   if(field == "p")
-      return this->group_p();
-   else if(field == "q")
-      return this->group_q();
-   else if(field == "g")
-      return this->group_g();
-   else if(field == "y")
-      return this->get_y();
-   else
-      return Public_Key::get_int_field(field);
-   }
-
-const BigInt& DL_Scheme_PrivateKey::get_int_field(const std::string& field) const
-   {
-   if(field == "x")
-      return this->get_x();
-   else
-      return DL_Scheme_PublicKey::get_int_field(field);
-   }
-
-}
diff --git a/src/lib/pubkey/dl_algo/dl_algo.h b/src/lib/pubkey/dl_algo/dl_algo.h
deleted file mode 100644
index 9dd76a70a..000000000
--- a/src/lib/pubkey/dl_algo/dl_algo.h
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
-* DL Scheme
-* (C) 1999-2007 Jack Lloyd
-*
-* Botan is released under the Simplified BSD License (see license.txt)
-*/
-
-#ifndef BOTAN_DL_ALGO_H_
-#define BOTAN_DL_ALGO_H_
-
-#include <botan/dl_group.h>
-#include <botan/pk_keys.h>
-
-namespace Botan {
-
-/**
-* This class represents discrete logarithm (DL) public keys.
-*/
-class BOTAN_PUBLIC_API(2,0) DL_Scheme_PublicKey : public virtual Public_Key
-   {
-   public:
-      bool check_key(RandomNumberGenerator& rng, bool) const override;
-
-      AlgorithmIdentifier algorithm_identifier() const override;
-
-      std::vector<uint8_t> public_key_bits() const override;
-
-      /**
-      * Get the DL domain parameters of this key.
-      * @return DL domain parameters of this key
-      */
-      const DL_Group& get_domain() const { return m_group; }
-
-      /**
-      * Get the DL domain parameters of this key.
-      * @return DL domain parameters of this key
-      */
-      const DL_Group& get_group() const { return m_group; }
-
-      /**
-      * Get the public value y with y = g^x mod p where x is the secret key.
-      */
-      const BigInt& get_y() const { return m_y; }
-
-      /**
-      * Get the prime p of the underlying DL group.
-      * @return prime p
-      */
-      const BigInt& group_p() const { return m_group.get_p(); }
-
-      /**
-      * Get the prime q of the underlying DL group.
-      * @return prime q
-      */
-      const BigInt& group_q() const { return m_group.get_q(); }
-
-      /**
-      * Get the generator g of the underlying DL group.
-      * @return generator g
-      */
-      const BigInt& group_g() const { return m_group.get_g(); }
-
-      /**
-      * Get the underlying groups encoding format.
-      * @return encoding format
-      */
-      virtual DL_Group_Format group_format() const = 0;
-
-      size_t key_length() const override;
-      size_t estimated_strength() const override;
-
-      const BigInt& get_int_field(const std::string& field) const override;
-
-      DL_Scheme_PublicKey& operator=(const DL_Scheme_PublicKey& other) = default;
-
-   protected:
-      DL_Scheme_PublicKey() = default;
-
-      /**
-      * Create a public key.
-      * @param alg_id the X.509 algorithm identifier
-      * @param key_bits DER encoded public key bits
-      * @param group_format the underlying groups encoding format
-      */
-      DL_Scheme_PublicKey(const AlgorithmIdentifier& alg_id,
-                          const std::vector<uint8_t>& key_bits,
-                          DL_Group_Format group_format);
-
-      DL_Scheme_PublicKey(const DL_Group& group, const BigInt& y);
-
-      /**
-      * The DL public key
-      */
-      BigInt m_y;
-
-      /**
-      * The DL group
-      */
-      DL_Group m_group;
-   };
-
-/**
-* This class represents discrete logarithm (DL) private keys.
-*/
-class BOTAN_PUBLIC_API(2,0) DL_Scheme_PrivateKey : public virtual DL_Scheme_PublicKey,
-                                       public virtual Private_Key
-   {
-   public:
-      bool check_key(RandomNumberGenerator& rng, bool) const override;
-
-      /**
-      * Get the secret key x.
-      * @return secret key
-      */
-      const BigInt& get_x() const { return m_x; }
-
-      secure_vector<uint8_t> private_key_bits() const override;
-
-      DL_Scheme_PrivateKey& operator=(const DL_Scheme_PrivateKey& other) = default;
-
-      const BigInt& get_int_field(const std::string& field) const override;
-
-   protected:
-      /**
-      * Create a private key.
-      * @param alg_id the X.509 algorithm identifier
-      * @param key_bits DER encoded private key bits
-      * @param group_format the underlying groups encoding format
-      */
-      DL_Scheme_PrivateKey(const AlgorithmIdentifier& alg_id,
-                           const secure_vector<uint8_t>& key_bits,
-                           DL_Group_Format group_format);
-
-      DL_Scheme_PrivateKey() = default;
-
-      /**
-      * The DL private key
-      */
-      BigInt m_x;
-   };
-
-}
-
-#endif
diff --git a/src/lib/pubkey/dl_algo/dl_scheme.cpp b/src/lib/pubkey/dl_algo/dl_scheme.cpp
new file mode 100644
index 000000000..afd4881e4
--- /dev/null
+++ b/src/lib/pubkey/dl_algo/dl_scheme.cpp
@@ -0,0 +1,163 @@
+/*
+* (C) 2023 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/internal/dl_scheme.h>
+#include <botan/der_enc.h>
+#include <botan/ber_dec.h>
+#include <botan/assert.h>
+
+namespace Botan {
+
+namespace {
+
+template<typename Alloc>
+BigInt decode_single_bigint(const std::vector<uint8_t, Alloc>& key_bits)
+   {
+   BigInt x;
+   BER_Decoder(key_bits).decode(x);
+   return x;
+   }
+
+BigInt generate_private_dl_key(const DL_Group& group, RandomNumberGenerator& rng)
+   {
+   if(group.has_q() && group.q_bits() >= 160 && group.q_bits() <= 384)
+      {
+      return BigInt::random_integer(rng, 2, group.get_q());
+      }
+   else
+      {
+      return BigInt(rng, group.exponent_bits());
+      }
+   }
+
+BigInt check_dl_private_key_input(const BigInt& x, const DL_Group& group)
+   {
+   BOTAN_ARG_CHECK(group.verify_private_element(x),
+                   "Invalid discrete logarithm private key value");
+   return x;
+   }
+
+}
+
+DL_PublicKey::DL_PublicKey(const DL_Group& group,
+                           const BigInt& public_key) :
+   m_group(group),
+   m_public_key(public_key)
+   {
+   }
+
+DL_PublicKey::DL_PublicKey(const AlgorithmIdentifier& alg_id,
+                           const std::vector<uint8_t>& key_bits,
+                           DL_Group_Format format) :
+   m_group(alg_id.parameters(), format),
+   m_public_key(decode_single_bigint(key_bits))
+   {
+   }
+
+std::vector<uint8_t> DL_PublicKey::public_key_as_bytes() const
+   {
+   std::vector<uint8_t> bits(m_group.p_bytes());
+   BigInt::encode_1363(bits.data(), bits.size(), m_public_key);
+   return bits;
+   }
+
+std::vector<uint8_t> DL_PublicKey::DER_encode() const
+   {
+   std::vector<uint8_t> output;
+   DER_Encoder(output).encode(m_public_key);
+   return output;
+   }
+
+bool DL_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const
+   {
+   return m_group.verify_group(rng, strong) &&
+      m_group.verify_public_element(m_public_key);
+   }
+
+size_t DL_PublicKey::estimated_strength() const
+   {
+   return m_group.estimated_strength();
+   }
+
+size_t DL_PublicKey::p_bits() const
+   {
+   return m_group.p_bits();
+   }
+
+DL_PrivateKey::DL_PrivateKey(const DL_Group& group,
+                             const BigInt& private_key) :
+   m_group(group),
+   m_private_key(check_dl_private_key_input(private_key, m_group)),
+   m_public_key(m_group.power_g_p(m_private_key, m_group.p_bits()))
+   {
+   }
+
+DL_PrivateKey::DL_PrivateKey(const DL_Group& group,
+                             RandomNumberGenerator& rng) :
+   m_group(group),
+   m_private_key(generate_private_dl_key(group, rng)),
+   m_public_key(m_group.power_g_p(m_private_key, m_group.p_bits()))
+   {
+   }
+
+DL_PrivateKey::DL_PrivateKey(const AlgorithmIdentifier& alg_id,
+                             const secure_vector<uint8_t>& key_bits,
+                             DL_Group_Format format) :
+   m_group(alg_id.parameters(), format),
+   m_private_key(check_dl_private_key_input(decode_single_bigint(key_bits), m_group)),
+   m_public_key(m_group.power_g_p(m_private_key, m_group.p_bits()))
+   {
+   }
+
+secure_vector<uint8_t> DL_PrivateKey::DER_encode() const
+   {
+   return DER_Encoder().encode(m_private_key).get_contents();
+   }
+
+bool DL_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const
+   {
+   return m_group.verify_group(rng, strong) &&
+      m_group.verify_private_element(m_private_key);
+   }
+
+std::shared_ptr<DL_PublicKey> DL_PrivateKey::public_key() const
+   {
+   return std::make_shared<DL_PublicKey>(m_group, m_public_key);
+   }
+
+const BigInt& DL_PublicKey::get_int_field(const std::string& algo,
+                                          const std::string& field) const
+   {
+   if(field == "p")
+      return m_group.get_p();
+   else if(field == "q")
+      return m_group.get_q();
+   else if(field == "g")
+      return m_group.get_g();
+   else if(field == "y")
+      return m_public_key;
+   else
+      throw Unknown_PK_Field_Name(algo, field);
+   }
+
+const BigInt& DL_PrivateKey::get_int_field(const std::string& algo,
+                                           const std::string& field) const
+   {
+   if(field == "p")
+      return m_group.get_p();
+   else if(field == "q")
+      return m_group.get_q();
+   else if(field == "g")
+      return m_group.get_g();
+   else if(field == "x")
+      return m_private_key;
+   else if(field == "y")
+      return m_public_key;
+   else
+      throw Unknown_PK_Field_Name(algo, field);
+   }
+
+}
diff --git a/src/lib/pubkey/dl_algo/dl_scheme.h b/src/lib/pubkey/dl_algo/dl_scheme.h
new file mode 100644
index 000000000..b3b415536
--- /dev/null
+++ b/src/lib/pubkey/dl_algo/dl_scheme.h
@@ -0,0 +1,95 @@
+/*
+* (C) 2023 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_DL_SCHEME_H_
+#define BOTAN_DL_SCHEME_H_
+
+#include <botan/bigint.h>
+#include <botan/dl_group.h>
+#include <memory>
+
+namespace Botan {
+
+class AlgorithmIdentifier;
+class RandomNumberGenerator;
+
+class DL_PublicKey final
+   {
+   public:
+      DL_PublicKey(const DL_Group& group,
+                   const BigInt& public_key);
+
+      DL_PublicKey(const AlgorithmIdentifier& alg_id,
+                   const std::vector<uint8_t>& key_bits,
+                   DL_Group_Format format);
+
+      bool check_key(RandomNumberGenerator& rng, bool strong) const;
+
+      const DL_Group& group() const { return m_group; }
+      const BigInt& public_key() const { return m_public_key; }
+
+      // Return the binary representation of the integer public key
+      std::vector<uint8_t> public_key_as_bytes() const;
+
+      const BigInt& get_int_field(const std::string& algo_name,
+                                  const std::string& field) const;
+
+      std::vector<uint8_t> DER_encode() const;
+
+      size_t estimated_strength() const;
+
+      size_t p_bits() const;
+   private:
+      const DL_Group m_group;
+      const BigInt m_public_key;
+   };
+
+class DL_PrivateKey final
+   {
+   public:
+      DL_PrivateKey(const DL_Group& group,
+                    const BigInt& private_key);
+
+      DL_PrivateKey(const DL_Group& group,
+                    RandomNumberGenerator& rng);
+
+      DL_PrivateKey(const AlgorithmIdentifier& alg_id,
+                    const secure_vector<uint8_t>& key_bits,
+                    DL_Group_Format format);
+
+      bool check_key(RandomNumberGenerator& rng, bool strong) const;
+
+      /**
+      * Return a new shared_ptr of the associated public key
+      */
+      std::shared_ptr<DL_PublicKey> public_key() const;
+
+      /**
+      * Return the group this key operates in
+      */
+      const DL_Group& group() const { return m_group; }
+
+      /**
+      * Return the integer value of the private key
+      */
+      const BigInt& private_key() const { return m_private_key; }
+
+      /**
+      * DER encode the private key
+      */
+      secure_vector<uint8_t> DER_encode() const;
+
+      const BigInt& get_int_field(const std::string& algo_name,
+                                  const std::string& field) const;
+   private:
+      const DL_Group m_group;
+      const BigInt m_private_key;
+      const BigInt m_public_key;
+   };
+
+}
+
+#endif
diff --git a/src/lib/pubkey/dl_algo/info.txt b/src/lib/pubkey/dl_algo/info.txt
index 1d946e8a7..819a19f1b 100644
--- a/src/lib/pubkey/dl_algo/info.txt
+++ b/src/lib/pubkey/dl_algo/info.txt
@@ -1,10 +1,10 @@
 <defines>
-DL_PUBLIC_KEY_FAMILY -> 20131128
+DL_SCHEME -> 20230101
 </defines>
 
 <module_info>
 name -> "Discrete Logarithm"
-brief -> "Base classes for discrete logarithm based schemes"
+brief -> "Classes for discrete logarithm based schemes"
 </module_info>
 
 <requires>
@@ -14,6 +14,6 @@ numbertheory
 rng
 </requires>
 
-<header:public>
-dl_algo.h
-</header:public>
+<header:internal>
+dl_scheme.h
+</header:internal>
diff --git a/src/lib/pubkey/dl_group/dl_group.cpp b/src/lib/pubkey/dl_group/dl_group.cpp
index 38c4ebd59..15d251b93 100644
--- a/src/lib/pubkey/dl_group/dl_group.cpp
+++ b/src/lib/pubkey/dl_group/dl_group.cpp
@@ -379,6 +379,20 @@ bool DL_Group::verify_public_element(const BigInt& y) const
    return true;
    }
 
+bool DL_Group::verify_private_element(const BigInt& x) const
+   {
+   const BigInt& p = get_p();
+   const BigInt& q = get_q();
+
+   if(x <= 1 || x >= p)
+      return false;
+
+   if(q > 0 && x > q)
+      return false;
+
+   return true;
+   }
+
 bool DL_Group::verify_element_pair(const BigInt& y, const BigInt& x) const
    {
    const BigInt& p = get_p();
@@ -484,6 +498,11 @@ std::shared_ptr<const Montgomery_Params> DL_Group::monty_params_p() const
    return data().monty_params_p();
    }
 
+bool DL_Group::has_q() const
+   {
+   return data().q_is_set();
+   }
+
 size_t DL_Group::p_bits() const
    {
    return data().p_bits();
@@ -578,6 +597,11 @@ BigInt DL_Group::power_g_p(const BigInt& x, size_t max_x_bits) const
    return data().power_g_p(x, max_x_bits);
    }
 
+BigInt DL_Group::power_b_p(const BigInt& b, const BigInt& x) const
+   {
+   return this->power_b_p(b, x, data().p_bits());
+   }
+
 BigInt DL_Group::power_b_p(const BigInt& b, const BigInt& x, size_t max_x_bits) const
    {
    return data().power_b_p(b, x, max_x_bits);
diff --git a/src/lib/pubkey/dl_group/dl_group.h b/src/lib/pubkey/dl_group/dl_group.h
index b9355da6f..2eededa8e 100644
--- a/src/lib/pubkey/dl_group/dl_group.h
+++ b/src/lib/pubkey/dl_group/dl_group.h
@@ -160,6 +160,14 @@ class BOTAN_PUBLIC_API(2,0) DL_Group final
       */
       bool verify_public_element(const BigInt& y) const;
 
+      /**
+      * Verify a private element
+      *
+      * Specifically this checks that x is > 1 and < p, and additionally if
+      * q is set then x must be < q
+      */
+      bool verify_private_element(const BigInt& x) const;
+
       /**
       * Verify a pair of elements y = g^x
       *
@@ -262,6 +270,15 @@ class BOTAN_PUBLIC_API(2,0) DL_Group final
       */
       BigInt power_b_p(const BigInt& b, const BigInt& x, size_t max_x_bits) const;
 
+      /**
+      * Modular exponentiation
+      * @param b the base
+      * @param x the exponent
+      *
+      * @return (b^x) % p
+      */
+      BigInt power_b_p(const BigInt& b, const BigInt& x) const;
+
       /**
       * Multi-exponentiate
       * Return (g^x * y^z) % p
@@ -299,6 +316,11 @@ class BOTAN_PUBLIC_API(2,0) DL_Group final
       */
       size_t q_bytes() const;
 
+      /**
+      * Return if the q value is set
+      */
+      bool has_q() const;
+
       /**
       * Return size in bits of a secret exponent
       *
diff --git a/src/lib/pubkey/dsa/dsa.cpp b/src/lib/pubkey/dsa/dsa.cpp
index 6af71f21d..9629b3e86 100644
--- a/src/lib/pubkey/dsa/dsa.cpp
+++ b/src/lib/pubkey/dsa/dsa.cpp
@@ -1,6 +1,6 @@
 /*
 * DSA
-* (C) 1999-2010,2014,2016 Jack Lloyd
+* (C) 1999-2010,2014,2016,2023 Jack Lloyd
 * (C) 2016 René Korthaus
 *
 * Botan is released under the Simplified BSD License (see license.txt)
@@ -8,10 +8,10 @@
 
 #include <botan/dsa.h>
 #include <botan/internal/keypair.h>
-#include <botan/reducer.h>
-#include <botan/rng.h>
-#include <botan/internal/divide.h>
+#include <botan/internal/dl_scheme.h>
 #include <botan/internal/pk_ops_impl.h>
+#include <botan/internal/divide.h>
+#include <botan/numthry.h>
 
 #if defined(BOTAN_HAS_RFC6979_GENERATOR)
   #include <botan/internal/rfc6979.h>
@@ -19,73 +19,106 @@
 
 namespace Botan {
 
-/*
-* DSA_PublicKey Constructor
-*/
+size_t DSA_PublicKey::message_part_size() const
+   {
+   return m_public_key->group().q_bytes();
+   }
 
-DSA_PublicKey::DSA_PublicKey(const AlgorithmIdentifier& alg_id,
-                             const std::vector<uint8_t>& key_bits) :
-   DL_Scheme_PublicKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_57)
+size_t DSA_PublicKey::estimated_strength() const
    {
-   BOTAN_ARG_CHECK(group_q().bytes() > 0, "Q parameter must be set for DSA");
+   return m_public_key->estimated_strength();
    }
 
+size_t DSA_PublicKey::key_length() const
+   {
+   return m_public_key->p_bits();
+   }
 
-DSA_PublicKey::DSA_PublicKey(const DL_Group& grp, const BigInt& y1)
+const BigInt& DSA_PublicKey::get_int_field(const std::string& field) const
    {
-   m_group = grp;
-   m_y = y1;
+   return m_public_key->get_int_field(algo_name(), field);
+   }
 
-   BOTAN_ARG_CHECK(grp.q_bytes() > 0, "Q parameter must be set for DSA");
+AlgorithmIdentifier DSA_PublicKey::algorithm_identifier() const
+   {
+   return AlgorithmIdentifier(
+      object_identifier(),
+      m_public_key->group().DER_encode(DL_Group_Format::ANSI_X9_57));
+   }
+
+std::vector<uint8_t> DSA_PublicKey::public_key_bits() const
+   {
+   return m_public_key->DER_encode();
+   }
+
+bool DSA_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const
+   {
+   return m_public_key->check_key(rng, strong);
+   }
+
+DSA_PublicKey::DSA_PublicKey(const AlgorithmIdentifier& alg_id,
+                             const std::vector<uint8_t>& key_bits)
+   {
+   m_public_key = std::make_shared<DL_PublicKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_57);
+
+   BOTAN_ARG_CHECK(m_public_key->group().has_q(),
+                   "Q parameter must be set for DSA");
+   }
+
+DSA_PublicKey::DSA_PublicKey(const DL_Group& group, const BigInt& y)
+   {
+   m_public_key = std::make_shared<DL_PublicKey>(group, y);
+
+   BOTAN_ARG_CHECK(m_public_key->group().has_q(),
+                   "Q parameter must be set for DSA");
    }
 
-/*
-* Create a DSA private key
-*/
 DSA_PrivateKey::DSA_PrivateKey(RandomNumberGenerator& rng,
-                               const DL_Group& grp,
-                               const BigInt& x_arg)
+                               const DL_Group& group)
+   {
+   m_private_key = std::make_shared<DL_PrivateKey>(group, rng);
+   m_public_key = m_private_key->public_key();
+   }
+
+DSA_PrivateKey::DSA_PrivateKey(const DL_Group& group,
+                               const BigInt& x)
    {
-   m_group = grp;
-   BOTAN_ARG_CHECK(x_arg.is_positive(), "x must be positive");
-
-   if(x_arg == 0)
-      m_x = BigInt::random_integer(rng, 2, group_q());
-   else
-      {
-      BOTAN_ARG_CHECK(m_x < m_group.get_q(), "x must not be larger than q");
-      m_x = x_arg;
-      }
-
-   m_y = m_group.power_g_p(m_x, m_group.q_bits());
+   m_private_key = std::make_shared<DL_PrivateKey>(group, x);
+   m_public_key = m_private_key->public_key();
    }
 
 DSA_PrivateKey::DSA_PrivateKey(const AlgorithmIdentifier& alg_id,
-                               const secure_vector<uint8_t>& key_bits) :
-   DL_Scheme_PrivateKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_57)
+                               const secure_vector<uint8_t>& key_bits)
    {
-   BOTAN_ARG_CHECK(m_x > 0, "x must be greater than zero");
-   BOTAN_ARG_CHECK(m_x < m_group.get_q(), "x must not be larger than q");
-   m_y = m_group.power_g_p(m_x, m_group.q_bits());
+   m_private_key = std::make_shared<DL_PrivateKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_57);
+   m_public_key = m_private_key->public_key();
    }
 
-/*
-* Check Private DSA Parameters
-*/
 bool DSA_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const
    {
-   if(!DL_Scheme_PrivateKey::check_key(rng, strong) || m_x >= group_q())
+   if(!m_private_key->check_key(rng, strong))
       return false;
 
-   if(!strong)
-      return true;
+   if(m_private_key->private_key() >= m_private_key->group().get_q())
+      return false;
 
    return KeyPair::signature_consistency_check(rng, *this, "SHA-256");
    }
 
+secure_vector<uint8_t> DSA_PrivateKey::private_key_bits() const
+   {
+   return m_private_key->DER_encode();
+   }
+
+const BigInt& DSA_PrivateKey::get_int_field(const std::string& field) const
+   {
+   return m_private_key->get_int_field(algo_name(), field);
+   }
+
 std::unique_ptr<Public_Key> DSA_PrivateKey::public_key() const
    {
-   return std::make_unique<DSA_PublicKey>(get_group(), get_y());
+   // can't use make_unique here due to private constructor
+   return std::unique_ptr<DSA_PublicKey>(new DSA_PublicKey(m_public_key));
    }
 
 namespace {
@@ -96,26 +129,24 @@ namespace {
 class DSA_Signature_Operation final : public PK_Ops::Signature_with_Hash
    {
    public:
-      DSA_Signature_Operation(const DSA_PrivateKey& dsa,
+      DSA_Signature_Operation(std::shared_ptr<const DL_PrivateKey> key,
                               const std::string& emsa,
                               RandomNumberGenerator& rng) :
          PK_Ops::Signature_with_Hash(emsa),
-         m_group(dsa.get_group()),
-         m_x(dsa.get_x())
+         m_key(key)
          {
-         m_b = BigInt::random_integer(rng, 2, dsa.group_q());
-         m_b_inv = m_group.inverse_mod_q(m_b);
+         m_b = BigInt::random_integer(rng, 2, key->group().get_q());
+         m_b_inv = m_key->group().inverse_mod_q(m_b);
          }
 
-      size_t signature_length() const override { return 2*m_group.q_bytes(); }
+      size_t signature_length() const override { return 2*m_key->group().q_bytes(); }
 
       secure_vector<uint8_t> raw_sign(const uint8_t msg[], size_t msg_len,
-                                   RandomNumberGenerator& rng) override;
+                                      RandomNumberGenerator& rng) override;
 
       AlgorithmIdentifier algorithm_identifier() const override;
    private:
-      const DL_Group m_group;
-      const BigInt& m_x;
+      std::shared_ptr<const DL_PrivateKey> m_key;
       BigInt m_b, m_b_inv;
    };
 
@@ -130,21 +161,23 @@ secure_vector<uint8_t>
 DSA_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len,
                                   RandomNumberGenerator& rng)
    {
-   const BigInt& q = m_group.get_q();
+   const DL_Group& group = m_key->group();
+   const BigInt& q = group.get_q();
 
-   BigInt m = BigInt::from_bytes_with_max_bits(msg, msg_len, m_group.q_bits());
+   BigInt m = BigInt::from_bytes_with_max_bits(msg, msg_len, group.q_bits());
 
    if(m >= q)
       m -= q;
 
 #if defined(BOTAN_HAS_RFC6979_GENERATOR)
    BOTAN_UNUSED(rng);
-   const BigInt k = generate_rfc6979_nonce(m_x, q, m, this->rfc6979_hash_function());
+   const BigInt k = generate_rfc6979_nonce(m_key->private_key(), q, m,
+                                           this->rfc6979_hash_function());
 #else
    const BigInt k = BigInt::random_integer(rng, 1, q);
 #endif
 
-   const BigInt k_inv = m_group.inverse_mod_q(k);
+   const BigInt k_inv = group.inverse_mod_q(k);
 
    /*
    * It may not be strictly necessary for the reduction (g^k mod p) mod q to be
@@ -155,18 +188,18 @@ DSA_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len,
    * However it only increases the cost of signatures by about 7-10%, and DSA is
    * only for legacy use anyway so we don't care about the performance so much.
    */
-   const BigInt r = ct_modulo(m_group.power_g_p(k, m_group.q_bits()), m_group.get_q());
+   const BigInt r = ct_modulo(group.power_g_p(k, group.q_bits()), group.get_q());
 
    /*
    * Blind the input message and compute x*r+m as (x*r*b + m*b)/b
    */
-   m_b = m_group.square_mod_q(m_b);
-   m_b_inv = m_group.square_mod_q(m_b_inv);
+   m_b = group.square_mod_q(m_b);
+   m_b_inv = group.square_mod_q(m_b_inv);
 
-   m = m_group.multiply_mod_q(m_b, m);
-   const BigInt xr = m_group.multiply_mod_q(m_b, m_x, r);
+   m = group.multiply_mod_q(m_b, m);
+   const BigInt xr = group.multiply_mod_q(m_b, m_key->private_key(), r);
 
-   const BigInt s = m_group.multiply_mod_q(m_b_inv, k_inv, m_group.mod_q(xr+m));
+   const BigInt s = group.multiply_mod_q(m_b_inv, k_inv, group.mod_q(xr+m));
 
    // With overwhelming probability, a bug rather than actual zero r/s
    if(r.is_zero() || s.is_zero())
@@ -181,33 +214,32 @@ DSA_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len,
 class DSA_Verification_Operation final : public PK_Ops::Verification_with_Hash
    {
    public:
-      DSA_Verification_Operation(const DSA_PublicKey& dsa,
+      DSA_Verification_Operation(std::shared_ptr<const DL_PublicKey> key,
                                  const std::string& emsa) :
          PK_Ops::Verification_with_Hash(emsa),
-         m_group(dsa.get_group()),
-         m_y(dsa.get_y())
+         m_key(key)
          {
          }
 
-      DSA_Verification_Operation(const DSA_PublicKey& dsa,
+      DSA_Verification_Operation(std::shared_ptr<const DL_PublicKey> key,
                                  const AlgorithmIdentifier& alg_id) :
          PK_Ops::Verification_with_Hash(alg_id, "DSA"),
-         m_group(dsa.get_group()),
-         m_y(dsa.get_y())
+         m_key(key)
          {
          }
 
       bool verify(const uint8_t msg[], size_t msg_len,
                   const uint8_t sig[], size_t sig_len) override;
    private:
-      const DL_Group m_group;
-      const BigInt& m_y;
+      std::shared_ptr<const DL_PublicKey> m_key;
    };
 
 bool DSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,
                                         const uint8_t sig[], size_t sig_len)
    {
-   const BigInt& q = m_group.get_q();
+   const auto group = m_key->group();
+
+   const BigInt& q = group.get_q();
    const size_t q_bytes = q.bytes();
 
    if(sig_len != 2*q_bytes)
@@ -215,7 +247,7 @@ bool DSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,
 
    BigInt r(sig, q_bytes);
    BigInt s(sig + q_bytes, q_bytes);
-   BigInt i = BigInt::from_bytes_with_max_bits(msg, msg_len, m_group.q_bits());
+   BigInt i = BigInt::from_bytes_with_max_bits(msg, msg_len, group.q_bits());
    if(i >= q)
       i -= q;
 
@@ -224,13 +256,13 @@ bool DSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,
 
    s = inverse_mod(s, q);
 
-   const BigInt sr = m_group.multiply_mod_q(s, r);
-   const BigInt si = m_group.multiply_mod_q(s, i);
+   const BigInt sr = group.multiply_mod_q(s, r);
+   const BigInt si = group.multiply_mod_q(s, i);
 
-   s = m_group.multi_exponentiate(si, m_y, sr);
+   s = group.multi_exponentiate(si, m_key->public_key(), sr);
 
    // s is too big for Barrett, and verification doesn't need to be const-time
-   return (s % m_group.get_q() == r);
+   return (s % group.get_q() == r);
    }
 
 }
@@ -240,7 +272,7 @@ DSA_PublicKey::create_verification_op(const std::string& params,
                                       const std::string& provider) const
    {
    if(provider == "base" || provider.empty())
-      return std::make_unique<DSA_Verification_Operation>(*this, params);
+      return std::make_unique<DSA_Verification_Operation>(this->m_public_key, params);
    throw Provider_Not_Found(algo_name(), provider);
    }
 
@@ -249,7 +281,7 @@ DSA_PublicKey::create_x509_verification_op(const AlgorithmIdentifier& signature_
                                            const std::string& provider) const
    {
    if(provider == "base" || provider.empty())
-      return std::make_unique<DSA_Verification_Operation>(*this, signature_algorithm);
+      return std::make_unique<DSA_Verification_Operation>(this->m_public_key, signature_algorithm);
 
    throw Provider_Not_Found(algo_name(), provider);
    }
@@ -260,7 +292,7 @@ DSA_PrivateKey::create_signature_op(RandomNumberGenerator& rng,
                                     const std::string& provider) const
    {
    if(provider == "base" || provider.empty())
-      return std::make_unique<DSA_Signature_Operation>(*this, params, rng);
+      return std::make_unique<DSA_Signature_Operation>(this->m_private_key, params, rng);
    throw Provider_Not_Found(algo_name(), provider);
    }
 
diff --git a/src/lib/pubkey/dsa/dsa.h b/src/lib/pubkey/dsa/dsa.h
index bde52e76d..9afacbd7e 100644
--- a/src/lib/pubkey/dsa/dsa.h
+++ b/src/lib/pubkey/dsa/dsa.h
@@ -1,6 +1,6 @@
 /*
 * DSA
-* (C) 1999-2010 Jack Lloyd
+* (C) 1999-2010,2023 Jack Lloyd
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -8,29 +8,29 @@
 #ifndef BOTAN_DSA_H_
 #define BOTAN_DSA_H_
 
-#include <botan/dl_algo.h>
+#include <botan/pk_keys.h>
+#include <memory>
 
 namespace Botan {
 
+class BigInt;
+class DL_Group;
+class DL_PublicKey;
+class DL_PrivateKey;
+
 /**
 * DSA Public Key
 */
-class BOTAN_PUBLIC_API(2,0) DSA_PublicKey : public virtual DL_Scheme_PublicKey
+class BOTAN_PUBLIC_API(2,0) DSA_PublicKey : public virtual Public_Key
    {
    public:
-      std::string algo_name() const override { return "DSA"; }
-
-      DL_Group_Format group_format() const override { return DL_Group_Format::ANSI_X9_57; }
-      size_t message_parts() const override { return 2; }
-      size_t message_part_size() const override { return group_q().bytes(); }
-
       bool supports_operation(PublicKeyOperation op) const override
          {
          return (op == PublicKeyOperation::Signature);
          }
 
       /**
-      * Load a public key.
+      * Load a public key from the ASN.1 encoding
       * @param alg_id the X.509 algorithm identifier
       * @param key_bits DER encoded public key bits
       */
@@ -38,12 +38,27 @@ class BOTAN_PUBLIC_API(2,0) DSA_PublicKey : public virtual DL_Scheme_PublicKey
                     const std::vector<uint8_t>& key_bits);
 
       /**
-      * Create a public key.
+      * Load a public key from the integer value
       * @param group the underlying DL group
       * @param y the public value y = g^x mod p
       */
       DSA_PublicKey(const DL_Group& group, const BigInt& y);
 
+      std::string algo_name() const override { return "DSA"; }
+
+      size_t message_parts() const override { return 2; }
+      size_t message_part_size() const override;
+
+      AlgorithmIdentifier algorithm_identifier() const override;
+      std::vector<uint8_t> public_key_bits() const override;
+
+      bool check_key(RandomNumberGenerator& rng, bool strong) const override;
+
+      size_t estimated_strength() const override;
+      size_t key_length() const override;
+
+      const BigInt& get_int_field(const std::string& field) const override;
+
       std::unique_ptr<PK_Ops::Verification>
          create_verification_op(const std::string& params,
                                 const std::string& provider) const override;
@@ -51,19 +66,27 @@ class BOTAN_PUBLIC_API(2,0) DSA_PublicKey : public virtual DL_Scheme_PublicKey
       std::unique_ptr<PK_Ops::Verification>
          create_x509_verification_op(const AlgorithmIdentifier& signature_algorithm,
                                      const std::string& provider) const override;
-   protected:
+   private:
+      friend class DSA_PrivateKey;
+
       DSA_PublicKey() = default;
+
+      DSA_PublicKey(std::shared_ptr<const DL_PublicKey> key) :
+         m_public_key(key) {}
+
+      std::shared_ptr<const DL_PublicKey> m_public_key;
    };
 
 /**
 * DSA Private Key
 */
-class BOTAN_PUBLIC_API(2,0) DSA_PrivateKey final : public DSA_PublicKey,
-                                 public virtual DL_Scheme_PrivateKey
+class BOTAN_PUBLIC_API(2,0) DSA_PrivateKey final :
+   public DSA_PublicKey,
+   public virtual Private_Key
    {
    public:
       /**
-      * Load a private key.
+      * Load a private key from the ASN.1 encoding
       * @param alg_id the X.509 algorithm identifier
       * @param key_bits DER encoded key bits in ANSI X9.57 format
       */
@@ -71,23 +94,35 @@ class BOTAN_PUBLIC_API(2,0) DSA_PrivateKey final : public DSA_PublicKey,
                      const secure_vector<uint8_t>& key_bits);
 
       /**
-      * Create a private key.
-      * @param rng the RNG to use
+      * Create a new private key.
       * @param group the underlying DL group
-      * @param private_key the private key (if zero, a new random key is generated)
+      * @param rng the RNG to use
       */
       DSA_PrivateKey(RandomNumberGenerator& rng,
-                     const DL_Group& group,
-                     const BigInt& private_key = BigInt::zero());
+                     const DL_Group& group);
+
+      /**
+      * Load a private key
+      * @param group the underlying DL group
+      * @param private_key the private key
+      */
+      DSA_PrivateKey(const DL_Group& group,
+                     const BigInt& private_key);
 
       std::unique_ptr<Public_Key> public_key() const override;
 
       bool check_key(RandomNumberGenerator& rng, bool strong) const override;
 
+      secure_vector<uint8_t> private_key_bits() const override;
+
+      const BigInt& get_int_field(const std::string& field) const override;
+
       std::unique_ptr<PK_Ops::Signature>
          create_signature_op(RandomNumberGenerator& rng,
                              const std::string& params,
                              const std::string& provider) const override;
+   private:
+      std::shared_ptr<const DL_PrivateKey> m_private_key;
    };
 
 }
diff --git a/src/lib/pubkey/elgamal/elgamal.cpp b/src/lib/pubkey/elgamal/elgamal.cpp
index 11a2c2aaa..73fff8be2 100644
--- a/src/lib/pubkey/elgamal/elgamal.cpp
+++ b/src/lib/pubkey/elgamal/elgamal.cpp
@@ -1,11 +1,12 @@
 /*
 * ElGamal
-* (C) 1999-2007,2018,2019 Jack Lloyd
+* (C) 1999-2007,2018,2019,2023 Jack Lloyd
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
 
 #include <botan/elgamal.h>
+#include <botan/internal/dl_scheme.h>
 #include <botan/internal/pk_ops_impl.h>
 #include <botan/internal/monty_exp.h>
 #include <botan/internal/keypair.h>
@@ -13,60 +14,91 @@
 
 namespace Botan {
 
-/*
-* ElGamal_PublicKey Constructor
-*/
-ElGamal_PublicKey::ElGamal_PublicKey(const DL_Group& group, const BigInt& y) :
-   DL_Scheme_PublicKey(group, y)
+ElGamal_PublicKey::ElGamal_PublicKey(const DL_Group& group, const BigInt& y)
    {
+   m_public_key = std::make_shared<DL_PublicKey>(group, y);
+   }
+
+ElGamal_PublicKey::ElGamal_PublicKey(const AlgorithmIdentifier& alg_id,
+                                     const std::vector<uint8_t>& key_bits)
+   {
+   m_public_key = std::make_shared<DL_PublicKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_42);
+   }
+
+size_t ElGamal_PublicKey::estimated_strength() const
+   {
+   return m_public_key->estimated_strength();
+   }
+
+size_t ElGamal_PublicKey::key_length() const
+   {
+   return m_public_key->p_bits();
+   }
+
+AlgorithmIdentifier ElGamal_PublicKey::algorithm_identifier() const
+   {
+   return AlgorithmIdentifier(
+      object_identifier(),
+      m_public_key->group().DER_encode(DL_Group_Format::ANSI_X9_42));
+   }
+
+std::vector<uint8_t> ElGamal_PublicKey::public_key_bits() const
+   {
+   return m_public_key->DER_encode();
+   }
+
+const BigInt& ElGamal_PublicKey::get_int_field(const std::string& field) const
+   {
+   return m_public_key->get_int_field(algo_name(), field);
+   }
+
+bool ElGamal_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const
+   {
+   return m_public_key->check_key(rng, strong);
    }
 
-/*
-* ElGamal_PrivateKey Constructor
-*/
 ElGamal_PrivateKey::ElGamal_PrivateKey(RandomNumberGenerator& rng,
-                                       const DL_Group& group,
-                                       const BigInt& x)
+                                       const DL_Group& group)
    {
-   m_x = x;
-   m_group = group;
+   m_private_key = std::make_shared<DL_PrivateKey>(group, rng);
+   m_public_key = m_private_key->public_key();
+   }
 
-   if(m_x.is_zero())
-      {
-      const size_t exp_bits = m_group.exponent_bits();
-      m_x.randomize(rng, exp_bits);
-      m_y = m_group.power_g_p(m_x, exp_bits);
-      }
-   else
-      {
-      m_y = m_group.power_g_p(m_x, m_group.p_bits());
-      }
+ElGamal_PrivateKey::ElGamal_PrivateKey(const DL_Group& group,
+                                       const BigInt& x)
+   {
+   m_private_key = std::make_shared<DL_PrivateKey>(group, x);
+   m_public_key = m_private_key->public_key();
    }
 
 ElGamal_PrivateKey::ElGamal_PrivateKey(const AlgorithmIdentifier& alg_id,
-                                       const secure_vector<uint8_t>& key_bits) :
-   DL_Scheme_PrivateKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_42)
+                                       const secure_vector<uint8_t>& key_bits)
    {
-   m_y = m_group.power_g_p(m_x, m_group.p_bits());
+   m_private_key = std::make_shared<DL_PrivateKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_42);
+   m_public_key = m_private_key->public_key();
    }
 
 std::unique_ptr<Public_Key> ElGamal_PrivateKey::public_key() const
    {
-   return std::make_unique<ElGamal_PublicKey>(get_group(), get_y());
+   return std::unique_ptr<Public_Key>(new ElGamal_PublicKey(m_public_key));
+   }
+
+const BigInt& ElGamal_PrivateKey::get_int_field(const std::string& field) const
+   {
+   return m_private_key->get_int_field(algo_name(), field);
+   }
+
+secure_vector<uint8_t> ElGamal_PrivateKey::private_key_bits() const
+   {
+   return m_private_key->DER_encode();
    }
 
-/*
-* Check Private ElGamal Parameters
-*/
 bool ElGamal_PrivateKey::check_key(RandomNumberGenerator& rng,
                                    bool strong) const
    {
-   if(!DL_Scheme_PrivateKey::check_key(rng, strong))
+   if(!m_private_key->check_key(rng, strong))
       return false;
 
-   if(!strong)
-      return true;
-
    return KeyPair::encryption_consistency_check(rng, *this, "OAEP(SHA-256)");
    }
 
@@ -79,38 +111,44 @@ class ElGamal_Encryption_Operation final : public PK_Ops::Encryption_with_EME
    {
    public:
 
-      size_t ciphertext_length(size_t /*ptext_len*/) const override { return 2*m_group.p_bytes(); }
+      ElGamal_Encryption_Operation(const std::shared_ptr<const DL_PublicKey> key,
+                                   const std::string& eme) :
+         PK_Ops::Encryption_with_EME(eme),
+         m_key(key)
+         {
+         const size_t powm_window = 4;
+         m_monty_y_p = monty_precompute(m_key->group().monty_params_p(),
+                                        m_key->public_key(),
+                                        powm_window);
+         }
 
-      size_t max_ptext_input_bits() const override { return m_group.p_bits() - 1; }
+      size_t ciphertext_length(size_t /*ptext_len*/) const override
+         {
+         return 2*m_key->group().p_bytes();
+         }
 
-      ElGamal_Encryption_Operation(const ElGamal_PublicKey& key, const std::string& eme);
+      size_t max_ptext_input_bits() const override
+         {
+         return m_key->group().p_bits() - 1;
+         }
 
       secure_vector<uint8_t> raw_encrypt(const uint8_t msg[], size_t msg_len,
                                       RandomNumberGenerator& rng) override;
 
    private:
-      const DL_Group m_group;
+      std::shared_ptr<const DL_PublicKey> m_key;
       std::shared_ptr<const Montgomery_Exponentation_State> m_monty_y_p;
    };
 
-ElGamal_Encryption_Operation::ElGamal_Encryption_Operation(const ElGamal_PublicKey& key,
-                                                           const std::string& eme) :
-   PK_Ops::Encryption_with_EME(eme),
-   m_group(key.get_group())
-   {
-   const size_t powm_window = 4;
-   m_monty_y_p = monty_precompute(key.get_group().monty_params_p(),
-                                  key.get_y(),
-                                  powm_window);
-   }
-
 secure_vector<uint8_t>
 ElGamal_Encryption_Operation::raw_encrypt(const uint8_t msg[], size_t msg_len,
                                           RandomNumberGenerator& rng)
    {
    BigInt m(msg, msg_len);
 
-   if(m >= m_group.get_p())
+   const auto& group = m_key->group();
+
+   if(m >= group.get_p())
       throw Invalid_Argument("ElGamal encryption: Input is too large");
 
    /*
@@ -121,13 +159,13 @@ ElGamal_Encryption_Operation::raw_encrypt(const uint8_t msg[], size_t msg_len,
 
    See https://eprint.iacr.org/2021/923
    */
-   const size_t k_bits = m_group.p_bits() - 1;
+   const size_t k_bits = group.p_bits() - 1;
    const BigInt k(rng, k_bits, false);
 
-   const BigInt a = m_group.power_g_p(k, k_bits);
-   const BigInt b = m_group.multiply_mod_p(m, monty_execute(*m_monty_y_p, k, k_bits));
+   const BigInt a = group.power_g_p(k, k_bits);
+   const BigInt b = group.multiply_mod_p(m, monty_execute(*m_monty_y_p, k, k_bits));
 
-   return BigInt::encode_fixed_length_int_pair(a, b, m_group.p_bytes());
+   return BigInt::encode_fixed_length_int_pair(a, b, group.p_bytes());
    }
 
 /**
@@ -137,47 +175,39 @@ class ElGamal_Decryption_Operation final : public PK_Ops::Decryption_with_EME
    {
    public:
 
-      ElGamal_Decryption_Operation(const ElGamal_PrivateKey& key,
+      ElGamal_Decryption_Operation(const std::shared_ptr<const DL_PrivateKey> key,
                                    const std::string& eme,
-                                   RandomNumberGenerator& rng);
-
-      size_t plaintext_length(size_t /*ctext_len*/) const override { return m_group.p_bytes(); }
+                                   RandomNumberGenerator& rng) :
+         PK_Ops::Decryption_with_EME(eme),
+         m_key(key),
+         m_blinder(m_key->group().get_p(),
+                   rng,
+                   [](const BigInt& k) { return k; },
+                   [this](const BigInt& k) { return powermod_x_p(k); })
+         {}
+
+      size_t plaintext_length(size_t /*ctext_len*/) const override
+         {
+         return m_key->group().p_bytes();
+         }
 
       secure_vector<uint8_t> raw_decrypt(const uint8_t msg[], size_t msg_len) override;
    private:
       BigInt powermod_x_p(const BigInt& v) const
          {
-         const size_t powm_window = 4;
-         auto powm_v_p = monty_precompute(m_monty_p, v, powm_window);
-         return monty_execute(*powm_v_p, m_x, m_x_bits);
+         return m_key->group().power_b_p(v, m_key->private_key());
          }
 
-      const DL_Group m_group;
-      const BigInt& m_x;
-      const size_t m_x_bits;
-      std::shared_ptr<const Montgomery_Params> m_monty_p;
+      std::shared_ptr<const DL_PrivateKey> m_key;
       Blinder m_blinder;
    };
 
-ElGamal_Decryption_Operation::ElGamal_Decryption_Operation(const ElGamal_PrivateKey& key,
-                                                           const std::string& eme,
-                                                           RandomNumberGenerator& rng) :
-   PK_Ops::Decryption_with_EME(eme),
-   m_group(key.get_group()),
-   m_x(key.get_x()),
-   m_x_bits(m_x.bits()),
-   m_monty_p(key.get_group().monty_params_p()),
-   m_blinder(m_group.get_p(),
-             rng,
-             [](const BigInt& k) { return k; },
-             [this](const BigInt& k) { return powermod_x_p(k); })
-   {
-   }
-
 secure_vector<uint8_t>
 ElGamal_Decryption_Operation::raw_decrypt(const uint8_t msg[], size_t msg_len)
    {
-   const size_t p_bytes = m_group.p_bytes();
+   const auto& group = m_key->group();
+
+   const size_t p_bytes = group.p_bytes();
 
    if(msg_len != 2 * p_bytes)
       throw Invalid_Argument("ElGamal decryption: Invalid message");
@@ -185,12 +215,12 @@ ElGamal_Decryption_Operation::raw_decrypt(const uint8_t msg[], size_t msg_len)
    BigInt a(msg, p_bytes);
    const BigInt b(msg + p_bytes, p_bytes);
 
-   if(a >= m_group.get_p() || b >= m_group.get_p())
+   if(a >= group.get_p() || b >= group.get_p())
       throw Invalid_Argument("ElGamal decryption: Invalid message");
 
    a = m_blinder.blind(a);
 
-   const BigInt r = m_group.multiply_mod_p(m_group.inverse_mod_p(powermod_x_p(a)), b);
+   const BigInt r = group.multiply_mod_p(group.inverse_mod_p(powermod_x_p(a)), b);
 
    return BigInt::encode_1363(m_blinder.unblind(r), p_bytes);
    }
@@ -203,7 +233,7 @@ ElGamal_PublicKey::create_encryption_op(RandomNumberGenerator& /*rng*/,
                                         const std::string& provider) const
    {
    if(provider == "base" || provider.empty())
-      return std::make_unique<ElGamal_Encryption_Operation>(*this, params);
+      return std::make_unique<ElGamal_Encryption_Operation>(this->m_public_key, params);
    throw Provider_Not_Found(algo_name(), provider);
    }
 
@@ -213,7 +243,7 @@ ElGamal_PrivateKey::create_decryption_op(RandomNumberGenerator& rng,
                                          const std::string& provider) const
    {
    if(provider == "base" || provider.empty())
-      return std::make_unique<ElGamal_Decryption_Operation>(*this, params, rng);
+      return std::make_unique<ElGamal_Decryption_Operation>(this->m_private_key, params, rng);
    throw Provider_Not_Found(algo_name(), provider);
    }
 
diff --git a/src/lib/pubkey/elgamal/elgamal.h b/src/lib/pubkey/elgamal/elgamal.h
index 430ff3591..2f4faba3d 100644
--- a/src/lib/pubkey/elgamal/elgamal.h
+++ b/src/lib/pubkey/elgamal/elgamal.h
@@ -1,6 +1,6 @@
 /*
 * ElGamal
-* (C) 1999-2007 Jack Lloyd
+* (C) 1999-2007,2023 Jack Lloyd
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -8,33 +8,34 @@
 #ifndef BOTAN_ELGAMAL_H_
 #define BOTAN_ELGAMAL_H_
 
-#include <botan/dl_algo.h>
+#include <botan/pk_keys.h>
+#include <memory>
 
 namespace Botan {
 
+class BigInt;
+class DL_Group;
+class DL_PublicKey;
+class DL_PrivateKey;
+
 /**
 * ElGamal Public Key
 */
-class BOTAN_PUBLIC_API(2,0) ElGamal_PublicKey : public virtual DL_Scheme_PublicKey
+class BOTAN_PUBLIC_API(2,0) ElGamal_PublicKey : public virtual Public_Key
    {
    public:
-      std::string algo_name() const override { return "ElGamal"; }
-      DL_Group_Format group_format() const override { return DL_Group_Format::ANSI_X9_42; }
-
       bool supports_operation(PublicKeyOperation op) const override
          {
          return (op == PublicKeyOperation::Encryption);
          }
 
       /**
-      * Load a public key.
+      * Load a public key from the ASN.1 encoding
       * @param alg_id the X.509 algorithm identifier
       * @param key_bits DER encoded public key bits
       */
       ElGamal_PublicKey(const AlgorithmIdentifier& alg_id,
-                        const std::vector<uint8_t>& key_bits) :
-         DL_Scheme_PublicKey(alg_id, key_bits, DL_Group_Format::ANSI_X9_42)
-         {}
+                        const std::vector<uint8_t>& key_bits);
 
       /**
       * Create a public key.
@@ -43,26 +44,44 @@ class BOTAN_PUBLIC_API(2,0) ElGamal_PublicKey : public virtual DL_Scheme_PublicK
       */
       ElGamal_PublicKey(const DL_Group& group, const BigInt& y);
 
+      AlgorithmIdentifier algorithm_identifier() const override;
+      std::vector<uint8_t> public_key_bits() const override;
+
+      bool check_key(RandomNumberGenerator& rng, bool strong) const override;
+
+      size_t estimated_strength() const override;
+      size_t key_length() const override;
+
+      std::string algo_name() const override { return "ElGamal"; }
+
+      const BigInt& get_int_field(const std::string& field) const override;
+
       std::unique_ptr<PK_Ops::Encryption>
          create_encryption_op(RandomNumberGenerator& rng,
                               const std::string& params,
                               const std::string& provider) const override;
 
-   protected:
+   private:
+      friend class ElGamal_PrivateKey;
+
       ElGamal_PublicKey() = default;
+
+      ElGamal_PublicKey(std::shared_ptr<const DL_PublicKey> key) :
+         m_public_key(key) {}
+
+      std::shared_ptr<const DL_PublicKey> m_public_key;
    };
 
 /**
 * ElGamal Private Key
 */
-class BOTAN_PUBLIC_API(2,0) ElGamal_PrivateKey final : public ElGamal_PublicKey,
-                                     public virtual DL_Scheme_PrivateKey
+class BOTAN_PUBLIC_API(2,0) ElGamal_PrivateKey final :
+   public ElGamal_PublicKey,
+   public virtual Private_Key
    {
    public:
-      bool check_key(RandomNumberGenerator& rng, bool) const override;
-
       /**
-      * Load a private key.
+      * Load a private key from the ASN.1 encoding
       * @param alg_id the X.509 algorithm identifier
       * @param key_bits DER encoded key bits in ANSI X9.42 format
       */
@@ -70,21 +89,35 @@ class BOTAN_PUBLIC_API(2,0) ElGamal_PrivateKey final : public ElGamal_PublicKey,
                          const secure_vector<uint8_t>& key_bits);
 
       /**
-      * Create a private key.
+      * Create a new random private key.
       * @param rng random number generator to use
       * @param group the group to be used in the key
-      * @param priv_key the key's secret value (or if zero, generate a new key)
       */
       ElGamal_PrivateKey(RandomNumberGenerator& rng,
-                         const DL_Group& group,
-                         const BigInt& priv_key = BigInt::zero());
+                         const DL_Group& group);
+
+      /**
+      * Load a private key from the integer encoding
+      * @param group the group to be used in the key
+      * @param private_key the key's secret value
+      */
+      ElGamal_PrivateKey(const DL_Group& group,
+                         const BigInt& private_key);
+
+      bool check_key(RandomNumberGenerator& rng, bool) const override;
 
       std::unique_ptr<Public_Key> public_key() const override;
 
+      secure_vector<uint8_t> private_key_bits() const override;
+
+      const BigInt& get_int_field(const std::string& field) const override;
+
       std::unique_ptr<PK_Ops::Decryption>
          create_decryption_op(RandomNumberGenerator& rng,
                               const std::string& params,
                               const std::string& provider) const override;
+   private:
+      std::shared_ptr<const DL_PrivateKey> m_private_key;
    };
 
 }
diff --git a/src/lib/pubkey/pk_algs.cpp b/src/lib/pubkey/pk_algs.cpp
index ad08b06b2..53fc58da0 100644
--- a/src/lib/pubkey/pk_algs.cpp
+++ b/src/lib/pubkey/pk_algs.cpp
@@ -291,6 +291,9 @@ create_ec_private_key(const std::string& alg_name,
                       const EC_Group& ec_group,
                       RandomNumberGenerator& rng)
    {
+   // Potentially unused if all EC algorthms are disabled
+   BOTAN_UNUSED(alg_name, ec_group, rng);
+
 #if defined(BOTAN_HAS_ECDSA)
    if(alg_name == "ECDSA")
       return std::make_unique<ECDSA_PrivateKey>(rng, ec_group);
diff --git a/src/lib/tls/tls12/msg_server_kex.cpp b/src/lib/tls/tls12/msg_server_kex.cpp
index dc605969f..551460c47 100644
--- a/src/lib/tls/tls12/msg_server_kex.cpp
+++ b/src/lib/tls/tls12/msg_server_kex.cpp
@@ -14,7 +14,9 @@
 #include <botan/credentials_manager.h>
 #include <botan/internal/loadstor.h>
 #include <botan/pubkey.h>
+#include <botan/pubkey.h>
 
+#include <botan/dl_group.h>
 #include <botan/dh.h>
 #include <botan/ecdh.h>
 
@@ -74,8 +76,8 @@ Server_Key_Exchange::Server_Key_Exchange(Handshake_IO& io,
       const std::string group_name = state.callbacks().tls_decode_group_param(shared_group);
       auto dh = std::make_unique<DH_PrivateKey>(rng, DL_Group(group_name));
 
-      append_tls_length_value(m_params, BigInt::encode(dh->get_domain().get_p()), 2);
-      append_tls_length_value(m_params, BigInt::encode(dh->get_domain().get_g()), 2);
+      append_tls_length_value(m_params, BigInt::encode(dh->get_int_field("p")), 2);
+      append_tls_length_value(m_params, BigInt::encode(dh->get_int_field("g")), 2);
       append_tls_length_value(m_params, dh->public_value(), 2);
       m_kex_key.reset(dh.release());
       }
diff --git a/src/lib/tls/tls13/tls_extensions_key_share.cpp b/src/lib/tls/tls13/tls_extensions_key_share.cpp
index a4298b577..8b7b7008e 100644
--- a/src/lib/tls/tls13/tls_extensions_key_share.cpp
+++ b/src/lib/tls/tls13/tls_extensions_key_share.cpp
@@ -25,6 +25,7 @@
 
 #include <botan/dh.h>
 #include <botan/ecdh.h>
+#include <botan/dl_group.h>
 
 namespace Botan::TLS {
 
diff --git a/src/lib/tls/tls_callbacks.cpp b/src/lib/tls/tls_callbacks.cpp
index b5bb8b1a1..da4dc5994 100644
--- a/src/lib/tls/tls_callbacks.cpp
+++ b/src/lib/tls/tls_callbacks.cpp
@@ -13,6 +13,7 @@
 #include <botan/x509path.h>
 #include <botan/ocsp.h>
 #include <botan/dh.h>
+#include <botan/dl_group.h>
 #include <botan/ecdh.h>
 #include <botan/tls_exceptn.h>
 #include <botan/internal/ct_utils.h>


Test patch:
diff --git a/src/tests/test_dh.cpp b/src/tests/test_dh.cpp
index 4e47157e4..8fc7dd82a 100644
--- a/src/tests/test_dh.cpp
+++ b/src/tests/test_dh.cpp
@@ -10,6 +10,7 @@
    #include "test_pubkey.h"
    #include <botan/pubkey.h>
    #include <botan/dh.h>
+   #include <botan/dl_group.h>
 #endif
 
 namespace Botan_Tests {
@@ -40,17 +41,17 @@ class Diffie_Hellman_KAT_Tests final : public PK_Key_Agreement_Test
          const Botan::BigInt g = vars.get_req_bn("G");
          const Botan::BigInt x = vars.get_req_bn("X");
 
-         Botan::DL_Group grp;
+         Botan::DL_Group group;
          if(q == 0)
             {
-            grp = Botan::DL_Group(p, g);
+            group = Botan::DL_Group(p, g);
             }
          else
             {
-            grp = Botan::DL_Group(p, q, g);
+            group = Botan::DL_Group(p, q, g);
             }
 
-         return std::make_unique<Botan::DH_PrivateKey>(Test::rng(), grp, x);
+         return std::make_unique<Botan::DH_PrivateKey>(group, x);
          }
 
       std::vector<uint8_t> load_their_key(const std::string& /*header*/, const VarMap& vars) override
@@ -60,17 +61,17 @@ class Diffie_Hellman_KAT_Tests final : public PK_Key_Agreement_Test
          const Botan::BigInt g = vars.get_req_bn("G");
          const Botan::BigInt y = vars.get_req_bn("Y");
 
-         Botan::DL_Group grp;
+         Botan::DL_Group group;
          if(q == 0)
             {
-            grp = Botan::DL_Group(p, g);
+            group = Botan::DL_Group(p, g);
             }
          else
             {
-            grp = Botan::DL_Group(p, q, g);
+            group = Botan::DL_Group(p, q, g);
             }
 
-         Botan::DH_PublicKey key(grp, y);
+         Botan::DH_PublicKey key(group, y);
          return key.public_value();
          }
 
@@ -80,10 +81,10 @@ class Diffie_Hellman_KAT_Tests final : public PK_Key_Agreement_Test
 
          const BigInt g("2");
          const BigInt p("58458002095536094658683755258523362961421200751439456159756164191494576279467");
-         const Botan::DL_Group grp(p, g);
+         const Botan::DL_Group group(p, g);
 
          const Botan::BigInt x("46205663093589612668746163860870963912226379131190812163519349848291472898748");
-         auto privkey = std::make_unique<Botan::DH_PrivateKey>(Test::rng(), grp, x);
+         auto privkey = std::make_unique<Botan::DH_PrivateKey>(group, x);
 
          auto kas = std::make_unique<Botan::PK_Key_Agreement>(*privkey, rng(), "Raw");
 
@@ -128,8 +129,9 @@ class DH_Invalid_Key_Tests final : public Text_Based_Test
          const Botan::BigInt g = vars.get_req_bn("G");
          const Botan::BigInt pubkey = vars.get_req_bn("InvalidKey");
 
-         Botan::DL_Group grp(p, q, g);
-         auto key = std::make_unique<Botan::DH_PublicKey>(grp, pubkey);
+         Botan::DL_Group group(p, q, g);
+
+         auto key = std::make_unique<Botan::DH_PublicKey>(group, pubkey);
          result.test_eq("public key fails check", key->check_key(Test::rng(), false), false);
          return result;
          }
diff --git a/src/tests/test_dlies.cpp b/src/tests/test_dlies.cpp
index 641eb9fa2..cf1dbc31c 100644
--- a/src/tests/test_dlies.cpp
+++ b/src/tests/test_dlies.cpp
@@ -11,6 +11,7 @@
    #include "test_pubkey.h"
    #include <botan/dlies.h>
    #include <botan/dh.h>
+   #include <botan/dl_group.h>
 #endif
 
 namespace Botan_Tests {
@@ -78,8 +79,8 @@ class DLIES_KAT_Tests final : public Text_Based_Test
 
          Botan::DL_Group domain(group_name);
 
-         Botan::DH_PrivateKey from(Test::rng(), domain, x1);
-         Botan::DH_PrivateKey to(Test::rng(), domain, x2);
+         Botan::DH_PrivateKey from(domain, x1);
+         Botan::DH_PrivateKey to(domain, x2);
 
          Botan::DLIES_Encryptor encryptor(from, Test::rng(),
                                           kdf->new_object(),
diff --git a/src/tests/test_dsa.cpp b/src/tests/test_dsa.cpp
index 4054e64c0..e97a8f23f 100644
--- a/src/tests/test_dsa.cpp
+++ b/src/tests/test_dsa.cpp
@@ -8,6 +8,8 @@
 
 #if defined(BOTAN_HAS_DSA)
    #include <botan/dsa.h>
+   #include <botan/bigint.h>
+   #include <botan/dl_group.h>
    #include "test_pubkey.h"
 #endif
 
@@ -43,9 +45,9 @@ class DSA_KAT_Tests final : public PK_Signature_Generation_Test
          const Botan::BigInt g = vars.get_req_bn("G");
          const Botan::BigInt x = vars.get_req_bn("X");
 
-         const Botan::DL_Group grp(p, q, g);
+         const Botan::DL_Group group(p, q, g);
 
-         return std::make_unique<Botan::DSA_PrivateKey>(Test::rng(), grp, x);
+         return std::make_unique<Botan::DSA_PrivateKey>(group, x);
          }
 
       std::string default_padding(const VarMap& vars) const override
@@ -82,7 +84,7 @@ class DSA_KAT_Verification_Tests final : public PK_Signature_Verification_Test
 
          const Botan::DL_Group grp(p, q, g);
 
-         const Botan::DSA_PrivateKey priv_key(Test::rng(), grp, x);
+         const Botan::DSA_PrivateKey priv_key(grp, x);
 
          return priv_key.public_key();
          }
@@ -113,9 +115,9 @@ class DSA_Verification_Tests final : public PK_Signature_Verification_Test
          const Botan::BigInt g = vars.get_req_bn("G");
          const Botan::BigInt y = vars.get_req_bn("Y");
 
-         const Botan::DL_Group grp(p, q, g);
+         const Botan::DL_Group group(p, q, g);
 
-         return std::make_unique<Botan::DSA_PublicKey>(grp, y);
+         return std::make_unique<Botan::DSA_PublicKey>(group, y);
          }
 
       std::string default_padding(const VarMap& /*unused*/) const override
diff --git a/src/tests/test_elgamal.cpp b/src/tests/test_elgamal.cpp
index 283c742fc..6cf72507e 100644
--- a/src/tests/test_elgamal.cpp
+++ b/src/tests/test_elgamal.cpp
@@ -8,6 +8,7 @@
 
 #if defined(BOTAN_HAS_ELGAMAL)
    #include <botan/elgamal.h>
+   #include <botan/dl_group.h>
    #include "test_pubkey.h"
 #endif
 
@@ -29,9 +30,9 @@ class ElGamal_Encrypt_Tests final : public PK_Encryption_Decryption_Test
       std::unique_ptr<Botan::Private_Key> load_private_key(const VarMap& vars) override
          {
          const Botan::BigInt x = vars.get_req_bn("Secret");
-         const Botan::DL_Group grp(vars.get_req_str("Group"));
+         const Botan::DL_Group group(vars.get_req_str("Group"));
 
-         return std::make_unique<Botan::ElGamal_PrivateKey>(Test::rng(), grp, x);
+         return std::make_unique<Botan::ElGamal_PrivateKey>(group, x);
          }
    };
 
@@ -50,9 +51,9 @@ class ElGamal_Decrypt_Tests final : public PK_Decryption_Test
          const Botan::BigInt g = vars.get_req_bn("G");
          const Botan::BigInt x = vars.get_req_bn("X");
 
-         const Botan::DL_Group grp(p, g);
+         const Botan::DL_Group group(p, g);
 
-         return std::make_unique<Botan::ElGamal_PrivateKey>(Test::rng(), grp, x);
+         return std::make_unique<Botan::ElGamal_PrivateKey>(group, x);
          }
    };
 
diff --git a/src/tests/unit_tls_policy.cpp b/src/tests/unit_tls_policy.cpp
index 6276b042f..61d345564 100644
--- a/src/tests/unit_tls_policy.cpp
+++ b/src/tests/unit_tls_policy.cpp
@@ -26,6 +26,7 @@
 #endif
 
 #if defined(BOTAN_HAS_DIFFIE_HELLMAN)
+   #include <botan/dl_group.h>
    #include <botan/dh.h>
 #endif
 
@@ -134,7 +135,7 @@ class TLS_Policy_Unit_Tests final : public Test
          const BigInt p("58458002095536094658683755258523362961421200751439456159756164191494576279467");
          const Botan::DL_Group grp(p, g);
          const Botan::BigInt x("46205663093589612668746163860870963912226379131190812163519349848291472898748");
-         auto dhkey = std::make_unique<Botan::DH_PrivateKey>(Test::rng(), grp, x);
+         auto dhkey = std::make_unique<Botan::DH_PrivateKey>(grp, x);
 
          Botan::TLS::Policy policy;
          try