diff --git a/crypto/ec/ec_asn1.cc b/crypto/ec/ec_asn1.cc
index ce81580..58f379c 100644
--- a/crypto/ec/ec_asn1.cc
+++ b/crypto/ec/ec_asn1.cc
@@ -19,6 +19,7 @@
 
 #include <openssl/bn.h>
 #include <openssl/bytestring.h>
+#include <openssl/ec_key.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
 #include <openssl/nid.h>
diff --git a/crypto/ec/ec_derive.cc b/crypto/ec/ec_derive.cc
index 8bea9c9..2d88ac6 100644
--- a/crypto/ec/ec_derive.cc
+++ b/crypto/ec/ec_derive.cc
@@ -31,7 +31,7 @@
   const char *name = EC_curve_nid2nist(EC_GROUP_get_curve_name(group));
   if (name == NULL || strlen(name) > EC_KEY_DERIVE_MAX_NAME_LEN) {
     OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
-    return NULL;
+    return nullptr;
   }
 
   // Assemble a label string to provide some key separation in case |secret| is
@@ -51,7 +51,7 @@
     // (The actual bound is a bit tighter, but our curves are much larger than
     // 128-bit.)
     OPENSSL_PUT_ERROR(EC, ERR_R_INTERNAL_ERROR);
-    return NULL;
+    return nullptr;
   }
 
   uint8_t derived[EC_KEY_DERIVE_EXTRA_BYTES + EC_MAX_BYTES];
@@ -59,38 +59,35 @@
       BN_num_bytes(EC_GROUP_get0_order(group)) + EC_KEY_DERIVE_EXTRA_BYTES;
   assert(derived_len <= sizeof(derived));
   if (!HKDF(derived, derived_len, EVP_sha256(), secret, secret_len,
-            /*salt=*/NULL, /*salt_len=*/0, (const uint8_t *)info,
+            /*salt=*/nullptr, /*salt_len=*/0, (const uint8_t *)info,
             strlen(info))) {
-    return NULL;
+    return nullptr;
   }
 
-  EC_KEY *key = EC_KEY_new();
-  BN_CTX *ctx = BN_CTX_new();
-  BIGNUM *priv = BN_bin2bn(derived, derived_len, NULL);
-  EC_POINT *pub = EC_POINT_new(group);
-  if (key == NULL || ctx == NULL || priv == NULL || pub == NULL ||
+  bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  bssl::UniquePtr<BIGNUM> priv(BN_bin2bn(derived, derived_len, nullptr));
+  bssl::UniquePtr<EC_POINT> pub(EC_POINT_new(group));
+  if (key == nullptr || ctx == nullptr || priv == nullptr || pub == nullptr ||
       // Reduce |priv| with Montgomery reduction. First, convert "from"
       // Montgomery form to compute |priv| * R^-1 mod |order|. This requires
       // |priv| be under order * R, which is true if the group order is large
       // enough. 2^(num_bytes(order)) < 2^8 * order, so:
       //
       //    priv < 2^8 * order * 2^128 < order * order < order * R
-      !BN_from_montgomery(priv, priv, &group->order, ctx) ||
+      !BN_from_montgomery(priv.get(), priv.get(), &group->order, ctx.get()) ||
       // Multiply by R^2 and do another Montgomery reduction to compute
       // priv * R^-1 * R^2 * R^-1 = priv mod order.
-      !BN_to_montgomery(priv, priv, &group->order, ctx) ||
-      !EC_POINT_mul(group, pub, priv, NULL, NULL, ctx) ||
-      !EC_KEY_set_group(key, group) || !EC_KEY_set_public_key(key, pub) ||
-      !EC_KEY_set_private_key(key, priv)) {
-    EC_KEY_free(key);
-    key = NULL;
-    goto err;
+      !BN_to_montgomery(priv.get(), priv.get(), &group->order, ctx.get()) ||
+      !EC_POINT_mul(group, pub.get(), priv.get(), nullptr, nullptr,
+                    ctx.get()) ||
+      !EC_KEY_set_group(key.get(), group) ||
+      !EC_KEY_set_public_key(key.get(), pub.get()) ||
+      !EC_KEY_set_private_key(key.get(), priv.get())) {
+    OPENSSL_cleanse(derived, sizeof(derived));
+    return nullptr;
   }
 
-err:
   OPENSSL_cleanse(derived, sizeof(derived));
-  BN_CTX_free(ctx);
-  BN_free(priv);
-  EC_POINT_free(pub);
-  return key;
+  return key.release();
 }
diff --git a/crypto/ecdsa/ecdsa_p1363_test.cc b/crypto/ecdsa/ecdsa_p1363_test.cc
index 0600115..d8f1a32 100644
--- a/crypto/ecdsa/ecdsa_p1363_test.cc
+++ b/crypto/ecdsa/ecdsa_p1363_test.cc
@@ -14,7 +14,6 @@
 
 #include <stdio.h>
 
-#include <utility>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -27,7 +26,6 @@
 #include <openssl/rand.h>
 
 #include "../test/file_test.h"
-#include "../test/test_util.h"
 #include "../test/wycheproof_util.h"
 
 
diff --git a/crypto/ex_data.cc b/crypto/ex_data.cc
index 825c35f..cb8a681 100644
--- a/crypto/ex_data.cc
+++ b/crypto/ex_data.cc
@@ -27,8 +27,6 @@
 #include "internal.h"
 
 
