Repository: randombit/botan (cpp)

Issue:
ECDSA producing unverifiable or incorrect signatures for secp128r2
Seems to be a new issue.

```cpp
#include <botan/ecdsa.h>
#include <botan/pubkey.h>
#include <botan/system_rng.h>
#include <botan/hash.h>
#include <botan/ber_dec.h>

static void sign_verify(const std::string& curve) {
    {
        const ::Botan::OID secp128r1_oid("1.3.132.0.28");
        const ::Botan::EC_Group secp128r1(
                ::Botan::BigInt("340282366762482138434845932244680310783"),
                ::Botan::BigInt("340282366762482138434845932244680310780"),
                ::Botan::BigInt("308990863222245658030922601041482374867"),
                ::Botan::BigInt("29408993404948928992877151431649155974"),
                ::Botan::BigInt("275621562871047521857442314737465260675"),
                ::Botan::BigInt("340282366762482138443322565580356624661"),
                1,
                secp128r1_oid);
        ::Botan::OID::register_oid(secp128r1_oid, "secp128r1");
    }

    {
        const ::Botan::OID secp128r2_oid("1.3.132.0.29");
        const ::Botan::EC_Group secp128r2(
                ::Botan::BigInt("340282366762482138434845932244680310783"),
                ::Botan::BigInt("284470887156368047300405921324061011681"),
                ::Botan::BigInt("126188322377389722996253562430093625949"),
                ::Botan::BigInt("164048790688614013222215505581242564928"),
                ::Botan::BigInt("52787839253935625605232456597451787076"),
                ::Botan::BigInt("85070591690620534603955721926813660579"),
                4,
                secp128r2_oid);
        ::Botan::OID::register_oid(secp128r2_oid, "secp128r2");
    }

    ::Botan::System_RNG rng;
    const std::vector<uint8_t> msg{0xFF};
    std::unique_ptr<::Botan::PK_Signer> signer;
    ::Botan::EC_Group group(curve);
    const ::Botan::BigInt priv_bn("1");
    const auto priv = std::make_unique<::Botan::ECDSA_PrivateKey>(::Botan::ECDSA_PrivateKey(rng, group, priv_bn));
    signer.reset(new ::Botan::PK_Signer(*priv, rng, "EMSA1(SHA-256)", ::Botan::Signature_Format::DerSequence));
    const auto signature = signer->sign_message(msg, rng);
    ::Botan::BER_Decoder decoder(signature);
    ::Botan::BER_Decoder ber_sig = decoder.start_sequence();

    size_t count = 0;

    ::Botan::BigInt R;
    ::Botan::BigInt S;
    while(ber_sig.more_items())
    {
        switch ( count ) {
            case    0:
                ber_sig.decode(R);
                break;
            case    1:
                ber_sig.decode(S);
                break;
            default:
                printf("Error: Too many parts in signature BER\n");
                abort();
        }

        ++count;
    }

    const auto pub_x = priv->public_point().get_affine_x();
    const auto pub_y = priv->public_point().get_affine_y();

    const ::Botan::PointGFp public_point = group.point(pub_x, pub_y);
    const auto pub = std::make_unique<::Botan::ECDSA_PublicKey>(::Botan::ECDSA_PublicKey(group, public_point));
    const auto sig = ::Botan::BigInt::encode_fixed_length_int_pair(R, S, group.get_order_bytes());
    auto hash = ::Botan::HashFunction::create("SHA-256");
    hash->update(msg);
    const auto _msg = hash->final();
    printf("%s; verified: %d\n", curve.c_str(), ::Botan::PK_Verifier(*pub, "Raw").verify_message(_msg, sig));

    return;
}
int main(void)
{
    const std::vector<std::string> curves{
            "secp128r1",
            "secp128r2",
    };

    for (const auto& c : curves) {
        sign_verify(c);
    }
}
```

Output:

```
secp128r1; verified: 1
secp128r2; verified: 0
```

Pull request:
Fix ECDSA verification on curves with cofactors
The trick in #4211 doesn't work correctly for curves with cofactors. Since such curves are weird and deprecated, just force to affine for such curves, rather than complicating the fast path.

Fixes #4219

Patch:
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 1240b7c65..9fa4fee1d 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -29,27 +29,6 @@ jobs:
 
       matrix:
         include:
-          - target: cross-alpha
-            compiler: gcc
-            host_os: ubuntu-24.04
-          - target: cross-hppa64
-            compiler: gcc
-            host_os: ubuntu-24.04
-          - target: cross-m68k
-            compiler: gcc
-            host_os: ubuntu-24.04
-          - target: cross-mips
-            compiler: gcc
-            host_os: ubuntu-24.04
-          - target: cross-ppc32
-            compiler: gcc
-            host_os: ubuntu-24.04
-          - target: cross-sh4
-            compiler: gcc
-            host_os: ubuntu-24.04
-          - target: cross-sparc64
-            compiler: gcc
-            host_os: ubuntu-24.04
           - target: cross-riscv64
             compiler: gcc
             host_os: ubuntu-24.04
diff --git a/configure.py b/configure.py
index aca1b3e3d..96a1c2555 100755
--- a/configure.py
+++ b/configure.py
@@ -309,7 +309,7 @@ class BuildPaths:
             return (self.example_sources, self.example_obj_dir)
         raise InternalError("Unknown src info type '%s'" % (typ))
 
