Repository: randombit/botan
Pull request #3201 (2023-01-20 09:16:35)

Feature: Generalization of RandomNumberGenerator::random_vec()
This is a spin-off from #3195 to harvest the low-hanging fruit. Namely, allowing to use `RNG::random_vec()` with arbitrary contiguous byte buffers.

Example usage (e.g. as desired in #3150):

```C++
// generate a random TLS Session ID
using Session_ID = Botan::Strong<std::vector<uint8_t>, struct Session_ID_>;
const auto random_id = rng.random_vec<Session_ID>(32);

// filling an arbitrary buffer with random bytes
std::array<uint8_t, 32> buffer;
rng.random_vec(buffer);
```


Patch:
diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp
index 14f2db70e..b8e3662f2 100644
--- a/src/lib/ffi/ffi_pkey_algs.cpp
+++ b/src/lib/ffi/ffi_pkey_algs.cpp
@@ -103,49 +103,116 @@ int pubkey_load_ec(std::unique_ptr<ECPublicKey_t>& key,
 Botan::BigInt pubkey_get_field(const Botan::Public_Key& key,
                                const std::string& field)
    {
+   // Maybe this should be `return key.get_integer_field(field_name)`?
+
+#if defined(BOTAN_HAS_RSA)
+   if(const Botan::RSA_PublicKey* rsa = dynamic_cast<const Botan::RSA_PublicKey*>(&key))
+      {
+      if(field == "n")
+         return rsa->get_n();
+      else if(field == "e")
+         return rsa->get_e();
+      else
+         throw Botan_FFI::FFI_Error("Bad field", BOTAN_FFI_ERROR_BAD_PARAMETER);
+      }
+#endif
+
+#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY)
+   // Handles DSA, ElGamal, etc
+   if(const Botan::DL_Scheme_PublicKey* dl = dynamic_cast<const Botan::DL_Scheme_PublicKey*>(&key))
+      {
+      if(field == "p")
+         return dl->group_p();
+      else if(field == "q")
+         return dl->group_q();
+      else if(field == "g")
+         return dl->group_g();
+      else if(field == "y")
+         return dl->get_y();
+      else
+         throw Botan_FFI::FFI_Error("Bad field", BOTAN_FFI_ERROR_BAD_PARAMETER);
+      }
+#endif
+
 #if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
-   // Not currently handled by get_int_field
    if(const Botan::EC_PublicKey* ecc = dynamic_cast<const Botan::EC_PublicKey*>(&key))
       {
       if(field == "public_x")
          return ecc->public_point().get_affine_x();
       else if(field == "public_y")
          return ecc->public_point().get_affine_y();
+      else if(field == "base_x")
+         return ecc->domain().get_g_x();
+      else if(field == "base_y")
+         return ecc->domain().get_g_y();
+      else if(field == "p")
+         return ecc->domain().get_p();
+      else if(field == "a")
+         return ecc->domain().get_a();
+      else if(field == "b")
+         return ecc->domain().get_b();
+      else if(field == "cofactor")
+         return ecc->domain().get_cofactor();
+      else if(field == "order")
+         return ecc->domain().get_order();
+      else
+         throw Botan_FFI::FFI_Error("Bad field", BOTAN_FFI_ERROR_BAD_PARAMETER);
       }
 #endif
 
-   try
-      {
-      return key.get_int_field(field);
-      }
-   catch(Botan::Unknown_PK_Field_Name&)
-      {
-      throw Botan_FFI::FFI_Error("Unknown key field", BOTAN_FFI_ERROR_BAD_PARAMETER);
-      }
+   // Some other algorithm type not supported by this function
+   throw Botan_FFI::FFI_Error("Field getter not implemented for this algorithm type",
+                              BOTAN_FFI_ERROR_NOT_IMPLEMENTED);
    }
 
 Botan::BigInt privkey_get_field(const Botan::Private_Key& key,
                                 const std::string& field)
    {
-#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
-   // Not currently handled by get_int_field
-   if(const Botan::EC_PublicKey* ecc = dynamic_cast<const Botan::EC_PublicKey*>(&key))
+   //return key.get_integer_field(field);
+
+#if defined(BOTAN_HAS_RSA)
+
+   if(const Botan::RSA_PrivateKey* rsa = dynamic_cast<const Botan::RSA_PrivateKey*>(&key))
       {
-      if(field == "public_x")
-         return ecc->public_point().get_affine_x();
-      else if(field == "public_y")
-         return ecc->public_point().get_affine_y();
+      if(field == "p")
+         return rsa->get_p();
+      else if(field == "q")
+         return rsa->get_q();
+      else if(field == "d")
+         return rsa->get_d();
+      else if(field == "c")
+         return rsa->get_c();
+      else if(field == "d1")
+         return rsa->get_d1();
+      else if(field == "d2")
+         return rsa->get_d2();
+      else
+         return pubkey_get_field(key, field);
       }
 #endif
 
-   try
+#if defined(BOTAN_HAS_DL_PUBLIC_KEY_FAMILY)
+   // Handles DSA, ElGamal, etc
+   if(const Botan::DL_Scheme_PrivateKey* dl = dynamic_cast<const Botan::DL_Scheme_PrivateKey*>(&key))
       {
-      return key.get_int_field(field);
+      if(field == "x")
+         return dl->get_x();
+      else
+         return pubkey_get_field(key, field);
       }
-   catch(Botan::Unknown_PK_Field_Name&)
+#endif
+
+#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
+   if(const Botan::EC_PrivateKey* ecc = dynamic_cast<const Botan::EC_PrivateKey*>(&key))
       {
-      throw Botan_FFI::FFI_Error("Unknown key field", BOTAN_FFI_ERROR_BAD_PARAMETER);
+      if(field == "x")
+         return ecc->private_value();
+      else
+         return pubkey_get_field(key, field);
       }
+#endif
+
+   return pubkey_get_field(key, field);
    }
 
 }
diff --git a/src/lib/pubkey/dl_algo/dl_algo.cpp b/src/lib/pubkey/dl_algo/dl_algo.cpp
index 66f26fb3f..66efe7456 100644
--- a/src/lib/pubkey/dl_algo/dl_algo.cpp
+++ b/src/lib/pubkey/dl_algo/dl_algo.cpp
@@ -69,10 +69,7 @@ DL_Scheme_PrivateKey::DL_Scheme_PrivateKey(const AlgorithmIdentifier& alg_id,
 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);
+   return m_group.verify_group(rng, strong) && m_group.verify_public_element(m_y);
    }
 
 /*
@@ -84,26 +81,4 @@ bool DL_Scheme_PrivateKey::check_key(RandomNumberGenerator& rng,
    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
index 9dd76a70a..d3edd6bb2 100644
--- a/src/lib/pubkey/dl_algo/dl_algo.h
+++ b/src/lib/pubkey/dl_algo/dl_algo.h
@@ -69,8 +69,6 @@ class BOTAN_PUBLIC_API(2,0) DL_Scheme_PublicKey : public virtual Public_Key
       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:
@@ -118,8 +116,6 @@ class BOTAN_PUBLIC_API(2,0) DL_Scheme_PrivateKey : public virtual DL_Scheme_Publ
 
       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.
diff --git a/src/lib/pubkey/ec_group/ec_point.h b/src/lib/pubkey/ec_group/ec_point.h
index 04d212b25..8c9293227 100644
--- a/src/lib/pubkey/ec_group/ec_point.h
+++ b/src/lib/pubkey/ec_group/ec_point.h
@@ -128,25 +128,8 @@ class BOTAN_PUBLIC_API(2,0) EC_Point final
       */
       BigInt get_affine_y() const;
 