-DEFINE_STACK_OF(CRYPTO_EX_DATA_FUNCS)
-
 struct crypto_ex_data_func_st {
   long argl;   // Arbitary long
   void *argp;  // Arbitary void pointer
diff --git a/crypto/fipsmodule/bn/ctx.cc.inc b/crypto/fipsmodule/bn/ctx.cc.inc
index 7cc7c9b..a862ac2 100644
--- a/crypto/fipsmodule/bn/ctx.cc.inc
+++ b/crypto/fipsmodule/bn/ctx.cc.inc
@@ -17,175 +17,91 @@
 #include <assert.h>
 #include <string.h>
 
+#include <utility>
+
 #include <openssl/err.h>
 #include <openssl/mem.h>
 
-#include "../../internal.h"
+#include "../../mem_internal.h"
 
 
-// The stack frame info is resizing, set a first-time expansion size;
-#define BN_CTX_START_FRAMES 32
-
-
-// BN_STACK
-
-// A |BN_STACK| is a stack of |size_t| values.
-typedef struct {
-  // Array of indexes into |ctx->bignums|.
-  size_t *indexes;
-  // Number of stack frames, and the size of the allocated array
-  size_t depth, size;
-} BN_STACK;
-
-static void BN_STACK_init(BN_STACK *);
-static void BN_STACK_cleanup(BN_STACK *);
-static int BN_STACK_push(BN_STACK *, size_t idx);
-static size_t BN_STACK_pop(BN_STACK *);
-
-
-// BN_CTX
-
-DEFINE_STACK_OF(BIGNUM)
-
-// The opaque BN_CTX type
 struct bignum_ctx {
-  // bignums is the stack of |BIGNUM|s managed by this |BN_CTX|.
-  STACK_OF(BIGNUM) *bignums;
-  // stack is the stack of |BN_CTX_start| frames. It is the value of |used| at
+  ~bignum_ctx() {
+    // All |BN_CTX_start| calls must be matched with |BN_CTX_end|, otherwise the
+    // function may use more memory than expected, potentially without bound if
+    // done in a loop. Assert that all |BIGNUM|s have been released.
+    assert(used_ == 0 || error_);
+  }
+
+  // bignums_ is the stack of |BIGNUM|s managed by this |BN_CTX|.
+  bssl::Vector<bssl::UniquePtr<BIGNUM>> bignums_;
+  // stack_ is the stack of |BN_CTX_start| frames. It is the value of |used_| at
   // the time |BN_CTX_start| was called.
-  BN_STACK stack;
-  // used is the number of |BIGNUM|s from |bignums| that have been used.
-  size_t used;
-  // error is one if any operation on this |BN_CTX| failed. All subsequent
+  bssl::Vector<size_t> stack_;
+  // used_ is the number of |BIGNUM|s from |bignums_| that have been used.
+  size_t used_ = 0;
+  // error_ is whether any operation on this |BN_CTX| failed. All subsequent
   // operations will fail.
-  char error;
-  // defer_error is one if an operation on this |BN_CTX| has failed, but no
+  bool error_ = false;
+  // defer_error_ is whether an operation on this |BN_CTX| has failed, but no
   // error has been pushed to the queue yet. This is used to defer errors from
   // |BN_CTX_start| to |BN_CTX_get|.
-  char defer_error;
-};
+  bool defer_error_ = false;
+} /* BN_CTX */;
 