-ACCEPTABLE_BUILD_TARGETS = ["static", "shared", "cli", "tests", "bogo_shim", "examples", "ct_selftest"]
+ACCEPTABLE_BUILD_TARGETS = ["static", "shared", "cli", "tests", "bogo_shim", "examples"]
 
 def process_command_line(args):
     """
@@ -2078,8 +2078,6 @@ def create_template_vars(source_paths, build_paths, options, modules, disabled_m
             yield 'bogo_shim'
         if 'examples' in options.build_targets:
             yield 'examples'
-        if 'ct_selftest' in options.build_targets:
-            yield 'ct_selftest'
 
     def install_targets(options):
         yield 'libs'
@@ -2282,9 +2280,6 @@ def create_template_vars(source_paths, build_paths, options, modules, disabled_m
         'build_bogo_shim': bool('bogo_shim' in options.build_targets),
         'bogo_shim_src': os.path.join(source_paths.src_dir, 'bogo_shim', 'bogo_shim.cpp'),
 
-        'build_ct_selftest': bool('ct_selftest' in options.build_targets),
-        'ct_selftest_src': os.path.join(source_paths.src_dir, 'ct_selftest', 'ct_selftest.cpp'),
-
         'os_features': osinfo.enabled_features(options),
         'os_name': osinfo.basename,
         'cpu_features': arch.supported_isa_extensions(cc, options),
diff --git a/doc/dev_ref/contributing.rst b/doc/dev_ref/contributing.rst
index b0cebdc49..6f3660ac2 100644
--- a/doc/dev_ref/contributing.rst
+++ b/doc/dev_ref/contributing.rst
@@ -14,7 +14,6 @@ Under ``src`` there are directories
 * ``bogo_shim`` contains the shim binary and configuration for
   `BoringSSL's TLS test suite <https://github.com/google/boringssl/tree/master/ssl/test>`_
 * ``fuzzer`` contains fuzz targets for various modules of the library
-* ``ct_selftest`` has some tests to validate constant time checker tools (e.g. valgrind)
 * ``build-data`` contains files read by the configure script. For
   example ``build-data/cc/gcc.txt`` describes various gcc options.
 * ``examples`` contains usage examples used in the documentation.
diff --git a/src/build-data/buildh.in b/src/build-data/buildh.in
index d39b5a624..11be766e5 100644
--- a/src/build-data/buildh.in
+++ b/src/build-data/buildh.in
@@ -190,17 +190,6 @@
    #define BOTAN_MEM_POOL_USE_MMU_PROTECTIONS
 #endif
 
-#if defined(BOTAN_HAS_VALGRIND)
-   /**
-    * If `BOTAN_CT_POISON_ENABLED` is defined, then the `CT::poison` and
-    * `CT::unpoison` functions have an effect and do not just compile to no-ops.
-    *
-    * At the moment that is only the case when building with valgrind support. We
-    * could potentially add support for other tools in the future.
-    */
-   #define BOTAN_CT_POISON_ENABLED
-#endif
-
 /**
 * If enabled uses memset via volatile function pointer to zero memory,
 * otherwise does a byte at a time write via a volatile pointer.
diff --git a/src/build-data/cc/gcc.txt b/src/build-data/cc/gcc.txt
index f73386c07..ba5b617dd 100644
--- a/src/build-data/cc/gcc.txt
+++ b/src/build-data/cc/gcc.txt
@@ -13,7 +13,7 @@ warning_flags "-Wall -Wextra -Wpedantic -Wstrict-aliasing -Wcast-align -Wmissing
 # -Wmaybe-uninitialized is buggy https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105937
 # -Wstringop-overread is buggy https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111499
 # -Wfree-nonheap-object is buggy https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115016
-werror_flags "-Werror -Wno-error=strict-overflow -Wno-error=zero-as-null-pointer-constant -Wno-error=non-virtual-dtor -Wno-error=maybe-uninitialized -Wno-error=stringop-overread -Wno-error=stringop-overflow -Wno-error=free-nonheap-object -Wno-error=restrict"
+werror_flags "-Werror -Wno-error=strict-overflow -Wno-error=zero-as-null-pointer-constant -Wno-error=non-virtual-dtor -Wno-error=maybe-uninitialized -Wno-error=stringop-overread -Wno-error=stringop-overflow -Wno-error=free-nonheap-object"
 
 maintainer_warning_flags "-Wstrict-overflow=5"
 
diff --git a/src/build-data/makefile.in b/src/build-data/makefile.in
index e59085667..5508d1684 100644
--- a/src/build-data/makefile.in
+++ b/src/build-data/makefile.in
@@ -119,15 +119,6 @@ bogo_shim: %{out_dir}/botan_bogo_shim
 
 %{endif}
 
-%{if build_ct_selftest}
-
-ct_selftest: %{out_dir}/botan_ct_selftest
-
-%{out_dir}/botan_ct_selftest: %{ct_selftest_src} $(LIBRARIES)
-	$(CXX) $(BUILD_FLAGS) -DBOTAN_IS_BEING_BUILT %{public_include_flags} %{internal_include_flags} %{external_include_flags} %{ct_selftest_src} $(BUILD_DIR_LINK_PATH) $(LDFLAGS) $(EXE_LINKS_TO) %{output_to_exe}$@
-
-%{endif}
-
 # Library targets
 
 %{if build_static_lib}
diff --git a/src/build-data/ninja.in b/src/build-data/ninja.in
index e2ee02ed2..10ebad684 100644
--- a/src/build-data/ninja.in
+++ b/src/build-data/ninja.in
@@ -127,14 +127,6 @@ build %{out_dir}/bogo_shim_object: compile_exe %{bogo_shim_src}
 
 %{endif}
 
-%{if build_ct_selftest}
-
-build botan_ct_selftest: link_cli ct_selftest_object | %{library_targets}
-
-build %{out_dir}/ct_selftest_object: compile_exe %{ct_selftest_src}
-
-%{endif}
-
 # Misc targets
 
 rule build_docs
@@ -168,8 +160,6 @@ build tests: phony %{test_exe}
 
 build bogo_shim: phony botan_bogo_shim
 
-build ct_selftest: phony botan_ct_selftest
-
 build libs: phony %{library_targets} $
 %{if symlink_shared_lib}
   %{out_dir}/%{soname_base} %{out_dir}/%{soname_patch}
diff --git a/src/lib/math/bigint/bigint.cpp b/src/lib/math/bigint/bigint.cpp
index 24a87e60e..d6d84facd 100644
--- a/src/lib/math/bigint/bigint.cpp
+++ b/src/lib/math/bigint/bigint.cpp
@@ -519,7 +519,7 @@ void BigInt::ct_cond_assign(bool predicate, const BigInt& other) {
    cond_flip_sign(predicate && different_sign);
 }
 
-#if defined(BOTAN_CT_POISON_ENABLED)
+#if defined(BOTAN_HAS_VALGRIND)
 void BigInt::_const_time_poison() const {
    CT::poison(m_data.const_data(), m_data.size());
 }
diff --git a/src/lib/math/bigint/bigint.h b/src/lib/math/bigint/bigint.h
index 3606e8ac1..47ca35983 100644
--- a/src/lib/math/bigint/bigint.h
+++ b/src/lib/math/bigint/bigint.h
@@ -804,7 +804,7 @@ class BOTAN_PUBLIC_API(2, 0) BigInt final {
 
       BOTAN_DEPRECATED("replaced by internal API") void const_time_unpoison() const { _const_time_unpoison(); }
 
-#if defined(BOTAN_CT_POISON_ENABLED)
+#if defined(BOTAN_HAS_VALGRIND)
       void _const_time_poison() const;
       void _const_time_unpoison() const;
 #else
diff --git a/src/lib/math/numbertheory/nistp_redc.cpp b/src/lib/math/numbertheory/nistp_redc.cpp
index ac579a77f..68209a05d 100644
--- a/src/lib/math/numbertheory/nistp_redc.cpp
+++ b/src/lib/math/numbertheory/nistp_redc.cpp
@@ -75,7 +75,7 @@ namespace {
 * Treating this MPI as a sequence of 32-bit words in big-endian
 * order, return word i. The array is assumed to be large enough.
 */
-constexpr uint32_t get_uint32(const word xw[], size_t i) {
+inline uint32_t get_uint32(const word xw[], size_t i) {
 #if(BOTAN_MP_WORD_BITS == 32)
    return xw[i];
 #else
diff --git a/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h b/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h
index 996423ea0..33225014f 100644
--- a/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h
+++ b/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h
@@ -176,7 +176,7 @@ class IntMod final {
          return Self(Rep::redc(z));
       }
 
-      constexpr void square_n(size_t n) {
+      void square_n(size_t n) {
          std::array<W, 2 * N> z;
          for(size_t i = 0; i != n; ++i) {
             comba_sqr<N>(z.data(), this->data());
@@ -698,52 +698,14 @@ class ProjectiveCurvePoint {
       }
 
       constexpr Self dbl_n(size_t n) const {
-         if constexpr(Self::A_is_minus_3) {
-            /*
-            Repeated doubling using an adaptation of Algorithm 3.23 in
-            "Guide To Elliptic Curve Cryptography"
-            Hankerson, Menezes, Vanstone
-
-            For A == -3
-            Cost: 2S + 1*2 + n*(4S + 4M + 2*2 + 1*3 + 4A)
-            Naive doubling
-            Cost: n*(4S + 4M + 2*2 + 1*3 + 5A + 1*4 + 1*8)
+         // TODO it is possible to optimize this by carrying over values from
+         // the previous iteration into the next
 
-            TODO adapt this for A == 0 and/or generic A cases
-            */
-
-            // The inverse of 2 modulo P is (P/2)+1; this avoids a constexpr time
-            // general inversion, which some compilers can't handle
-            constexpr auto INV_2 = FieldElement::from_words(p_div_2_plus_1(Params::PW));
-
-            auto nx = x();
-            auto ny = y();
-            auto nz = z();
-            ny = ny.mul2();
-            auto w = nz.square().square();
-
-            while(n > 0) {
-               const auto ny2 = ny.square();
-               const auto ny4 = ny2.square();
-               const auto t1 = (nx.square() - w).mul3();
-               const auto t2 = nx * ny2;
-               nx = t1.square() - t2.mul2();
-               nz *= ny;
-               ny = t1 * (t2 - nx).mul2() - ny4;
-               n--;
-               if(n > 0) {
-                  w *= ny4;
-               }
-            }
-            ny *= INV_2;
-            return Self(nx, ny, nz);
-         } else {
-            Self pt = (*this);
-            for(size_t i = 0; i != n; ++i) {
-               pt = pt.dbl();
-            }
-            return pt;
+         Self pt = (*this);
+         for(size_t i = 0; i != n; ++i) {
+            pt = pt.dbl();
          }
+         return pt;
       }
 
       constexpr Self dbl() const {
@@ -756,7 +718,7 @@ class ProjectiveCurvePoint {
             if a == -3 then
             3*x^2 + a*z^4 == 3*x^2 - 3*z^4 == 3*(x^2-z^4) == 3*(x-z^2)*(x+z^2)
 
-            Cost: 1M + 1S + 2A + 1*3
+            Cost: 2M + 2A + 1*3
             */
             const auto z2 = z().square();
             m = (x() - z2).mul3() * (x() + z2);
@@ -769,6 +731,7 @@ class ProjectiveCurvePoint {
             const auto z2 = z().square();
             m = x().square().mul3() + A * z2.square();
          }
+
          const auto y2 = y().square();
          const auto s = x().mul4() * y2;
          const auto nx = m.square() - s.mul2();
diff --git a/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h b/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h
index 33a86fed4..0f3e9af4a 100644
--- a/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h
+++ b/src/lib/math/pcurves/pcurves_impl/pcurves_solinas.h
@@ -47,9 +47,9 @@ class SolinasAccum {
 
       static constexpr size_t N32 = N * (WordInfo<W>::bits / 32);
 
-      constexpr SolinasAccum(std::array<W, N>& r) : m_r(r), m_S(0), m_idx(0) {}
+      SolinasAccum(std::array<W, N>& r) : m_r(r), m_S(0), m_idx(0) {}
 
-      constexpr void accum(int64_t v) {
+      void accum(int64_t v) {
          BOTAN_DEBUG_ASSERT(m_idx < N32);
 
          m_S += v;
@@ -65,7 +65,7 @@ class SolinasAccum {
          m_idx += 1;
       }
 
-      constexpr W final_carry(int64_t C) {
+      W final_carry(int64_t C) {
          BOTAN_DEBUG_ASSERT(m_idx == N32);
          m_S += C;
          BOTAN_DEBUG_ASSERT(m_S >= 0);
diff --git a/src/lib/math/pcurves/pcurves_impl/pcurves_util.h b/src/lib/math/pcurves/pcurves_impl/pcurves_util.h
index 1f81e954b..21f4a627d 100644
--- a/src/lib/math/pcurves/pcurves_impl/pcurves_util.h
+++ b/src/lib/math/pcurves/pcurves_impl/pcurves_util.h
@@ -181,15 +181,6 @@ inline consteval std::array<W, N> p_minus_1_over_2(const std::array<W, N>& p) {
    return r;
 }
 
-template <WordType W, size_t N>
-inline consteval std::array<W, N> p_div_2_plus_1(const std::array<W, N>& p) {
-   const W one = 1;
-   std::array<W, N> r = p;
-   shift_right<1>(r);
-   bigint_add2_nc(r.data(), N, &one, 1);
-   return r;
-}
-
 template <WordType W, size_t N>
 inline consteval size_t count_bits(const std::array<W, N>& p) {
    auto get_bit = [&](size_t i) {
diff --git a/src/lib/pubkey/ec_group/ec_inner_bn.cpp b/src/lib/pubkey/ec_group/ec_inner_bn.cpp
index 271fe93ae..d45318bab 100644
--- a/src/lib/pubkey/ec_group/ec_inner_bn.cpp
+++ b/src/lib/pubkey/ec_group/ec_inner_bn.cpp
@@ -177,9 +177,16 @@ bool EC_Mul2Table_Data_BN::mul2_vartime_x_mod_order_eq(const EC_Scalar_Data& v,
       return false;
    }
 
+   /*
+   * The trick used below doesn't work for curves with cofactors
+   */
+   if(m_group->has_cofactor()) {
+      return m_group->mod_order(pt.get_affine_x()) == bn_v.value();
+   }
+
    /*
    * Note we're working with the projective coordinate directly here!
-   * Nominally we're doing this:
+   * Nominally we're comparing v with the affine x coordinate.
    *
    * return m_group->mod_order(pt.get_affine_x()) == bn_v.value();
    *
diff --git a/src/lib/utils/ct_utils.h b/src/lib/utils/ct_utils.h
index 32e06cb60..005ab7b40 100644
--- a/src/lib/utils/ct_utils.h
+++ b/src/lib/utils/ct_utils.h
@@ -71,22 +71,6 @@ constexpr inline void unpoison(const T* p, size_t n) {
    BOTAN_UNUSED(p, n);
 }
 
-/**
- * Checks whether CT::poison() and CT::unpoison() actually have an effect.
- *
- * If the build is not instrumented and/or not run using an analysis tool like
- * valgrind, the functions are no-ops and the return value is false.
- *
- * @returns true if CT::poison() and CT::unpoison() are effective
- */
-inline bool poison_has_effect() {
-#if defined(BOTAN_HAS_VALGRIND)
-   return RUNNING_ON_VALGRIND;
-#else
-   return false;
-#endif
-}
-
 /// @}
 
 /// @name Constant Time Check Annotation Convenience overloads
diff --git a/src/lib/utils/loadstor.h b/src/lib/utils/loadstor.h
index bf895ded2..b4f090e88 100644
--- a/src/lib/utils/loadstor.h
+++ b/src/lib/utils/loadstor.h
@@ -138,7 +138,6 @@ constexpr bool is_native(Endianness endianness) {
 #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN)
    return endianness == Endianness::Little;
 #else
-   BOTAN_UNUSED(endianness);
    return false;
 #endif
 }
@@ -153,7 +152,6 @@ constexpr bool is_opposite(Endianness endianness) {
 #elif defined(BOTAN_TARGET_CPU_IS_LITTLE_ENDIAN)
    return endianness == Endianness::Big;
 #else
-   BOTAN_UNUSED(endianness);
    return false;
 #endif
 }
diff --git a/src/scripts/ci/setup_gh_actions.sh b/src/scripts/ci/setup_gh_actions.sh
index 19fb7981c..8352d7dd6 100755
--- a/src/scripts/ci/setup_gh_actions.sh
+++ b/src/scripts/ci/setup_gh_actions.sh
@@ -51,36 +51,12 @@ if type -p "apt-get"; then
     elif [ "$TARGET" = "cross-arm64" ] || [ "$TARGET" = "cross-arm64-amalgamation" ]; then
         sudo apt-get -qq install qemu-user g++-aarch64-linux-gnu
 
-    elif [ "$TARGET" = "cross-ppc32" ]; then
-        sudo apt-get -qq install qemu-user g++-powerpc-linux-gnu
-
     elif [ "$TARGET" = "cross-ppc64" ]; then
         sudo apt-get -qq install qemu-user g++-powerpc64le-linux-gnu
 
-    elif [ "$TARGET" = "cross-sh4" ]; then
-        sudo apt-get -qq install qemu-user g++-sh4-linux-gnu
-
-    elif [ "$TARGET" = "cross-sparc64" ]; then
-        sudo apt-get -qq install qemu-user g++-sparc64-linux-gnu
-
-    elif [ "$TARGET" = "cross-m68k" ]; then
-        sudo apt-get -qq install qemu-user g++-m68k-linux-gnu
-
     elif [ "$TARGET" = "cross-riscv64" ]; then
         sudo apt-get -qq install qemu-user g++-riscv64-linux-gnu
 
-    elif [ "$TARGET" = "cross-alpha" ]; then
-        sudo apt-get -qq install qemu-user g++-alpha-linux-gnu
-
-    elif [ "$TARGET" = "cross-arc" ]; then
-        sudo apt-get -qq install qemu-user g++-arc-linux-gnu
-
-    elif [ "$TARGET" = "cross-hppa64" ]; then
-        sudo apt-get -qq install qemu-user g++-hppa-linux-gnu
-
-    elif [ "$TARGET" = "cross-mips" ]; then
-        sudo apt-get -qq install qemu-user g++-mips-linux-gnu
-
     elif [ "$TARGET" = "cross-mips64" ]; then
         sudo apt-get -qq install qemu-user g++-mips64-linux-gnuabi64
 
diff --git a/src/scripts/ci_build.py b/src/scripts/ci_build.py
index e95831b9f..5171fbaa8 100755
--- a/src/scripts/ci_build.py
+++ b/src/scripts/ci_build.py
@@ -33,7 +33,6 @@ def known_targets():
         'bsi',
         'codeql',
         'coverage',
-        'cross-alpha',
         'cross-android-arm32',
         'cross-android-arm64',
         'cross-android-arm64-amalgamation',
@@ -41,18 +40,13 @@ def known_targets():
         'cross-arm32-baremetal',
         'cross-arm64',
         'cross-arm64-amalgamation',
-        'cross-hppa64',
         'cross-i386',
         'cross-ios-arm64',
-        'cross-m68k',
-        'cross-mips',
         'cross-mips64',
         'cross-ppc32',
         'cross-ppc64',
-        'cross-riscv64',
         'cross-s390x',
-        'cross-sh4',
-        'cross-sparc64',
+        'cross-riscv64',
         'cross-win64',
         'docs',
         'emscripten',
@@ -121,8 +115,6 @@ def build_targets(target, target_os):
         yield 'bogo_shim'
     if target in ['examples']:
         yield 'examples'
-    if target in ['valgrind', 'valgrind-full']:
-        yield 'ct_selftest'
 
 def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                     root_dir, build_dir, test_results_dir, pkcs11_lib, use_gdb,
@@ -135,7 +127,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
 
     if target_os not in ['linux', 'osx', 'windows', 'freebsd']:
         print('Error unknown OS %s' % (target_os))
-        return (None, None, None, None)
+        return (None, None, None)
 
     if is_cross_target:
         if target_os == 'osx':
@@ -156,7 +148,6 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
 
     make_prefix = []
     test_prefix = []
-    pretest_cmd = []
     test_cmd = [os.path.join(build_dir, 'botan-test'),
                 '--data-dir=%s' % os.path.join(root_dir, 'src', 'tests', 'data'),
                 '--run-memory-intensive-tests']
@@ -249,8 +240,6 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                        '--show-reachable=yes',
                        '--track-origins=yes']
 
-        pretest_cmd = ['python3', os.path.join(root_dir, 'src', 'ct_selftest', 'ct_selftest.py'), os.path.join(build_dir, 'botan_ct_selftest')]
-
         # valgrind is single threaded anyway
         test_cmd += ['--test-threads=1']
 
@@ -355,32 +344,10 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                 flags += ['--cpu=aarch64']
                 cc_bin = 'aarch64-linux-gnu-g++'
                 test_prefix = ['qemu-aarch64', '-L', '/usr/aarch64-linux-gnu/']
-            elif target == 'cross-alpha':
-                flags += ['--cpu=alpha']
-                cc_bin = 'alpha-linux-gnu-g++'
-                test_prefix = ['qemu-alpha', '-L', '/usr/alpha-linux-gnu/']
-                flags += ['--without-stack-protector'] # not supported
-            elif target == 'cross-sh4':
-                flags += ['--cpu=sh4']
-                cc_bin = 'sh4-linux-gnu-g++'
-                test_prefix = ['qemu-sh4', '-L', '/usr/sh4-linux-gnu/']
-            elif target == 'cross-m68k':
-                flags += ['--cpu=m68k']
-                cc_bin = 'm68k-linux-gnu-g++'
-                test_prefix = ['qemu-m68k', '-L', '/usr/m68k-linux-gnu/']
-            elif target == 'cross-hppa64':
-                flags += ['--cpu=hppa']
-                cc_bin = 'hppa-linux-gnu-g++'
-                test_prefix = ['qemu-hppa', '-L', '/usr/hppa-linux-gnu/']
-            elif target == 'cross-sparc64':
-                flags += ['--cpu=sparc64']
-                cc_bin = 'sparc64-linux-gnu-g++'
-                test_prefix = ['qemu-sparc64', '-L', '/usr/sparc64-linux-gnu/']
             elif target == 'cross-ppc32':
                 flags += ['--cpu=ppc32']
                 cc_bin = 'powerpc-linux-gnu-g++'
                 test_prefix = ['qemu-ppc', '-L', '/usr/powerpc-linux-gnu/']
-                test_cmd = None # qemu crashes ...
             elif target == 'cross-ppc64':
                 flags += ['--cpu=ppc64', '--with-endian=little']
                 cc_bin = 'powerpc64le-linux-gnu-g++'
@@ -393,10 +360,6 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
                 flags += ['--cpu=s390x']
                 cc_bin = 's390x-linux-gnu-g++'
                 test_prefix = ['qemu-s390x', '-L', '/usr/s390x-linux-gnu/']
-            elif target == 'cross-mips':
-                flags += ['--cpu=mips32', '--with-endian=big']
-                cc_bin = 'mips-linux-gnu-g++'
-                test_prefix = ['qemu-mips', '-L', '/usr/mips-linux-gnu/']
             elif target == 'cross-mips64':
                 flags += ['--cpu=mips64', '--with-endian=big']
                 cc_bin = 'mips64-linux-gnuabi64-g++'
@@ -464,9 +427,6 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
 
     flags += ['--cc-bin=%s' % (cc_bin)]
 
-    if not pretest_cmd:
-        pretest_cmd = None
-
     if test_cmd is None:
         run_test_command = None
     else:
@@ -486,7 +446,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache,
         else:
             run_test_command = test_prefix + test_cmd
 
-    return flags, pretest_cmd, run_test_command, make_prefix
+    return flags, run_test_command, make_prefix
 
 def run_cmd(cmd, root_dir, build_dir):
     """