-      /**
-      * Return the internal x coordinate
-      *
-      * Note this may be in Montgomery form
-      */
       const BigInt& get_x() const { return m_coord_x; }
-
-      /**
-      * Return the internal y coordinate
-      *
-      * Note this may be in Montgomery form
-      */
       const BigInt& get_y() const { return m_coord_y; }
-
-      /**
-      * Return the internal z coordinate
-      *
-      * Note this may be in Montgomery form
-      */
       const BigInt& get_z() const { return m_coord_z; }
 
       void swap_coords(BigInt& new_x, BigInt& new_y, BigInt& new_z)
diff --git a/src/lib/pubkey/ecc_key/ecc_key.cpp b/src/lib/pubkey/ecc_key/ecc_key.cpp
index fcd67ef22..67d526c25 100644
--- a/src/lib/pubkey/ecc_key/ecc_key.cpp
+++ b/src/lib/pubkey/ecc_key/ecc_key.cpp
@@ -193,42 +193,4 @@ EC_PrivateKey::EC_PrivateKey(const AlgorithmIdentifier& alg_id,
       }
    }
 
-const BigInt& EC_PublicKey::get_int_field(const std::string& field) const
-   {
-   if(field == "public_x")
-      {
-      BOTAN_ASSERT_NOMSG(this->public_point().is_affine());
-      return this->public_point().get_x();
-      }
-   else if(field == "public_y")
-      {
-      BOTAN_ASSERT_NOMSG(this->public_point().is_affine());
-      return this->public_point().get_y();
-      }
-   else if(field == "base_x")
-      return this->domain().get_g_x();
-   else if(field == "base_y")
-      return this->domain().get_g_y();
-   else if(field == "p")
-      return this->domain().get_p();
-   else if(field == "a")
-      return this->domain().get_a();
-   else if(field == "b")
-      return this->domain().get_b();
-   else if(field == "cofactor")
-      return this->domain().get_cofactor();
-   else if(field == "order")
-      return this->domain().get_order();
-   else
-      return Public_Key::get_int_field(field);
-   }
-
-const BigInt& EC_PrivateKey::get_int_field(const std::string& field) const
-   {
-   if(field == "x")
-      return this->private_value();
-   else
-      return EC_PublicKey::get_int_field(field);
-   }
-
 }
diff --git a/src/lib/pubkey/ecc_key/ecc_key.h b/src/lib/pubkey/ecc_key/ecc_key.h
index 272725180..271cca665 100644
--- a/src/lib/pubkey/ecc_key/ecc_key.h
+++ b/src/lib/pubkey/ecc_key/ecc_key.h
@@ -91,8 +91,6 @@ class BOTAN_PUBLIC_API(2,0) EC_PublicKey : public virtual Public_Key
       size_t key_length() const override;
       size_t estimated_strength() const override;
 