-BN_CTX *BN_CTX_new(void) {
-  BN_CTX *ret = reinterpret_cast<BN_CTX *>(OPENSSL_malloc(sizeof(BN_CTX)));
-  if (!ret) {
-    return NULL;
-  }
+BN_CTX *BN_CTX_new(void) { return bssl::New<BN_CTX>(); }
 
-  // Initialise the structure
-  ret->bignums = NULL;
-  BN_STACK_init(&ret->stack);
-  ret->used = 0;
-  ret->error = 0;
-  ret->defer_error = 0;
-  return ret;
-}
-
-void BN_CTX_free(BN_CTX *ctx) {
-  // All |BN_CTX_start| calls must be matched with |BN_CTX_end|, otherwise the
-  // function may use more memory than expected, potentially without bound if
-  // done in a loop. Assert that all |BIGNUM|s have been released.
-  if (ctx == nullptr) {
-    return;
-  }
-  assert(ctx->used == 0 || ctx->error);
-  sk_BIGNUM_pop_free(ctx->bignums, BN_free);
-  BN_STACK_cleanup(&ctx->stack);
-  OPENSSL_free(ctx);
-}
+void BN_CTX_free(BN_CTX *ctx) { bssl::Delete(ctx); }
 
 void BN_CTX_start(BN_CTX *ctx) {
-  if (ctx->error) {
+  if (ctx->error_) {
     // Once an operation has failed, |ctx->stack| no longer matches the number
     // of |BN_CTX_end| calls to come. Do nothing.
     return;
   }
 
-  if (!BN_STACK_push(&ctx->stack, ctx->used)) {
-    ctx->error = 1;
+  if (!ctx->stack_.Push(ctx->used_)) {
+    ctx->error_ = true;
     // |BN_CTX_start| cannot fail, so defer the error to |BN_CTX_get|.
-    ctx->defer_error = 1;
+    ctx->defer_error_ = true;
+    ERR_clear_error();
   }
 }
 
 BIGNUM *BN_CTX_get(BN_CTX *ctx) {
   // Once any operation has failed, they all do.
-  if (ctx->error) {
-    if (ctx->defer_error) {
+  if (ctx->error_) {
+    if (ctx->defer_error_) {
       OPENSSL_PUT_ERROR(BN, BN_R_TOO_MANY_TEMPORARY_VARIABLES);
-      ctx->defer_error = 0;
+      ctx->defer_error_ = false;
     }
-    return NULL;
+    return nullptr;
   }
 
-  if (ctx->bignums == NULL) {
-    ctx->bignums = sk_BIGNUM_new_null();
-    if (ctx->bignums == NULL) {
-      ctx->error = 1;
-      return NULL;
-    }
-  }
-
-  if (ctx->used == sk_BIGNUM_num(ctx->bignums)) {
-    BIGNUM *bn = BN_new();
-    if (bn == NULL || !sk_BIGNUM_push(ctx->bignums, bn)) {
+  if (ctx->used_ == ctx->bignums_.size()) {
+    bssl::UniquePtr<BIGNUM> bn(BN_new());
+    if (bn == nullptr || !ctx->bignums_.Push(std::move(bn))) {
       OPENSSL_PUT_ERROR(BN, BN_R_TOO_MANY_TEMPORARY_VARIABLES);
-      BN_free(bn);
-      ctx->error = 1;
-      return NULL;
+      ctx->error_ = true;
+      return nullptr;
     }
   }
 
-  BIGNUM *ret = sk_BIGNUM_value(ctx->bignums, ctx->used);
+  BIGNUM *ret = ctx->bignums_[ctx->used_].get();
   BN_zero(ret);
-  // This is bounded by |sk_BIGNUM_num|, so it cannot overflow.
-  ctx->used++;
+  // This is bounded by |ctx->bignums_.size()|, so it cannot overflow.
+  ctx->used_++;
   return ret;
 }
 
 void BN_CTX_end(BN_CTX *ctx) {
-  if (ctx->error) {
-    // Once an operation has failed, |ctx->stack| no longer matches the number
+  if (ctx->error_) {
+    // Once an operation has failed, |ctx->stack_| no longer matches the number
     // of |BN_CTX_end| calls to come. Do nothing.
     return;
   }
 
-  ctx->used = BN_STACK_pop(&ctx->stack);
-}
-
-
-// BN_STACK
-
-static void BN_STACK_init(BN_STACK *st) {
-  st->indexes = NULL;
-  st->depth = st->size = 0;
-}
-
-static void BN_STACK_cleanup(BN_STACK *st) { OPENSSL_free(st->indexes); }
-
-static int BN_STACK_push(BN_STACK *st, size_t idx) {
-  if (st->depth == st->size) {
-    // This function intentionally does not push to the error queue on error.
-    // Error-reporting is deferred to |BN_CTX_get|.
-    size_t new_size = st->size != 0 ? st->size * 3 / 2 : BN_CTX_START_FRAMES;
-    if (new_size <= st->size || new_size > SIZE_MAX / sizeof(size_t)) {
-      return 0;
-    }
-    size_t *new_indexes = reinterpret_cast<size_t *>(
-        OPENSSL_realloc(st->indexes, new_size * sizeof(size_t)));
-    if (new_indexes == NULL) {
-      return 0;
-    }
-    st->indexes = new_indexes;
-    st->size = new_size;
-  }
-
-  st->indexes[st->depth] = idx;
-  st->depth++;
-  return 1;
-}
-
-static size_t BN_STACK_pop(BN_STACK *st) {
-  assert(st->depth > 0);
-  st->depth--;
-  return st->indexes[st->depth];
+  assert(!ctx->stack_.empty());
+  ctx->used_ = ctx->stack_.back();
+  ctx->stack_.pop_back();
 }
diff --git a/crypto/fipsmodule/digest/md32_common.h b/crypto/fipsmodule/digest/md32_common.h
index b64f20b..9e237b2 100644
--- a/crypto/fipsmodule/digest/md32_common.h
+++ b/crypto/fipsmodule/digest/md32_common.h
@@ -16,14 +16,13 @@
 #define OPENSSL_HEADER_CRYPTO_FIPSMODULE_DIGEST_MD32_COMMON_H
 
 #include <openssl/base.h>
+#include <openssl/span.h>
 
 #include <assert.h>
 
 #include "../../internal.h"
 
-#if defined(__cplusplus)
-extern "C" {
-#endif
+BSSL_NAMESPACE_BEGIN
 
 
 // This is a generic 32-bit "collector" for message digest algorithms. It
@@ -48,70 +47,80 @@
 // |h| is the hash state and is updated by a function of type
 // |crypto_md32_block_func|. |data| is the partial unprocessed block and has
 // |num| bytes. |Nl| and |Nh| maintain the number of bits processed so far.
+//
+// The template parameter is then a traits struct defined as follows:
+//
+//     struct HashTraits {
+//       // HashContext is the hash type defined above.
+//       using HashContext = <NAME>_CTX;
+//
+//       // kBlockSize is the block size of the hash function.
+//       static constexpr size_t kBlockSize = <block size>;
+//
+//       // kLengthIsBigEndian determines whether the final length is encoded in
+//       // big or little endian.
+//       static constexpr bool kLengthIsBigEndian = ...;
+//
+//       // HashBlocks incorporates |num_blocks| blocks of input from |data|
+//       // into |state|. It is assumed the caller has sized |state| and |data|
+//       // for the hash function.
+//       static void HashBlocks(uint32_t *state, const uint8_t *data,
+//                              size_t num_blocks) {
+//         <name>_block_data_order(state, data, num_blocks);
+//       }
+//     };
+//
+// The reason for this formulation is to encourage the compiler to specialize
+// all the code for the block size and block function.
 
-// A crypto_md32_block_func should incorporate |num_blocks| of input from |data|
-// into |state|. It is assumed the caller has sized |state| and |data| for the
-// hash function.
-typedef void (*crypto_md32_block_func)(uint32_t *state, const uint8_t *data,
-                                       size_t num_blocks);
-
-// crypto_md32_update adds |len| bytes from |in| to the digest. |data| must be a
-// buffer of length |block_size| with the first |*num| bytes containing a
-// partial block. This function combines the partial block with |in| and
-// incorporates any complete blocks into the digest state |h|. It then updates
-// |data| and |*num| with the new partial block and updates |*Nh| and |*Nl| with
-// the data consumed.
-static inline void crypto_md32_update(crypto_md32_block_func block_func,
-                                      uint32_t *h, uint8_t *data,
-                                      size_t block_size, unsigned *num,
-                                      uint32_t *Nh, uint32_t *Nl,
-                                      const uint8_t *in, size_t len) {
-  if (len == 0) {
+// crypto_md32_update hashes |in| to |ctx|.
+template <typename Traits>
+inline void crypto_md32_update(typename Traits::HashContext *ctx,
+                               Span<const uint8_t> in) {
+  static_assert(Traits::kBlockSize == sizeof(ctx->data), "block size is wrong");
+  if (in.empty()) {
     return;
   }
 
-  uint32_t l = *Nl + (((uint32_t)len) << 3);
-  if (l < *Nl) {
+  uint32_t l = ctx->Nl + ((static_cast<uint32_t>(in.size())) << 3);
+  if (l < ctx->Nl) {
     // Handle carries.
-    (*Nh)++;
+    ctx->Nh++;
   }
-  *Nh += (uint32_t)(len >> 29);
-  *Nl = l;
+  ctx->Nh += static_cast<uint32_t>(in.size() >> 29);
+  ctx->Nl = l;
 
-  size_t n = *num;
+  size_t n = ctx->num;
   if (n != 0) {
-    if (len >= block_size || len + n >= block_size) {
-      OPENSSL_memcpy(data + n, in, block_size - n);
-      block_func(h, data, 1);
-      n = block_size - n;
-      in += n;
-      len -= n;
-      *num = 0;
+    if (in.size() >= Traits::kBlockSize ||
+        in.size() + n >= Traits::kBlockSize) {
+      OPENSSL_memcpy(ctx->data + n, in.data(), Traits::kBlockSize - n);
+      Traits::HashBlocks(ctx->h, ctx->data, 1);
+      in = in.subspan(Traits::kBlockSize - n);
+      ctx->num = 0;
       // Keep |data| zeroed when unused.
-      OPENSSL_memset(data, 0, block_size);
+      OPENSSL_memset(ctx->data, 0, Traits::kBlockSize);
     } else {
-      OPENSSL_memcpy(data + n, in, len);
-      *num += (unsigned)len;
+      OPENSSL_memcpy(ctx->data + n, in.data(), in.size());
+      ctx->num += static_cast<unsigned>(in.size());
       return;
     }
   }
 
-  n = len / block_size;
+  n = in.size() / Traits::kBlockSize;
   if (n > 0) {
-    block_func(h, in, n);
-    n *= block_size;
-    in += n;
-    len -= n;
+    Traits::HashBlocks(ctx->h, in.data(), n);
+    in = in.subspan(n * Traits::kBlockSize);
   }
 
-  if (len != 0) {
-    *num = (unsigned)len;
-    OPENSSL_memcpy(data, in, len);
+  if (!in.empty()) {
+    ctx->num = static_cast<unsigned>(in.size());
+    OPENSSL_memcpy(ctx->data, in.data(), in.size());
   }
 }
 
 // crypto_md32_final incorporates the partial block and trailing length into the
-// digest state |h|. The trailing length is encoded in little-endian if
+// digest state in |ctx|. The trailing length is encoded in little-endian if
 // |is_big_endian| is zero and big-endian otherwise. |data| must be a buffer of
 // length |block_size| with the first |*num| bytes containing a partial block.
 // |Nh| and |Nl| contain the total number of bits processed. On return, this
@@ -120,42 +129,38 @@
 //
 // This function does not serialize |h| into a final digest. This is the
 // responsibility of the caller.
-static inline void crypto_md32_final(crypto_md32_block_func block_func,
-                                     uint32_t *h, uint8_t *data,
-                                     size_t block_size, unsigned *num,
-                                     uint32_t Nh, uint32_t Nl,
-                                     int is_big_endian) {
+template <typename Traits>
+inline void crypto_md32_final(typename Traits::HashContext *ctx) {
+  static_assert(Traits::kBlockSize == sizeof(ctx->data), "block size is wrong");
   // |data| always has room for at least one byte. A full block would have
   // been consumed.
-  size_t n = *num;
-  assert(n < block_size);
-  data[n] = 0x80;
+  size_t n = ctx->num;
+  assert(n < Traits::kBlockSize);
+  ctx->data[n] = 0x80;
   n++;
 
   // Fill the block with zeros if there isn't room for a 64-bit length.
-  if (n > block_size - 8) {
-    OPENSSL_memset(data + n, 0, block_size - n);
+  if (n > Traits::kBlockSize - 8) {
+    OPENSSL_memset(ctx->data + n, 0, Traits::kBlockSize - n);
     n = 0;
-    block_func(h, data, 1);
+    Traits::HashBlocks(ctx->h, ctx->data, 1);
   }
-  OPENSSL_memset(data + n, 0, block_size - 8 - n);
+  OPENSSL_memset(ctx->data + n, 0, Traits::kBlockSize - 8 - n);
 
   // Append a 64-bit length to the block and process it.
-  if (is_big_endian) {
-    CRYPTO_store_u32_be(data + block_size - 8, Nh);
-    CRYPTO_store_u32_be(data + block_size - 4, Nl);
+  if constexpr (Traits::kLengthIsBigEndian) {
+    CRYPTO_store_u32_be(ctx->data + Traits::kBlockSize - 8, ctx->Nh);
+    CRYPTO_store_u32_be(ctx->data + Traits::kBlockSize - 4, ctx->Nl);
   } else {
-    CRYPTO_store_u32_le(data + block_size - 8, Nl);
-    CRYPTO_store_u32_le(data + block_size - 4, Nh);
+    CRYPTO_store_u32_le(ctx->data + Traits::kBlockSize - 8, ctx->Nl);
+    CRYPTO_store_u32_le(ctx->data + Traits::kBlockSize - 4, ctx->Nh);
   }
-  block_func(h, data, 1);
-  *num = 0;
-  OPENSSL_memset(data, 0, block_size);
+  Traits::HashBlocks(ctx->h, ctx->data, 1);
+  ctx->num = 0;
+  OPENSSL_memset(ctx->data, 0, Traits::kBlockSize);
 }
 
 
-#if defined(__cplusplus)
-}  // extern C
-#endif
+BSSL_NAMESPACE_END
 
 #endif  // OPENSSL_HEADER_CRYPTO_FIPSMODULE_DIGEST_MD32_COMMON_H
diff --git a/crypto/fipsmodule/sha/sha1.cc.inc b/crypto/fipsmodule/sha/sha1.cc.inc
index ca591fd..530ab93 100644
--- a/crypto/fipsmodule/sha/sha1.cc.inc
+++ b/crypto/fipsmodule/sha/sha1.cc.inc
@@ -15,6 +15,7 @@
 #include <string.h>
 
 #include <openssl/mem.h>
+#include <openssl/span.h>
 
 #include "../../internal.h"
 #include "../bcm_interface.h"
@@ -43,10 +44,21 @@
   return bcm_infallible::approved;
 }
 
+namespace {
+struct SHA1Traits {
+  using HashContext = SHA_CTX;
+  static constexpr size_t kBlockSize = BCM_SHA_CBLOCK;
+  static constexpr bool kLengthIsBigEndian = true;
+  static void HashBlocks(uint32_t *state, const uint8_t *data,
+                         size_t num_blocks) {
+    sha1_block_data_order(state, data, num_blocks);
+  }
+};
+}  // namespace
+
 bcm_infallible BCM_sha1_update(SHA_CTX *c, const void *data, size_t len) {
-  crypto_md32_update(&sha1_block_data_order, c->h, c->data, SHA_CBLOCK, &c->num,
-                     &c->Nh, &c->Nl, reinterpret_cast<const uint8_t *>(data),
-                     len);
+  bssl::crypto_md32_update<SHA1Traits>(
+      c, bssl::Span(static_cast<const uint8_t *>(data), len));
   return bcm_infallible::approved;
 }
 
@@ -60,9 +72,7 @@
 }
 
 bcm_infallible BCM_sha1_final(uint8_t out[SHA_DIGEST_LENGTH], SHA_CTX *c) {
-  crypto_md32_final(&sha1_block_data_order, c->h, c->data, SHA_CBLOCK, &c->num,
-                    c->Nh, c->Nl, /*is_big_endian=*/1);
-
+  bssl::crypto_md32_final<SHA1Traits>(c);
   sha1_output_state(out, c);
   FIPS_service_indicator_update_state();
   return bcm_infallible::approved;
diff --git a/crypto/fipsmodule/sha/sha256.cc.inc b/crypto/fipsmodule/sha/sha256.cc.inc
index 1ce4210..a1a22a9 100644
--- a/crypto/fipsmodule/sha/sha256.cc.inc
+++ b/crypto/fipsmodule/sha/sha256.cc.inc
@@ -15,6 +15,7 @@
 #include <string.h>
 
 #include <openssl/mem.h>
+#include <openssl/span.h>
 
 #include "../../internal.h"
 #include "../bcm_interface.h"
@@ -62,10 +63,21 @@
   return bcm_infallible::approved;
 }
 
+namespace {
+struct SHA256Traits {
+  using HashContext = SHA256_CTX;
+  static constexpr size_t kBlockSize = BCM_SHA256_CBLOCK;
+  static constexpr bool kLengthIsBigEndian = true;
+  static void HashBlocks(uint32_t *state, const uint8_t *data,
+                         size_t num_blocks) {
+    sha256_block_data_order(state, data, num_blocks);
+  }
+};
+}  // namespace
+
 bcm_infallible BCM_sha256_update(SHA256_CTX *c, const void *data, size_t len) {
-  crypto_md32_update(&sha256_block_data_order, c->h, c->data, BCM_SHA256_CBLOCK,
-                     &c->num, &c->Nh, &c->Nl,
-                     reinterpret_cast<const uint8_t *>(data), len);
+  bssl::crypto_md32_update<SHA256Traits>(
+      c, bssl::Span(static_cast<const uint8_t *>(data), len));
   return bcm_infallible::approved;
 }
 
@@ -75,8 +87,7 @@
 }
 
 static void sha256_final_impl(uint8_t *out, size_t md_len, SHA256_CTX *c) {
-  crypto_md32_final(&sha256_block_data_order, c->h, c->data, BCM_SHA256_CBLOCK,
-                    &c->num, c->Nh, c->Nl, /*is_big_endian=*/1);
+  bssl::crypto_md32_final<SHA256Traits>(c);
 
   BSSL_CHECK(md_len <= BCM_SHA256_DIGEST_LENGTH);
 
diff --git a/crypto/hrss/hrss.cc b/crypto/hrss/hrss.cc
index e035ff5..81807c2 100644
--- a/crypto/hrss/hrss.cc
+++ b/crypto/hrss/hrss.cc
@@ -275,7 +275,7 @@
 #endif  // (ARM || AARCH64) && NEON
 
 // Polynomials in this scheme have N terms.
-// #define N 701
+#define N HRSS_N
 
 // Underlying data types and arithmetic operations.
 // ------------------------------------------------
diff --git a/crypto/hrss/hrss_test.cc b/crypto/hrss/hrss_test.cc
index d921dc2..fd67e45 100644
--- a/crypto/hrss/hrss_test.cc
+++ b/crypto/hrss/hrss_test.cc
@@ -50,7 +50,7 @@
 
   p.s.v[0] = 0;
   p.a.v[0] = 1;
-  for (size_t i = 0; i < N - 1; i++) {
+  for (size_t i = 0; i < HRSS_N - 1; i++) {
     SCOPED_TRACE(i);
     poly3 r;
     OPENSSL_memset(&r, 0, sizeof(r));
@@ -485,9 +485,9 @@
     return;
   }
 
-  alignas(16) uint16_t r[N + 3];
-  alignas(16) uint16_t a[N + 3] = {0};
-  alignas(16) uint16_t b[N + 3] = {0};
+  alignas(16) uint16_t r[HRSS_N + 3];
+  alignas(16) uint16_t a[HRSS_N + 3] = {0};
+  alignas(16) uint16_t b[HRSS_N + 3] = {0};
 
   uint8_t kCanary[256];
   static_assert(sizeof(kCanary) % 32 == 0, "needed for alignment");
diff --git a/crypto/hrss/internal.h b/crypto/hrss/internal.h
index ab7ebc8..753a491 100644
--- a/crypto/hrss/internal.h
+++ b/crypto/hrss/internal.h
@@ -23,10 +23,10 @@
 #endif
 
 
-#define N 701
+#define HRSS_N 701
 #define BITS_PER_WORD (sizeof(crypto_word_t) * 8)
-#define WORDS_PER_POLY ((N + BITS_PER_WORD - 1) / BITS_PER_WORD)
-#define BITS_IN_LAST_WORD (N % BITS_PER_WORD)
+#define WORDS_PER_POLY ((HRSS_N + BITS_PER_WORD - 1) / BITS_PER_WORD)
+#define BITS_IN_LAST_WORD (HRSS_N % BITS_PER_WORD)
 
 struct poly2 {
   crypto_word_t v[WORDS_PER_POLY];
@@ -54,7 +54,8 @@
 // poly_Rq_mul is defined in assembly. Inputs and outputs must be 16-byte-
 // aligned.
 extern void poly_Rq_mul(
-    uint16_t r[N + 3], const uint16_t a[N + 3], const uint16_t b[N + 3],
+    uint16_t r[HRSS_N + 3], const uint16_t a[HRSS_N + 3],
+    const uint16_t b[HRSS_N + 3],
     // The following should be `scratch[POLY_MUL_RQ_SCRATCH_SPACE]` but
     // GCC 11.1 has a bug with unions that breaks that.
     uint8_t scratch[]);
diff --git a/crypto/md4/md4.cc b/crypto/md4/md4.cc
index ae6dea8..10a8130 100644
--- a/crypto/md4/md4.cc
+++ b/crypto/md4/md4.cc
@@ -17,6 +17,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <openssl/span.h>
+
 #include "../fipsmodule/digest/md32_common.h"
 #include "../internal.h"
 
@@ -48,17 +50,26 @@
   md4_block_data_order(c->h, data, 1);
 }
 
+namespace {
+struct MD4Traits {
+  using HashContext = MD4_CTX;
+  static constexpr size_t kBlockSize = MD4_CBLOCK;
+  static constexpr bool kLengthIsBigEndian = false;
+  static void HashBlocks(uint32_t *state, const uint8_t *data,
+                         size_t num_blocks) {
+    md4_block_data_order(state, data, num_blocks);
+  }
+};
+}  // namespace
+
 int MD4_Update(MD4_CTX *c, const void *data, size_t len) {
-  crypto_md32_update(&md4_block_data_order, c->h, c->data, MD4_CBLOCK, &c->num,
-                     &c->Nh, &c->Nl, reinterpret_cast<const uint8_t *>(data),
-                     len);
+  bssl::crypto_md32_update<MD4Traits>(
+      c, bssl::Span(static_cast<const uint8_t *>(data), len));
   return 1;
 }
 
 int MD4_Final(uint8_t out[MD4_DIGEST_LENGTH], MD4_CTX *c) {
-  crypto_md32_final(&md4_block_data_order, c->h, c->data, MD4_CBLOCK, &c->num,
-                    c->Nh, c->Nl, /*is_big_endian=*/0);
-
+  bssl::crypto_md32_final<MD4Traits>(c);
   CRYPTO_store_u32_le(out, c->h[0]);
   CRYPTO_store_u32_le(out + 4, c->h[1]);
   CRYPTO_store_u32_le(out + 8, c->h[2]);
diff --git a/crypto/md5/md5.cc b/crypto/md5/md5.cc
index 89444e9..3bd2a2b 100644
--- a/crypto/md5/md5.cc
+++ b/crypto/md5/md5.cc
@@ -17,6 +17,7 @@
 #include <string.h>
 
 #include <openssl/mem.h>
+#include <openssl/span.h>
 
 #include "../fipsmodule/digest/md32_common.h"
 #include "../internal.h"
@@ -52,17 +53,26 @@
   md5_block_data_order(c->h, data, 1);
 }
 
+namespace {
+struct MD5Traits {
+  using HashContext = MD5_CTX;
+  static constexpr size_t kBlockSize = MD5_CBLOCK;
+  static constexpr bool kLengthIsBigEndian = false;
+  static void HashBlocks(uint32_t *state, const uint8_t *data,
+                         size_t num_blocks) {
+    md5_block_data_order(state, data, num_blocks);
+  }
+};
+}  // namespace
+
 int MD5_Update(MD5_CTX *c, const void *data, size_t len) {
-  crypto_md32_update(&md5_block_data_order, c->h, c->data, MD5_CBLOCK, &c->num,
-                     &c->Nh, &c->Nl, reinterpret_cast<const uint8_t *>(data),
-                     len);
+  bssl::crypto_md32_update<MD5Traits>(
+      c, bssl::Span(static_cast<const uint8_t *>(data), len));
   return 1;
 }
 
 int MD5_Final(uint8_t out[MD5_DIGEST_LENGTH], MD5_CTX *c) {
-  crypto_md32_final(&md5_block_data_order, c->h, c->data, MD5_CBLOCK, &c->num,
-                    c->Nh, c->Nl, /*is_big_endian=*/0);
-
+  bssl::crypto_md32_final<MD5Traits>(c);
   CRYPTO_store_u32_le(out, c->h[0]);
   CRYPTO_store_u32_le(out + 4, c->h[1]);
   CRYPTO_store_u32_le(out + 8, c->h[2]);
diff --git a/crypto/mem_test.cc b/crypto/mem_test.cc
index 70b283c..96fbc68 100644
--- a/crypto/mem_test.cc
+++ b/crypto/mem_test.cc
@@ -12,6 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <openssl/mem.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
 #include <gtest/gtest.h>
 
 #include "mem_internal.h"
diff --git a/crypto/pem/pem_lib.cc b/crypto/pem/pem_lib.cc
index 7679aa0..e7c4e0b 100644
--- a/crypto/pem/pem_lib.cc
+++ b/crypto/pem/pem_lib.cc
@@ -21,6 +21,7 @@
 
 #include <openssl/base64.h>
 #include <openssl/buf.h>
+#include <openssl/cipher.h>
 #include <openssl/des.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index aa96a28..6159d40 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -33,6 +33,7 @@
 #include <openssl/nid.h>
 #include <openssl/pem.h>
 #include <openssl/pool.h>
+#include <openssl/span.h>
 #include <openssl/x509.h>
 
 #include "../internal.h"
diff --git a/crypto/x509/x_x509.cc b/crypto/x509/x_x509.cc
index 485231f..01b3569 100644
--- a/crypto/x509/x_x509.cc
+++ b/crypto/x509/x_x509.cc
@@ -16,6 +16,7 @@
 #include <limits.h>
 #include <stdio.h>
 
+#include <openssl/asn1.h>
 #include <openssl/asn1t.h>
 #include <openssl/bytestring.h>
 #include <openssl/evp.h>
diff --git a/decrepit/ripemd/ripemd.cc b/decrepit/ripemd/ripemd.cc
index 716a14e..dbbb2b3 100644
--- a/decrepit/ripemd/ripemd.cc
+++ b/decrepit/ripemd/ripemd.cc
@@ -44,18 +44,26 @@
   ripemd160_block_data_order(c->h, data, 1);
 }
 
+namespace {
+struct RIPEMD160Traits {
+  using HashContext = RIPEMD160_CTX;
+  static constexpr size_t kBlockSize = RIPEMD160_CBLOCK;
+  static constexpr bool kLengthIsBigEndian = false;
+  static void HashBlocks(uint32_t *state, const uint8_t *data,
+                         size_t num_blocks) {
+    ripemd160_block_data_order(state, data, num_blocks);
+  }
+};
+}  // namespace
+
 int RIPEMD160_Update(RIPEMD160_CTX *c, const void *data, size_t len) {
-  crypto_md32_update(&ripemd160_block_data_order, c->h, c->data,
-                     RIPEMD160_CBLOCK, &c->num, &c->Nh, &c->Nl,
-                     reinterpret_cast<const uint8_t *>(data), len);
+  bssl::crypto_md32_update<RIPEMD160Traits>(
+      c, bssl::Span(static_cast<const uint8_t *>(data), len));
   return 1;
 }
 
 int RIPEMD160_Final(uint8_t out[RIPEMD160_DIGEST_LENGTH], RIPEMD160_CTX *c) {
-  crypto_md32_final(&ripemd160_block_data_order, c->h, c->data,
-                    RIPEMD160_CBLOCK, &c->num, c->Nh, c->Nl,
-                    /*is_big_endian=*/0);
-
+  bssl::crypto_md32_final<RIPEMD160Traits>(c);
   CRYPTO_store_u32_le(out, c->h[0]);
   CRYPTO_store_u32_le(out + 4, c->h[1]);
   CRYPTO_store_u32_le(out + 8, c->h[2]);
diff --git a/pki/test_helpers.cc b/pki/test_helpers.cc
index 123aacb..3237614 100644
--- a/pki/test_helpers.cc
+++ b/pki/test_helpers.cc
@@ -31,6 +31,7 @@
 #include "../crypto/test/test_data.h"
 #include "cert_error_params.h"
 #include "cert_errors.h"
+#include "parse_values.h"
 #include "parser.h"
 #include "pem.h"
 #include "simple_path_builder_delegate.h"
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index cd6a25d..1b1a9b5 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -17,6 +17,7 @@
 
 #include <assert.h>
 
+#include <algorithm>
 #include <utility>
 
 #include <openssl/rand.h>
diff --git a/ssl/test/packeted_bio.cc b/ssl/test/packeted_bio.cc
index dfd8c91..b30699b 100644
--- a/ssl/test/packeted_bio.cc
+++ b/ssl/test/packeted_bio.cc
@@ -24,6 +24,7 @@
 #include <utility>
 #include <vector>
 
+#include <openssl/bio.h>
 #include <openssl/mem.h>
 
 #include "../../crypto/internal.h"
diff --git a/util/fipstools/delocate/delocate.go b/util/fipstools/delocate/delocate.go
index 0252781..ec1cd14 100644
--- a/util/fipstools/delocate/delocate.go
+++ b/util/fipstools/delocate/delocate.go
@@ -627,8 +627,10 @@
 						}
 						return statement, nil
 					} else if parts.pegRule == ruleLow12BitsSymbolRef {
-						if instructionName != "ldr" {
-							panic("Symbol reference outside of ldr instruction")
+						switch instructionName {
+						case "ldr", "ldrh", "ldrb", "ldrsw", "ldrsh", "ldrsb":
+						default:
+							panic("Symbol reference outside of load instruction")
 						}
 
 						// Suppress the offset; adrp loaded the full address. This assumes the
diff --git a/util/fipstools/delocate/testdata/aarch64-Basic/in.s b/util/fipstools/delocate/testdata/aarch64-Basic/in.s
index a2334f0..8aebf05 100644
--- a/util/fipstools/delocate/testdata/aarch64-Basic/in.s
+++ b/util/fipstools/delocate/testdata/aarch64-Basic/in.s
@@ -28,10 +28,24 @@
 	// Load from local symbol
 	adrp x10, .Llocal_data2
 	ldr q0, [x10, :lo12:.Llocal_data2]
+	ldr x0, [x10, :lo12:.Llocal_data2]
+	ldr w0, [x10, :lo12:.Llocal_data2]
+	ldrh w0, [x10, :lo12:.Llocal_data2]
+	ldrb w0, [x10, :lo12:.Llocal_data2]
+	ldrsw x0, [x10, :lo12:.Llocal_data2]
+	ldrsh w0, [x10, :lo12:.Llocal_data2]
+	ldrsb w0, [x10, :lo12:.Llocal_data2]
 
 	// Load from local symbol with offset
 	adrp x10, .Llocal_data2+16
 	ldr q0, [x10, :lo12:.Llocal_data2+16]
+	ldr x0, [x10, :lo12:.Llocal_data2+16]
+	ldr w0, [x10, :lo12:.Llocal_data2+16]
+	ldrh w0, [x10, :lo12:.Llocal_data2+16]
+	ldrb w0, [x10, :lo12:.Llocal_data2+16]
+	ldrsw x0, [x10, :lo12:.Llocal_data2+16]
+	ldrsh w0, [x10, :lo12:.Llocal_data2+16]
+	ldrsb w0, [x10, :lo12:.Llocal_data2+16]
 
 	bl local_function
 
diff --git a/util/fipstools/delocate/testdata/aarch64-Basic/out.s b/util/fipstools/delocate/testdata/aarch64-Basic/out.s
index 9ff57b5..9c5614b 100644
--- a/util/fipstools/delocate/testdata/aarch64-Basic/out.s
+++ b/util/fipstools/delocate/testdata/aarch64-Basic/out.s
@@ -58,12 +58,40 @@
 	adr x10, .Llocal_data2
 // WAS ldr q0, [x10, :lo12:.Llocal_data2]
 	ldr	q0, [x10]
+// WAS ldr x0, [x10, :lo12:.Llocal_data2]
+	ldr	x0, [x10]
+// WAS ldr w0, [x10, :lo12:.Llocal_data2]
+	ldr	w0, [x10]
+// WAS ldrh w0, [x10, :lo12:.Llocal_data2]
+	ldrh	w0, [x10]
+// WAS ldrb w0, [x10, :lo12:.Llocal_data2]
+	ldrb	w0, [x10]
+// WAS ldrsw x0, [x10, :lo12:.Llocal_data2]
+	ldrsw	x0, [x10]
+// WAS ldrsh w0, [x10, :lo12:.Llocal_data2]
+	ldrsh	w0, [x10]
+// WAS ldrsb w0, [x10, :lo12:.Llocal_data2]
+	ldrsb	w0, [x10]
 
 	// Load from local symbol with offset
 // WAS adrp x10, .Llocal_data2+16
 	adr x10, .Llocal_data2+16
 // WAS ldr q0, [x10, :lo12:.Llocal_data2+16]
 	ldr	q0, [x10]
+// WAS ldr x0, [x10, :lo12:.Llocal_data2+16]
+	ldr	x0, [x10]
+// WAS ldr w0, [x10, :lo12:.Llocal_data2+16]
+	ldr	w0, [x10]
+// WAS ldrh w0, [x10, :lo12:.Llocal_data2+16]
+	ldrh	w0, [x10]
+// WAS ldrb w0, [x10, :lo12:.Llocal_data2+16]
+	ldrb	w0, [x10]
+// WAS ldrsw x0, [x10, :lo12:.Llocal_data2+16]
+	ldrsw	x0, [x10]
+// WAS ldrsh w0, [x10, :lo12:.Llocal_data2+16]
+	ldrsh	w0, [x10]
+// WAS ldrsb w0, [x10, :lo12:.Llocal_data2+16]
+	ldrsb	w0, [x10]
 
 // WAS bl local_function
 	bl	.Llocal_function_local_target