@@ -726,8 +686,7 @@ def main(args=None):
             'src/scripts/dev_tools/run_clang_tidy.py',
             'src/editors/vscode/scripts/bogo.py',
             'src/editors/vscode/scripts/common.py',
-            'src/editors/vscode/scripts/test.py',
-            'src/ct_selftest/ct_selftest.py']
+            'src/editors/vscode/scripts/test.py']
 
         # This has to run in the repository root to generate the correct
         # relative paths in the output. Otherwise GitHub Actions will not
@@ -744,7 +703,7 @@ def main(args=None):
         if options.test_results_dir:
             os.makedirs(options.test_results_dir)
 
-        config_flags, pretest_cmd, run_test_command, make_prefix = determine_flags(
+        config_flags, run_test_command, make_prefix = determine_flags(
             target, options.os, options.cpu, options.cc, options.cc_bin,
             options.compiler_cache, root_dir, build_dir, options.test_results_dir,
             options.pkcs11_lib, options.use_gdb, options.disable_werror,
@@ -776,9 +735,6 @@ def main(args=None):
             if target in ['examples']:
                 make_targets += ['examples']
 
-            if target in ['valgrind', 'valgrind-full']:
-                make_targets += ['ct_selftest']
-
             if target in ['coverage', 'sanitizer'] and options.os not in ['windows']:
                 make_targets += ['bogo_shim']
 
@@ -787,9 +743,6 @@ def main(args=None):
             if options.compiler_cache is not None:
                 cmds.append([options.compiler_cache, '--show-stats'])
 
-        if pretest_cmd is not None:
-            cmds.append(pretest_cmd)
-
         if run_test_command is not None:
             cmds.append(run_test_command)
 


Test patch:
diff --git a/src/ct_selftest/ct_selftest.cpp b/src/ct_selftest/ct_selftest.cpp
deleted file mode 100644
index 18beeaae4..000000000
--- a/src/ct_selftest/ct_selftest.cpp
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Basic tests for the CT::poison annotations.
- * Some of those are expected to fail, because they deliberately
- * branch on secret memory.
- *
- * (C) 2024 Jack Lloyd
- * (C) 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
- *
- * Botan is released under the Simplified BSD License (see license.txt)
- */
-
-#include <iostream>
-
-#include <botan/hex.h>
-#include <botan/system_rng.h>
-
-#include <botan/internal/ct_utils.h>
-#include <botan/internal/fmt.h>
-
-#include <functional>
-#include <map>
-
-namespace {
-
-void test_conditional_jump_on_poisoned_data(Botan::RandomNumberGenerator& rng) {
-   const uint8_t poisoned_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   // Performing a conditional jump on a "secret" value would introduce
-   // a potential side channel.
-   if(poisoned_byte == 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_array_access_on_poisoned_data(Botan::RandomNumberGenerator& rng) {
-   const uint8_t poisoned_byte = rng.next_byte();
-   const auto lookup_table = rng.random_array<1 << (sizeof(poisoned_byte) * 8)>();
-   Botan::CT::poison(poisoned_byte);
-
-   // Accessing memory with a "secret" value would introduce a potential side channel.
-   std::cout << lookup_table[poisoned_byte] << std::endl;
-}
-
-void test_conditional_jump_on_transitively_poisoned_data(Botan::RandomNumberGenerator& rng) {
-   const uint8_t poisoned_byte = rng.next_byte();
-   const uint8_t innocent_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   const uint8_t derived_byte = poisoned_byte ^ innocent_byte;
-
-   // Performing a conditional jump on a value that was calculated from a "secret" value
-   // would (in general) introduce a potential side channel.
-   if(derived_byte == 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_array_access_on_transitively_poisoned_data(Botan::RandomNumberGenerator& rng) {
-   const uint8_t poisoned_byte = rng.next_byte();
-   const uint8_t innocent_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   const uint8_t derived_byte = poisoned_byte ^ innocent_byte;
-   const auto lookup_table = rng.random_array<1 << (sizeof(derived_byte) * 8)>();
-
-   // Performing a memory access on a value that was calculated from a "secret" value
-   // would introduce a potential side channel.
-   std::cout << lookup_table[poisoned_byte] << std::endl;
-}
-
-void test_unpoisen_transitively_poisoned_data(Botan::RandomNumberGenerator& rng) {
-   const uint8_t poisoned_byte = rng.next_byte();
-   const uint8_t innocent_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   const uint8_t derived_byte = poisoned_byte ^ innocent_byte;
-   Botan::CT::unpoison(derived_byte);
-
-   // This is okay, because the value is not secret anymore.
-   if(derived_byte == 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_poisoned_and_cleared_data(Botan::RandomNumberGenerator& rng) {
-   const uint8_t poisoned_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   uint8_t derived_byte = poisoned_byte & 0x00;
-   derived_byte ^= rng.next_byte();  // To prevent the compiler from optimizing the whole thing away.
-
-   // This is okay, because the value was cleared (i.e. is not secret-dependent anymore)
-   if(derived_byte == 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_conditional_branch_on_unpoisoned_bit(Botan::RandomNumberGenerator& rng) {
-   const auto poisoned_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   // Clears the last poisoned bit (effectively unpoisoning it)
-   // All other bits are still poisoned (aka secret dependent)
-   uint8_t derived_byte = poisoned_byte & 0b11111110;
-
-   derived_byte ^= rng.next_byte();  // To prevent the compiler from optimizing the whole thing away.
-
-   // This conditional jump is okay, because the jump does not depend on the "secret" value.
-   if((derived_byte & 0b00000001) == 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_conditional_branch_on_poisoned_bit(Botan::RandomNumberGenerator& rng) {
-   const auto poisoned_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   // Clears all but one poisoned bit (effectively unpoisoning them)
-   // One bit remains poisoned (aka secret dependent)
-   uint8_t derived_byte = poisoned_byte & 0b00000010;
-
-   derived_byte ^= rng.next_byte() & 0b00000010;  // To prevent the compiler from optimizing the whole thing away.
-
-   // This conditional jump is not okay, because the jump depends on the "secret" value.
-   if((derived_byte & 0b00000010) == 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_clang_conditional_jump_on_bare_metal_ct_mask(Botan::RandomNumberGenerator& rng) {
-   const auto poisoned_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   std::array<uint8_t, 16> output_bytes;
-   std::memset(output_bytes.data(), 0x42, sizeof(output_bytes));
-
-   // This mimicks what went wrong in Kyber's secret message expansion
-   // that was found by PQShield in Kyber's reference implementation and
-   // was fixed in https://github.com/randombit/botan/pull/4107.
-   //
-   // Certain versions of Clang, namely 15, 16, 17 and 18 (maybe more) with
-   // specific optimization flags (-Os, -O1, -O2 -fno-vectorize, ...) do
-   // realize that `poisoned_mask` can only ever be all-zero or all-one and
-   // conditionally jump over the loop execution below.
-   //
-   // See: https://pqshield.com/pqshield-plugs-timing-leaks-in-kyber-ml-kem-to-improve-pqc-implementation-maturity/
-   const uint8_t poisoned_mask = -static_cast<uint8_t>(poisoned_byte & 1);
-   for(size_t i = 0; i < sizeof(output_bytes); ++i) {
-      output_bytes[i] &= poisoned_mask;
-   }
-
-   // Unpoison output_bytes to safely print them. The actual side channel
-   // happened above.
-   Botan::CT::unpoison(output_bytes);
-   std::cout << Botan::hex_encode(output_bytes) << std::endl;
-}
-
-void test_poison_range(Botan::RandomNumberGenerator& rng) {
-   auto range = rng.random_array<16>();
-   Botan::CT::poison(range);
-
-   // conditional jump on a "secret" value in a range
-   if((range.back() & 0xF0) != 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_unpoison_range(Botan::RandomNumberGenerator& rng) {
-   auto range = rng.random_array<16>();
-   Botan::CT::poison(range);
-
-   // "calculations" on poisoned memory are fine
-   std::vector<uint8_t> result;
-   std::copy(range.begin(), range.end(), std::back_inserter(result));
-
-   // unpoison the result range
-   Botan::CT::unpoison(result);
-
-   // the result range is not poisoned and conditional jumps should be fine
-   if((result.back() & 0xF0) != 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_scoped_poison_inner(Botan::RandomNumberGenerator& rng) {
-   const auto poisoned_byte = rng.next_byte();
-
-   const auto scope = Botan::CT::scoped_poison(poisoned_byte);
-
-   // conditional jump on a "secret" value in a range
-   if(poisoned_byte != 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-void test_scoped_poison_outer(Botan::RandomNumberGenerator& rng) {
-   auto poisoned_byte = rng.next_byte();
-
-   {
-      const auto scope = Botan::CT::scoped_poison(poisoned_byte);
-      poisoned_byte += rng.next_byte();
-   }
-
-   // conditional jump on a value that should be unpoisoned
-   // by the closing curly brace above
-   if(poisoned_byte != 0) {
-      std::cout << "I may or may not be printed." << std::endl;
-   }
-}
-
-// Similar to test_clang_conditional_jump_on_bare_metal_ct_mask() but with Botan's CT::Masks.
-// Should not fail, because CT::Mask uses CT::value_barrier().
-void regression_test_conditional_jump_in_ct_mask(Botan::RandomNumberGenerator& rng) {
-   const auto poisoned_byte = rng.next_byte();
-   Botan::CT::poison(poisoned_byte);
-
-   std::array<uint8_t, 16> output_bytes;
-   std::memset(output_bytes.data(), 0x42, sizeof(output_bytes));
-
-   // Before the introduction of CT::value_barrier, this did generate a
-   // conditional jump when compiled with clang using certain compiler
-   // optimizations. See the test case above for further details.
-   auto poisoned_mask = Botan::CT::Mask<uint8_t>::expand(poisoned_byte & 1);
-   for(size_t i = 0; i < sizeof(output_bytes); ++i) {
-      output_bytes[i] = poisoned_mask.select(output_bytes[i], 0);
-   }
-
-   Botan::CT::unpoison(output_bytes);
-   std::cout << Botan::hex_encode(output_bytes) << std::endl;
-}
-
-struct Test {
-      bool expect_failure;
-      bool needs_special_conditions;
-      std::function<void(Botan::RandomNumberGenerator&)> test;
-};
-
-constexpr bool SHOULD_FAIL = true;
-constexpr bool SHOULD_SUCCEED = false;
-
-/// Marks tests that don't produce the expected results without a special
-/// constellation of external conditions (e.g. a specific compiler version
-/// and/or specific optimization flags).
-constexpr bool REQUIRES_SPECIAL_CONDITIONS = true;
-
-/// Tests that should always expose the expected behavior, regardless of
-/// compiler or other external factors.
-constexpr bool IS_GENERIC = false;
-
-void print_help(std::string_view path) {
-   std::cerr << "Usage: valgrind [...] " << path << " [testname]" << std::endl;
-   std::cerr << "Usage: " << path << " [--list|--list-special|--help]" << std::endl;
-   std::cerr << "By design, this can only run one test at a time. "
-             << "Some tests are expected to cause CT::poison warnings and crashes." << std::endl;
-}
-
-void list_tests(const std::map<std::string, Test>& tests) {
-   std::cout << "fail?\tspecial?\ttest name\n\n";
-
-   auto str = [](bool value) { return value ? "true" : "false"; };
-
-   for(const auto& [name, test_info] : tests) {
-      std::cout << Botan::fmt(
-         "{}\t{}\t{}\n", str(test_info.expect_failure), str(test_info.needs_special_conditions), name);
-   }
-}
-
-}  // namespace
-
-int main(int argc, char* argv[]) {
-   // clang-format off
-   const std::map<std::string, Test> available_tests = {
-      {"poisoned_conditional_jump",            {SHOULD_FAIL,    IS_GENERIC,                  test_conditional_jump_on_poisoned_data}},
-      {"poisoned_memory_lookup",               {SHOULD_FAIL,    IS_GENERIC,                  test_array_access_on_poisoned_data}},
-      {"transitive_poisoned_conditional_jump", {SHOULD_FAIL,    IS_GENERIC,                  test_conditional_jump_on_transitively_poisoned_data}},
-      {"transitive_poisoned_memory_lookup",    {SHOULD_FAIL,    IS_GENERIC,                  test_array_access_on_transitively_poisoned_data}},
-      {"unpoison_transitive_poisoned",         {SHOULD_SUCCEED, IS_GENERIC,                  test_unpoisen_transitively_poisoned_data}},
-      {"poisoned_and_cleared",                 {SHOULD_SUCCEED, IS_GENERIC,                  test_poisoned_and_cleared_data}},
-      {"conditional_jump_on_unpoisoned_bit",   {SHOULD_SUCCEED, IS_GENERIC,                  test_conditional_branch_on_unpoisoned_bit}},
-      {"conditional_jump_on_poisoned_bit",     {SHOULD_FAIL,    IS_GENERIC,                  test_conditional_branch_on_poisoned_bit}},
-      {"regression_test_clang_vs_ct_mask",     {SHOULD_SUCCEED, IS_GENERIC,                  regression_test_conditional_jump_in_ct_mask}},
-      {"poison_range",                         {SHOULD_FAIL,    IS_GENERIC,                  test_poison_range}},
-      {"unpoison_range",                       {SHOULD_SUCCEED, IS_GENERIC,                  test_unpoison_range}},
-      {"scoped_poison_inner",                  {SHOULD_FAIL,    IS_GENERIC,                  test_scoped_poison_inner}},
-      {"scoped_poison_outer",                  {SHOULD_SUCCEED, IS_GENERIC,                  test_scoped_poison_outer}},
-      {"clang_vs_bare_metal_ct_mask",          {SHOULD_FAIL,    REQUIRES_SPECIAL_CONDITIONS, test_clang_conditional_jump_on_bare_metal_ct_mask}},
-   };
-   // clang-format on
-
-   if(argc != 2) {
-      print_help(argv[0]);
-      return 1;
-   }
-
-   const std::string argument(argv[1]);
-   if(argument == "--help") {
-      print_help(argv[0]);
-      return 0;
-   }
-
-   if(argument == "--list") {
-      list_tests(available_tests);
-      return 0;
-   }
-
-   const auto test = available_tests.find(argument);
-   if(test == available_tests.end()) {
-      std::cerr << "Unknown test: " << argument << std::endl;
-      return 1;
-   }
-
-#if !defined(BOTAN_CT_POISON_ENABLED)
-   std::cout << "The CT::poison API is disabled in this build, this test won't do anything useful\n"
-             << "Configure with a compatible checker (e.g. --with-valgrind) to make the magic happen." << std::endl;
-   return 1;
-#else
-   if(!Botan::CT::poison_has_effect()) {
-      std::cerr << "This test must run with a tool populating the CT::poison (e.g. valgrind)." << std::endl;
-      return 1;
-   }
-
-   try {
-      test->second.test(Botan::system_rng());
-   } catch(const std::exception& ex) {
-      std::cerr << "Caught exception: " << ex.what() << std::endl;
-      return 1;
-   }
-
-   return 0;
-#endif
-}
diff --git a/src/ct_selftest/ct_selftest.py b/src/ct_selftest/ct_selftest.py
deleted file mode 100755
index 5ce1d04d3..000000000
--- a/src/ct_selftest/ct_selftest.py
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env python3
-
-"""
-Runs all tests inmplemented in `ct_selftest`
-
-(C) 2024 Jack Lloyd
-    2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
-
-Botan is released under the Simplified BSD License (see license.txt)
-"""
-
-import subprocess
-import argparse
-import os
-import sys
-from typing import Self
-from enum import StrEnum, auto
-import json
-
-def run_command(cmd: list[str], is_text = True):
-    """ Run the command . """
-    return subprocess.run(cmd, capture_output=True, text=is_text, check=False)
-
-def run_with_valgrind(cmd: list[str]):
-    """ Run a command with valgrind. """
-    valgrind_args = ['valgrind',
-                     '-v',
-                     '--error-exitcode=2',
-                     '--leak-check=full',
-                     '--show-reachable=yes',
-                     '--track-origins=yes']
-    res = run_command(valgrind_args + cmd, is_text=False)
-    # valgrind may output non-utf-8 characters
-    res.stdout = res.stdout.decode("utf-8", errors="replace")
-    res.stderr = res.stderr.decode("utf-8", errors="replace")
-    return res
-
-class ValgrindTest:
-    """ A single test from ct_selftest """
-
-    class Status(StrEnum):
-        """ Defines the test result status """
-        OK = auto()
-        WARNING = auto()
-        ERROR = auto()
-        SKIP = auto()
-        UNKNOWN = auto()
-
-    def __init__(self, name, expect_failure, needs_special_config):
-        self.name = name
-        self.expect_failure = expect_failure
-        self.needs_special_config = needs_special_config
-        self.status = self.Status.UNKNOWN
-        self.stdout = None
-        self.stderr = None
-
-    @staticmethod
-    def from_line(line: str):
-        info = line.split("\t")
-        assert len(info) == 3
-        return ValgrindTest(name=info[2],
-                            expect_failure=info[0] == "true",
-                            needs_special_config=info[1] == "true")
-
-    def runnable(self, build_cfg):
-        """ Decide whether or not to run this test given build config info """
-        if not self.needs_special_config:
-            return True
-
-        if not build_cfg:
-            return False  # test has special build requirements, but we have no info
-
-        if self.name == "clang_vs_bare_metal_ct_mask":
-            return build_cfg["cc_macro"] == "CLANG" and "-Os" in build_cfg["cc_compile_flags"]
-
-        raise LookupError(f"Unknown special config test '{self.name}'")
-
-    def run(self, exe_path: str, build_config):
-        """ Run the test and return whether it succeeded """
-
-        if not self.runnable(build_config):
-            self.status = self.Status.SKIP
-        else:
-            result = run_with_valgrind([exe_path, self.name])
-            self.stdout = result.stdout
-            self.stderr = result.stderr
-
-            if result.returncode == 1:
-                # The ct_selftest program reported an error
-                self.status = self.Status.WARNING
-            else:
-                failed = result.returncode != 0
-                if failed != self.expect_failure:
-                    # Valgrind reported an error (or not), but we expected the opposite
-                    self.status = self.Status.ERROR
-                else:
-                    # Everything behaved as expected
-                    self.status = self.Status.OK
-
-        return self.status
-
-    @staticmethod
-    def read_test_list(ct_selftest_test_list: str) -> list[Self]:
-        """ Read the list of tests from the output of `ct_selftest --list`. """
-
-        return [ValgrindTest.from_line(line) for line in ct_selftest_test_list.split("\n")[2:] if line]
-
-
-def main(): # pylint: disable=missing-function-docstring
-    parser = argparse.ArgumentParser("ct_selftests")
-    parser.add_argument("ct_selftest_path", help="Path to the ct_selftest executable")
-    parser.add_argument("--build-config-path", help="Path to Botan's build-config.json file", default="")
-
-    args = parser.parse_args()
-
-    # Check if the path is valid
-    if not os.path.isfile(args.ct_selftest_path):
-        raise FileNotFoundError(f"Invalid path: {args.ct_selftest_path}")
-
-    def find_test_list():
-        test_list_result = run_command([args.ct_selftest_path, "--list"])
-        if test_list_result.returncode != 0:
-            raise RuntimeError("Failed to collect the test list from ct_selftest")
-        return ValgrindTest.read_test_list(test_list_result.stdout)
-
-    def open_build_config(build_config_path):
-        if not build_config_path:
-            return None
-
-        if not os.path.isfile(build_config_path):
-            raise FileNotFoundError(f"Invalid path: {build_config_path}")
-
-        with open(build_config_path, encoding="utf-8") as build_info_file:
-            return json.load(build_info_file)
-
-    test_list = find_test_list()
-    build_config = open_build_config(args.build_config_path)
-
-    for test in test_list:
-        print(f"running {test.name}... ", end="", flush=True)
-        print(test.run(args.ct_selftest_path, build_config))
-        if test.status not in [ValgrindTest.Status.OK, ValgrindTest.Status.SKIP]:
-            if test.stdout:
-                print("stdout:")
-                print(test.stdout)
-            if test.stderr:
-                print("stderr:")
-                print(test.stderr)
-
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/src/tests/test_ct_utils.cpp b/src/tests/test_ct_utils.cpp
index f6943adea..ca6bd7e73 100644
--- a/src/tests/test_ct_utils.cpp
+++ b/src/tests/test_ct_utils.cpp
@@ -177,80 +177,4 @@ class CT_Option_Tests final : public Test {
 
 BOTAN_REGISTER_TEST("ct_utils", "ct_option", CT_Option_Tests);
 
-namespace {
-
-template <typename T = void>
-struct Poisonable {
-      mutable bool poisoned = false;  // NOLINT(misc-non-private-member-variables-in-classes)
-
-      void _const_time_poison() const { poisoned = true; }
-
-      void _const_time_unpoison() const { poisoned = false; }
-};
-
-std::vector<Test::Result> test_higher_level_ct_poison() {
-   return {
-      CHECK("custom poisonable object",
-            [](Test::Result& result) {
-               Poisonable p;
-               result.confirm("not poisoned", p.poisoned == false);
-               Botan::CT::poison(p);
-               result.confirm("poisoned", p.poisoned == true);
-               Botan::CT::unpoison(p);
-               result.confirm("unpoisoned", p.poisoned == false);
-            }),
-
-      CHECK("poison multiple objects",
-            [](Test::Result& result) {
-               // template is useless, but p1, p2, and p3 are different types and we
-               // want to make sure that poison_all/unpoison_all can deal with that.
-               Poisonable<int> p1;
-               Poisonable<double> p2;
-               Poisonable<std::string> p3;
-
-               result.confirm("all not poisoned", !p1.poisoned && !p2.poisoned && !p3.poisoned);
-               Botan::CT::poison_all(p1, p2, p3);
-               result.confirm("all poisoned", p1.poisoned && p2.poisoned && p3.poisoned);
-               Botan::CT::unpoison_all(p1, p2, p3);
-               result.confirm("all unpoisoned", !p1.poisoned && !p2.poisoned && !p3.poisoned);
-            }),
-
-      CHECK("scoped poison",
-            [](Test::Result& result) {
-               // template is useless, but p1, p2, and p3 are different types and we
-               // want to make sure that poison_all/unpoison_all can deal with that.
-               Poisonable<int> p1;
-               Poisonable<double> p2;
-               Poisonable<std::string> p3;
-
-               result.confirm("not poisoned", !p1.poisoned && !p2.poisoned, !p3.poisoned);
-
-               {
-                  auto scope = Botan::CT::scoped_poison(p1, p2, p3);
-                  result.confirm("poisoned", p1.poisoned && p2.poisoned && p3.poisoned);
-               }
-
-               result.confirm("unpoisoned", !p1.poisoned && !p2.poisoned && !p3.poisoned);
-            }),
-
-      CHECK("poison a range of poisonable objects",
-            [](Test::Result& result) {
-               auto is_poisoned = [](const auto& p) { return p.poisoned; };
-
-               std::vector<Poisonable<>> v(10);
-               result.confirm("none poisoned", std::none_of(v.begin(), v.end(), is_poisoned));
-
-               Botan::CT::poison_range(v);
-               result.confirm("all poisoned", std::all_of(v.begin(), v.end(), is_poisoned));
-
-               Botan::CT::unpoison_range(v);
-               result.confirm("all unpoisoned", std::none_of(v.begin(), v.end(), is_poisoned));
-            }),
-   };
-}
-
-}  // namespace
-
-BOTAN_REGISTER_TEST_FN("ct_utils", "ct_poison", test_higher_level_ct_poison);
-
 }  // namespace Botan_Tests
diff --git a/src/tests/test_pcurves.cpp b/src/tests/test_pcurves.cpp
index f07d9df3d..16fb98123 100644
--- a/src/tests/test_pcurves.cpp
+++ b/src/tests/test_pcurves.cpp
@@ -44,7 +44,7 @@ class Pcurve_Basemul_Tests final : public Text_Based_Test {
                result.test_eq("mul correct", pt4, P_bytes);
 
                auto pt5 = curve->mul(g, scalar.value(), null_rng).to_affine().serialize();
-               result.test_eq("mul correct (Null_RNG)", pt5, P_bytes);
+               result.test_eq("mul correct", pt5, P_bytes);
             } else {
                result.test_failure("Curve rejected scalar input");
             }