-      const BigInt& get_int_field(const std::string& field) const override;
-
    protected:
       /**
       * Create a public key.
@@ -138,8 +136,6 @@ class BOTAN_PUBLIC_API(2,0) EC_PrivateKey : public virtual EC_PublicKey,
       EC_PrivateKey& operator=(const EC_PrivateKey& other) = default;
       ~EC_PrivateKey() = default;
 
-      const BigInt& get_int_field(const std::string& field) const override;
-
    protected:
       /*
       * If x=0, creates a new private key in the domain
diff --git a/src/lib/pubkey/pk_keys.cpp b/src/lib/pubkey/pk_keys.cpp
index 1588809fe..c07f9ed8f 100644
--- a/src/lib/pubkey/pk_keys.cpp
+++ b/src/lib/pubkey/pk_keys.cpp
@@ -47,11 +47,6 @@ std::vector<uint8_t> Public_Key::subject_public_key() const
    return output;
    }
 
-const BigInt& Public_Key::get_int_field(const std::string& field) const
-   {
-   throw Unknown_PK_Field_Name(algo_name(), field);
-   }
-
 /*
 * Default OID access
 */
diff --git a/src/lib/pubkey/pk_keys.h b/src/lib/pubkey/pk_keys.h
index 4c0504cd3..7f96029e9 100644
--- a/src/lib/pubkey/pk_keys.h
+++ b/src/lib/pubkey/pk_keys.h
@@ -15,7 +15,6 @@
 
 namespace Botan {
 
-class BigInt;
 class RandomNumberGenerator;
 
 /**
@@ -96,21 +95,6 @@ class BOTAN_PUBLIC_API(2,0) Public_Key
        */
       std::string fingerprint_public(const std::string& alg = "SHA-256") const;
 
-      /**
-      * Access an algorithm specific field
-      *
-      * If the field is not known for this algorithm, an Invalid_Argument is
-      * thrown. The interpretation of the result requires knowledge of which
-      * algorithm is involved. For instance for RSA "p" represents one of the
-      * secret primes, while for DSA "p" is the public prime.
-      *
-      * Some algorithms may not implement this method at all.
-      *
-      * This is primarily used to implement the FFI botan_pubkey_get_field
-      * and botan_privkey_get_field functions.
-      */
-      virtual const BigInt& get_int_field(const std::string& field) const;
-
       // Internal or non-public declarations follow
 
       /**
diff --git a/src/lib/pubkey/rsa/rsa.cpp b/src/lib/pubkey/rsa/rsa.cpp
index 91e87e3a7..337b43581 100644
--- a/src/lib/pubkey/rsa/rsa.cpp
+++ b/src/lib/pubkey/rsa/rsa.cpp
@@ -101,16 +101,6 @@ std::shared_ptr<const RSA_Public_Data> RSA_PublicKey::public_data() const
    return m_public;
    }
 
-const BigInt& RSA_PublicKey::get_int_field(const std::string& field) const
-   {
-   if(field == "n")
-      return m_public->get_n();
-   else if(field == "e")
-      return m_public->get_e();
-   else
-      return Public_Key::get_int_field(field);
-   }
-
 const BigInt& RSA_PublicKey::get_n() const { return m_public->get_n(); }
 const BigInt& RSA_PublicKey::get_e() const { return m_public->get_e(); }
 
@@ -330,24 +320,6 @@ RSA_PrivateKey::RSA_PrivateKey(RandomNumberGenerator& rng,
                         std::move(d1), std::move(d2), std::move(c));
    }
 
-const BigInt& RSA_PrivateKey::get_int_field(const std::string& field) const
-   {
-   if(field == "p")
-      return m_private->get_p();
-   else if(field == "q")
-      return m_private->get_q();
-   else if(field == "d")
-      return m_private->get_d();
-   else if(field == "c")
-      return m_private->get_c();
-   else if(field == "d1")
-      return m_private->get_d1();
-   else if(field == "d2")
-      return m_private->get_d2();
-   else
-      return RSA_PublicKey::get_int_field(field);
-   }
-
 std::unique_ptr<Public_Key> RSA_PrivateKey::public_key() const
    {
    return std::make_unique<RSA_PublicKey>(get_n(), get_e());
diff --git a/src/lib/pubkey/rsa/rsa.h b/src/lib/pubkey/rsa/rsa.h
index 9a9ed92b1..99a83f820 100644
--- a/src/lib/pubkey/rsa/rsa.h
+++ b/src/lib/pubkey/rsa/rsa.h
@@ -61,8 +61,6 @@ class BOTAN_PUBLIC_API(2,0) RSA_PublicKey : public virtual Public_Key
       size_t key_length() const override;
       size_t estimated_strength() const override;
 
-      const BigInt& get_int_field(const std::string& field) const override;
-
       // internal functions:
       std::shared_ptr<const RSA_Public_Data> public_data() const;
 
@@ -130,8 +128,6 @@ class BOTAN_PUBLIC_API(2,0) RSA_PrivateKey final : public Private_Key, public RS
 
       bool check_key(RandomNumberGenerator& rng, bool) const override;
 
-      const BigInt& get_int_field(const std::string& field) const override;
-
       /**
       * Get the first prime p.
       * @return prime p
diff --git a/src/lib/rng/rng.h b/src/lib/rng/rng.h
index 2cda22562..39c3cc60f 100644
--- a/src/lib/rng/rng.h
+++ b/src/lib/rng/rng.h
@@ -1,6 +1,7 @@
 /*
 * Random Number Generator base classes
 * (C) 1999-2009,2015,2016 Jack Lloyd
+*     2023                René Meusel - Rohde & Schwarz Cybersecurity
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -8,12 +9,16 @@
 #ifndef BOTAN_RANDOM_NUMBER_GENERATOR_H_
 #define BOTAN_RANDOM_NUMBER_GENERATOR_H_
 
+#include <botan/concepts.h>
 #include <botan/secmem.h>
 #include <botan/exceptn.h>
 #include <botan/mutex.h>
+
 #include <type_traits>
 #include <chrono>
 #include <string>
+#include <span>
+#include <concepts>
 
 namespace Botan {
 
@@ -158,24 +163,52 @@ class BOTAN_PUBLIC_API(2,0) RandomNumberGenerator
       // Some utility functions built on the interface above:
 
       /**
-      * Return a random vector
-      * @param bytes number of bytes in the result
-      * @return randomized vector of length bytes
-      * @throws PRNG_Unseeded if the RNG fails because it has not enough entropy
-      * @throws Exception if the RNG fails
+      * Fill a given byte container with @p bytes random bytes
+      *
+      * @param  v     the container to be filled with @p bytes random bytes
+      * @throws Exception if RNG fails
       */
-      secure_vector<uint8_t> random_vec(size_t bytes)
+      void random_vec(std::span<uint8_t> v)
          {
-         secure_vector<uint8_t> output;
-         random_vec(output, bytes);
-         return output;
+         this->randomize(v.data(), v.size());
          }
 
-      template<typename Alloc>
-         void random_vec(std::vector<uint8_t, Alloc>& v, size_t bytes)
+      /**
+      * Resize a given byte container to @p bytes and fill it with random bytes
+      *
+      * @tparam T     the desired byte container type (e.g std::vector<uint8_t>)
+      * @param  v     the container to be filled with @p bytes random bytes
+      * @param  bytes number of random bytes to initialize the container with
+      * @throws Exception if RNG or memory allocation fails
+      */
+      template<typename T>
+      requires(concepts::contiguous_container<T> &&
+               concepts::resizable_container<T> &&
+               std::same_as<typename T::value_type, uint8_t>)
+      void random_vec(T& v, size_t bytes)
          {
          v.resize(bytes);
-         this->randomize(v.data(), v.size());
+         random_vec(v);
+         }
+
+      /**
+      * Create some byte container type and fill it with some random @p bytes.
+      *
+      * @tparam T     the desired byte container type (e.g std::vector<uint8_t>)
+      * @param  bytes number of random bytes to initialize the container with
+      * @return       a container of type T with @p bytes random bytes
+      * @throws Exception if RNG or memory allocation fails
+      */
+      template<typename T = secure_vector<uint8_t>>
+      requires(concepts::contiguous_container<T> &&
+               concepts::resizable_container<T> &&
+               concepts::default_initializable<T> &&
+               std::same_as<typename T::value_type, uint8_t>)
+      T random_vec(size_t bytes)
+         {
+         T result;
+         random_vec(result, bytes);
+         return result;
          }
 
       /**
diff --git a/src/lib/stream/chacha/chacha.cpp b/src/lib/stream/chacha/chacha.cpp
index 03e8d0c61..7c0b85bbf 100644
--- a/src/lib/stream/chacha/chacha.cpp
+++ b/src/lib/stream/chacha/chacha.cpp
@@ -69,16 +69,6 @@ ChaCha::ChaCha(size_t rounds) : m_rounds(rounds)
                    "ChaCha only supports 8, 12 or 20 rounds");
    }
 
-size_t ChaCha::parallelism()
-   {
-#if defined(BOTAN_HAS_CHACHA_AVX2)
-   if(CPUID::has_avx2())
-      return 8;
-#endif
-
-   return 4;
-   }
-
 std::string ChaCha::provider() const
    {
 #if defined(BOTAN_HAS_CHACHA_AVX2)
@@ -98,43 +88,34 @@ std::string ChaCha::provider() const
    return "base";
    }
 
-void ChaCha::chacha(uint8_t output[],
-                    size_t output_blocks,
-                    uint32_t state[16], size_t rounds)
+//static
+void ChaCha::chacha_x8(uint8_t output[64*8], uint32_t input[16], size_t rounds)
    {
    BOTAN_ASSERT(rounds % 2 == 0, "Valid rounds");
 
 #if defined(BOTAN_HAS_CHACHA_AVX2)
    if(CPUID::has_avx2())
       {
-      while(output_blocks >= 8)
-         {
-         ChaCha::chacha_avx2_x8(output, state, rounds);
-         output += 8*64;
-         output_blocks -= 8;
-         }
+      return ChaCha::chacha_avx2_x8(output, input, rounds);
       }
 #endif
 
 #if defined(BOTAN_HAS_CHACHA_SIMD32)
    if(CPUID::has_simd_32())
       {
-      while(output_blocks >= 4)
-         {
-         ChaCha::chacha_simd32_x4(output, state, rounds);
-         output += 4*64;
-         output_blocks -= 4;
-         }
+      ChaCha::chacha_simd32_x4(output, input, rounds);
+      ChaCha::chacha_simd32_x4(output + 4*64, input, rounds);
+      return;
       }
 #endif
 
    // TODO interleave rounds
-   for(size_t i = 0; i != output_blocks; ++i)
+   for(size_t i = 0; i != 8; ++i)
       {
-      uint32_t x00 = state[ 0], x01 = state[ 1], x02 = state[ 2], x03 = state[ 3],
-               x04 = state[ 4], x05 = state[ 5], x06 = state[ 6], x07 = state[ 7],
-               x08 = state[ 8], x09 = state[ 9], x10 = state[10], x11 = state[11],
-               x12 = state[12], x13 = state[13], x14 = state[14], x15 = state[15];
+      uint32_t x00 = input[ 0], x01 = input[ 1], x02 = input[ 2], x03 = input[ 3],
+               x04 = input[ 4], x05 = input[ 5], x06 = input[ 6], x07 = input[ 7],
+               x08 = input[ 8], x09 = input[ 9], x10 = input[10], x11 = input[11],
+               x12 = input[12], x13 = input[13], x14 = input[14], x15 = input[15];
 
       for(size_t r = 0; r != rounds / 2; ++r)
          {
@@ -149,22 +130,22 @@ void ChaCha::chacha(uint8_t output[],
          chacha_quarter_round(x03, x04, x09, x14);
          }
 
-      x00 += state[0];
-      x01 += state[1];
-      x02 += state[2];
-      x03 += state[3];
-      x04 += state[4];
-      x05 += state[5];
-      x06 += state[6];
-      x07 += state[7];
-      x08 += state[8];
-      x09 += state[9];
-      x10 += state[10];
-      x11 += state[11];
-      x12 += state[12];
-      x13 += state[13];
-      x14 += state[14];
-      x15 += state[15];
+      x00 += input[0];
+      x01 += input[1];
+      x02 += input[2];
+      x03 += input[3];
+      x04 += input[4];
+      x05 += input[5];
+      x06 += input[6];
+      x07 += input[7];
+      x08 += input[8];
+      x09 += input[9];
+      x10 += input[10];
+      x11 += input[11];
+      x12 += input[12];
+      x13 += input[13];
+      x14 += input[14];
+      x15 += input[15];
 
       store_le(x00, output + 64 * i + 4 *  0);
       store_le(x01, output + 64 * i + 4 *  1);
@@ -183,8 +164,8 @@ void ChaCha::chacha(uint8_t output[],
       store_le(x14, output + 64 * i + 4 * 14);
       store_le(x15, output + 64 * i + 4 * 15);
 
-      state[12]++;
-      state[13] += (state[12] == 0);
+      input[12]++;
+      input[13] += (input[12] == 0);
       }
    }
 
@@ -200,8 +181,7 @@ void ChaCha::cipher(const uint8_t in[], uint8_t out[], size_t length)
       const size_t available = m_buffer.size() - m_position;
 
       xor_buf(out, in, &m_buffer[m_position], available);
-      chacha(m_buffer.data(), m_buffer.size() / 64,
-             m_state.data(), m_rounds);
+      chacha_x8(m_buffer.data(), m_state.data(), m_rounds);
 
       length -= available;
       in += available;
@@ -222,11 +202,8 @@ void ChaCha::write_keystream(uint8_t out[], size_t length)
       {
       const size_t available = m_buffer.size() - m_position;
 
-      // TODO: this could write directly to the output buffer
-      // instead of bouncing it through m_buffer first
       copy_mem(out, &m_buffer[m_position], available);
-      chacha(m_buffer.data(), m_buffer.size() / 64,
-             m_state.data(), m_rounds);
+      chacha_x8(m_buffer.data(), m_state.data(), m_rounds);
 
       length -= available;
       out += available;
@@ -294,8 +271,9 @@ void ChaCha::key_schedule(const uint8_t key[], size_t length)
 
    m_state.resize(16);
 
+   const size_t chacha_parallelism = 8; // chacha_x8
    const size_t chacha_block = 64;
-   m_buffer.resize(parallelism() * chacha_block);
+   m_buffer.resize(chacha_parallelism * chacha_block);
 
    set_iv(nullptr, 0);
    }
@@ -370,8 +348,7 @@ void ChaCha::set_iv(const uint8_t iv[], size_t length)
       m_state[15] = load_le<uint32_t>(iv, 5);
       }
 
-   chacha(m_buffer.data(), m_buffer.size() / 64,
-          m_state.data(), m_rounds);
+   chacha_x8(m_buffer.data(), m_state.data(), m_rounds);
    m_position = 0;
    }
 
@@ -402,7 +379,7 @@ void ChaCha::seek(uint64_t offset)
    m_state[12] = load_le<uint32_t>(out, 0);
    m_state[13] += load_le<uint32_t>(out, 1);
 
-   chacha(m_buffer.data(), m_buffer.size() / 64, m_state.data(), m_rounds);
+   chacha_x8(m_buffer.data(), m_state.data(), m_rounds);
    m_position = offset % 64;
    }
 }
diff --git a/src/lib/stream/chacha/chacha.h b/src/lib/stream/chacha/chacha.h
index 351d4226e..c042dda8a 100644
--- a/src/lib/stream/chacha/chacha.h
+++ b/src/lib/stream/chacha/chacha.h
@@ -58,12 +58,7 @@ class ChaCha final : public StreamCipher
 
       void initialize_state();
 
-      static size_t parallelism();
-
-      static void chacha(uint8_t output[],
-                         size_t output_blocks,
-                         uint32_t state[16],
-                         size_t rounds);
+      static void chacha_x8(uint8_t output[64*8], uint32_t state[16], size_t rounds);
 
 #if defined(BOTAN_HAS_CHACHA_SIMD32)
       static void chacha_simd32_x4(uint8_t output[64*4], uint32_t state[16], size_t rounds);
diff --git a/src/lib/utils/concepts.h b/src/lib/utils/concepts.h
index 16540b028..efda68047 100644
--- a/src/lib/utils/concepts.h
+++ b/src/lib/utils/concepts.h
@@ -12,6 +12,7 @@
 #include <compare>
 #include <concepts>
 #include <type_traits>
+#include <ostream>
 
 namespace Botan::concepts {
 
@@ -47,6 +48,19 @@ concept three_way_comparable = requires(const std::remove_reference_t<T>& a, con
    { a <=> b } -> three_way_comparison_result;
    };
 
+template<class T>
+concept destructible = std::is_nothrow_destructible_v<T>;
+
+template<class T, class... Args>
+concept constructible_from =
+   destructible<T> && std::is_constructible_v<T, Args...>;
+
+template<class T>
+concept default_initializable =
+    constructible_from<T> &&
+    requires { T{}; } &&
+    requires { ::new (static_cast<void*>(nullptr)) T; };
+
 // TODO: C++20 provides concepts like std::ranges::range or ::sized_range
 //       but at the time of this writing clang had not caught up on all
 //       platforms. E.g. clang 14 on Xcode does not support ranges properly.
@@ -70,6 +84,7 @@ concept container = requires(T a)
    { a.cbegin() } -> container_iterator<T>;
    { a.cend() } -> container_iterator<T>;
    { a.size() } -> std::same_as<typename T::size_type>;
+   typename T::value_type;
    };
 
 template<typename T>
diff --git a/src/lib/utils/exceptn.cpp b/src/lib/utils/exceptn.cpp
index 973b00b39..6b5b5d385 100644
--- a/src/lib/utils/exceptn.cpp
+++ b/src/lib/utils/exceptn.cpp
@@ -101,12 +101,6 @@ Internal_Error::Internal_Error(const std::string& err) :
    Exception("Internal error: " + err)
    {}
 
-Unknown_PK_Field_Name::Unknown_PK_Field_Name(const std::string& algo_name,
-                                             const std::string& field_name) :
-   Invalid_Argument("Unknown field '" + field_name +
-                    "' for algorithm " + algo_name)
-   {}
-
 Invalid_Key_Length::Invalid_Key_Length(const std::string& name, size_t length) :
    Invalid_Argument(name + " cannot accept a key of length " +
                     std::to_string(length))
diff --git a/src/lib/utils/exceptn.h b/src/lib/utils/exceptn.h
index e12d8fb0f..23acf429f 100644
--- a/src/lib/utils/exceptn.h
+++ b/src/lib/utils/exceptn.h
@@ -141,15 +141,6 @@ class BOTAN_PUBLIC_API(2,0) Invalid_Argument : public Exception
       ErrorType error_type() const noexcept override { return ErrorType::InvalidArgument; }
    };
 
-/**
-* An invalid/unknown field name was passed to Public_Key::get_int_field
-*/
-class BOTAN_PUBLIC_API(3,0) Unknown_PK_Field_Name final : public Invalid_Argument
-   {
-   public:
-      Unknown_PK_Field_Name(const std::string& algo_name, const std::string& field_name);
-   };
-
 /**
 * An invalid key length was used
 */
diff --git a/src/lib/utils/safeint.h b/src/lib/utils/safeint.h
index abd1648f0..33e58c156 100644
--- a/src/lib/utils/safeint.h
+++ b/src/lib/utils/safeint.h
@@ -25,14 +25,9 @@ class Integer_Overflow_Detected final : public Exception
 
 inline size_t checked_add(size_t x, size_t y, const char* file, int line)
    {
-#if BOTAN_COMPILER_HAS_BUILTIN(__builtin_add_overflow)
-   size_t z;
-   if(__builtin_add_overflow(x, y, &z)) [[unlikely]]
-#else
-   // (Possible) TODO Windows supports SizeTAdd (intsafe.h) but unsure it is really a win.
+   // TODO: use __builtin_x_overflow on GCC and Clang
    size_t z = x + y;
    if(z < x) [[unlikely]]
-#endif
       {
       throw Integer_Overflow_Detected(file, line);
       }
diff --git a/src/lib/utils/strong_type.h b/src/lib/utils/strong_type.h
index 9b6c36181..02d0020c9 100644
--- a/src/lib/utils/strong_type.h
+++ b/src/lib/utils/strong_type.h
@@ -51,6 +51,11 @@ class Strong_Adapter<T> : public Strong_Base<T>
    {
    public:
       using value_type = typename T::value_type;
+      using size_type = typename T::size_type;
+      using iterator = typename T::iterator;
+      using const_iterator = typename T::const_iterator;
+      using pointer = typename T::pointer;
+      using const_pointer = typename T::const_pointer;
 
    public:
       using Strong_Base<T>::Strong_Base;
@@ -79,7 +84,19 @@ class Strong_Adapter<T> : public Strong_Base<T>
       decltype(auto) end() const noexcept(noexcept(this->get().end()))
          { return this->get().end(); }
 
-      auto size() const noexcept(noexcept(this->get().size()))
+      decltype(auto) cbegin() noexcept(noexcept(this->get().cbegin()))
+         { return this->get().cbegin(); }
+
+      decltype(auto) cbegin() const noexcept(noexcept(this->get().cbegin()))
+         { return this->get().cbegin(); }
+
+      decltype(auto) cend() noexcept(noexcept(this->get().cend()))
+         { return this->get().cend(); }
+
+      decltype(auto) cend() const noexcept(noexcept(this->get().cend()))
+         { return this->get().cend(); }
+
+      size_type size() const noexcept(noexcept(this->get().size()))
          { return this->get().size(); }
 
       decltype(auto) data() noexcept(noexcept(this->get().data()))
@@ -94,7 +111,7 @@ class Strong_Adapter<T> : public Strong_Base<T>
       requires(concepts::has_empty<T>)
          { return this->get().empty(); }
 
-      void resize(size_t size) noexcept(noexcept(this->get().resize(size)))
+      void resize(size_type size) noexcept(noexcept(this->get().resize(size)))
       requires(concepts::resizable_container<T>)
          { this->get().resize(size); }
    };
diff --git a/src/scripts/ci/setup_gh_actions_after_vcvars.ps1 b/src/scripts/ci/setup_gh_actions_after_vcvars.ps1
index 06dd072ab..503974dd5 100644
--- a/src/scripts/ci/setup_gh_actions_after_vcvars.ps1
+++ b/src/scripts/ci/setup_gh_actions_after_vcvars.ps1
@@ -6,7 +6,7 @@
 #
 # Botan is released under the Simplified BSD License (see license.txt)
 
-if ($args[0] -in @('shared')) {
+if ($args[0] -in @('shared','sanitizer')) {
     nuget install -NonInteractive -OutputDirectory $env:DEPENDENCIES_LOCATION -Version 1.79.0 boost
 
     $boostincdir = Join-Path -Path $env:DEPENDENCIES_LOCATION -ChildPath "boost.1.79.0/lib/native/include"
diff --git a/src/scripts/ci_build.py b/src/scripts/ci_build.py
index 89a6fa27c..59941afd1 100755
--- a/src/scripts/ci_build.py
+++ b/src/scripts/ci_build.py
@@ -112,9 +112,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
 
     make_prefix = []
     test_prefix = []
-    test_cmd = [os.path.join(build_dir, 'botan-test'),
-                '--data-dir=%s' % os.path.join(root_dir, 'src', 'tests', 'data'),
-                '--run-memory-intensive-tests']
+    test_cmd = [os.path.join(build_dir, 'botan-test'), '--data-dir=%s' % os.path.join(root_dir, 'src', 'tests', 'data'), '--run-memory-intensive-tests']
 
     # generate JUnit test report
     if test_results_dir:
@@ -323,16 +321,10 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
         if target_os in ['osx', 'ios']:
             flags += ['--with-commoncrypto']
 
-        def add_boost_support(target, target_os):
-            if target in ['coverage', 'shared']:
-                return True
-
-            if target == 'sanitizer' and target_os == 'linux':
-                return True
-
-            return False
+        add_boost_support = target in ['coverage', 'sanitizer', 'shared'] \
+            and not (target_os == 'osx' and target_cc == 'gcc')
 
-        if add_boost_support(target, target_os):
+        if add_boost_support:
             flags += ['--with-boost']
             if target_cc == 'clang':
                 # make sure clang ignores warnings in boost headers
@@ -358,16 +350,14 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                 test_cmd += ['--pkcs11-lib=%s' % (pkcs11_lib)]
 
     if target in ['coverage', 'sanitizer']:
-        test_cmd += ['--run-long-tests']
-
         if target_os == 'windows' and target == 'sanitizer':
             # GitHub Actions worker intermittently ran out of memory when
             # asked to allocate multi-gigabyte buffers under MSVC's ASan.
-            test_cmd.remove('--run-memory-intensive-tests')
-
-            # MSVC sanitizer produces very slow code causing some of the
-            # slower tests to take as long as 5 minutes
-            test_cmd.remove('--run-long-tests')
+            try:
+                test_cmd.remove('--run-memory-intensive-tests')
+            except ValueError:
+                pass
+        test_cmd += ['--run-long-tests']
 
     flags += ['--cc-bin=%s' % (cc_bin)]
 


Test patch:
diff --git a/src/tests/test_dl_group.cpp b/src/tests/test_dl_group.cpp
index 22f6c2e33..453645751 100644
--- a/src/tests/test_dl_group.cpp
+++ b/src/tests/test_dl_group.cpp
@@ -99,20 +99,19 @@ class DL_Generate_Group_Tests final : public Test
          result.test_eq("DH p size", dh1050.get_p().bits(), 1050);
          result.test_eq("DH q size", dh1050.get_q().bits(), 175);
          result.test_lte("DH g size", dh1050.get_g().bits(), 1050);
-         result.test_eq("DH group verifies", dh1050.verify_group(rng, false), true);
+         result.test_eq("DH group verifies", dh1050.verify_group(rng, true), true);
 
          Botan::DL_Group dh_implicit_q(rng, Botan::DL_Group::Prime_Subgroup, 1040);
          result.test_eq("DH p size", dh_implicit_q.get_p().bits(), 1040);
          result.test_eq("DH q size", dh_implicit_q.get_q().bits(), Botan::dl_exponent_size(1040));
-         result.test_lte("DH g size", dh_implicit_q.get_g().bits(), 1040);
-         result.test_eq("DH group verifies", dh_implicit_q.verify_group(rng, false), true);
+         result.test_eq("DH group verifies", dh_implicit_q.verify_group(rng, true), true);
 
          if(Test::run_long_tests())
             {
             Botan::DL_Group dh_strong(rng, Botan::DL_Group::Strong, 1025);
             result.test_eq("DH p size", dh_strong.get_p().bits(), 1025);
             result.test_eq("DH q size", dh_strong.get_q().bits(), 1024);
-            result.test_eq("DH group verifies", dh_strong.verify_group(rng, false), true);
+            result.test_eq("DH group verifies", dh_strong.verify_group(rng, true), true);
             }
 
 #if defined(BOTAN_HAS_SHA1)
@@ -120,7 +119,7 @@ class DL_Generate_Group_Tests final : public Test
          result.test_eq("DSA p size", dsa1024.get_p().bits(), 1024);
          result.test_eq("DSA q size", dsa1024.get_q().bits(), 160);
          result.test_lte("DSA g size", dsa1024.get_g().bits(), 1024);
-         result.test_eq("DSA group verifies", dsa1024.verify_group(rng, false), true);
+         result.test_eq("DSA group verifies", dsa1024.verify_group(rng, true), true);
 
          const std::vector<uint8_t> short_seed(16);
          const std::vector<uint8_t> invalid_seed(20);
@@ -149,7 +148,7 @@ class DL_Generate_Group_Tests final : public Test
          // Modulo just to avoid embedding entire 1024-bit P in src file
          result.test_eq("DSA p from seed", static_cast<size_t>(dsa_from_seed.get_p() % 4294967291), size_t(2513712339));
 
-         result.test_eq("DSA group from seed verifies", dsa_from_seed.verify_group(rng, false), true);
+         result.test_eq("DSA group from seed verifies", dsa_from_seed.verify_group(rng, true), true);
 #endif
 
          result.end_timer();
@@ -223,7 +222,7 @@ class DL_Named_Group_Tests final : public Test
 
             if(group.p_bits() <= 1536 || Test::run_long_tests())
                {
-               result.test_eq(name + " verifies", group.verify_group(Test::rng()), true);
+               result.test_eq(name + " strong verifies", group.verify_group(Test::rng(), true), true);
                }
 
             }
diff --git a/src/tests/test_strong_type.cpp b/src/tests/test_strong_type.cpp
index dc6740750..44937e77a 100644
--- a/src/tests/test_strong_type.cpp
+++ b/src/tests/test_strong_type.cpp
@@ -6,12 +6,18 @@
  */
 
 #include "tests.h"
+#include "test_rng.h"
 
 #include <botan/strong_type.h>
 #include <botan/hex.h>
+#include <botan/rng.h>
+#include <botan/secmem.h>
 
 #include <sstream>
 #include <algorithm>
+#include <array>
+#include <vector>
+#include <string>
 
 namespace Botan_Tests {
 
@@ -95,6 +101,27 @@ std::vector<Test::Result> test_container_strong_type()
             { result.confirm("iteration", c > 0); }
          }),
 
+      Botan_Tests::CHECK("container concepts are satisfied", [](auto& result)
+         {
+         using Test_Map = Botan::Strong<std::map<int, std::string>, struct Test_Map_>;
+         using Test_Array = Botan::Strong<std::array<uint64_t, 32>, struct Test_Array_>;
+
+         result.confirm("Test_Nonce is container", Botan::concepts::container<Test_Nonce>);
+         result.confirm("Test_Array is container", Botan::concepts::container<Test_Array>);
+         result.confirm("Test_Map is container", Botan::concepts::container<Test_Map>);
+         result.confirm("Test_Size is not container", !Botan::concepts::container<Test_Size>);
+
+         result.confirm("Test_Nonce is contiguous_container", Botan::concepts::contiguous_container<Test_Nonce>);
+         result.confirm("Test_Array is contiguous_container", Botan::concepts::contiguous_container<Test_Array>);
+         result.confirm("Test_Map is not contiguous_container", !Botan::concepts::contiguous_container<Test_Map>);
+         result.confirm("Test_Size is not contiguous_container", !Botan::concepts::contiguous_container<Test_Size>);
+
+         result.confirm("Test_Nonce is resizable_container", Botan::concepts::resizable_container<Test_Nonce>);
+         result.confirm("Test_Array is not resizable_container", !Botan::concepts::resizable_container<Test_Array>);
+         result.confirm("Test_Map is not resizable_container", !Botan::concepts::resizable_container<Test_Map>);
+         result.confirm("Test_Size is not resizable_container", !Botan::concepts::resizable_container<Test_Size>);
+         }),
+
       Botan_Tests::CHECK("binds to a std::span<>", [](auto& result)
          {
          auto get_size = [](std::span<const uint8_t> data)
@@ -134,6 +161,30 @@ std::vector<Test::Result> test_container_strong_type()
          result.test_eq("3", hashes.get().at(2).get(), size_t(3));
          result.test_eq("4", hashes.get().at(3).get(), size_t(4));
          }),
+
+      Botan_Tests::CHECK("byte-container strong types can be randomly generated", [](auto& result)
+         {
+         using Test_Buffer = Botan::Strong<std::vector<uint8_t>, struct Test_Buffer_>;
+         using Test_Secure_Buffer = Botan::Strong<Botan::secure_vector<uint8_t>, struct Test_Secure_Buffer_>;
+         using Test_Fixed_Array = Botan::Strong<std::array<uint8_t, 4>, struct Test_Fixed_Array_>;
+
+         Botan_Tests::Fixed_Output_RNG rng;
+         const auto e1 = Botan::hex_decode("deadbeef");
+         const auto e2 = Botan::hex_decode("baadcafe");
+         const auto e3 = Botan::hex_decode("baadf00d");
+         rng.add_entropy(e1.data(), e1.size());
+         rng.add_entropy(e2.data(), e2.size());
+         rng.add_entropy(e3.data(), e3.size());
+
+         auto tb = rng.random_vec<Test_Buffer>(4);
+         auto tsb = rng.random_vec<Test_Secure_Buffer>(4);
+         Test_Fixed_Array tfa;
+         rng.random_vec(tfa);
+
+         result.test_eq("generated expected output", tb.get(), Botan::hex_decode("deadbeef"));
+         result.test_eq("generated expected secure output", tsb.get(), Botan::hex_decode_locked("baadcafe"));
+         result.test_eq("generated expected fixed output", std::vector(tfa.begin(), tfa.end()), Botan::hex_decode("baadf00d"));
+         }),
       };
    }