diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json
index 6d8f767..ed376c9 100644
--- a/.bcr/metadata.template.json
+++ b/.bcr/metadata.template.json
@@ -7,14 +7,14 @@
       "name": "Adam Langley"
     },
     {
-      "email": "bbe@google.com",
-      "github": "bob-beck",
-      "name": "Bob Beck"
-    },
-    {
       "email": "davidben@google.com",
       "github": "davidben",
       "name": "David Benjamin"
+    },
+    {
+      "email": "dadrian@google.com",
+      "github": "dadrian",
+      "name": "David Adrian"
     }
   ],
   "repository": [
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82b19fc..4211db0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,6 +36,9 @@
 enable_language(C)
 enable_language(CXX)
 
+# TODO(crbug,com/389897612): Register tests with CTest. For now, we include this
+# only to pick up the standard |BUILD_TESTING| option.
+include(CTest)
 include(GNUInstallDirs)
 
 set(INSTALL_ENABLED 1)
@@ -476,33 +479,33 @@
   target_link_libraries(libcxx libcxxabi)
 endif()
 
-# Add minimal googletest targets. The provided one has many side-effects, and
-# googletest has a very straightforward build.
-add_library(
-  boringssl_gtest
-  third_party/googletest/googlemock/src/gmock-all.cc
-  third_party/googletest/googletest/src/gtest-all.cc
-)
-if(USE_CUSTOM_LIBCXX)
-  target_link_libraries(boringssl_gtest libcxx)
-endif()
-target_include_directories(
+if(BUILD_TESTING)
+  # Add minimal googletest targets. The provided one has many side-effects, and
+  # googletest has a very straightforward build.
+  add_library(
     boringssl_gtest
-    PUBLIC
-    third_party/googletest/googlemock/include
-    third_party/googletest/googletest/include
-    PRIVATE
-    third_party/googletest/googlemock
-    third_party/googletest/googletest
-)
+    third_party/googletest/googlemock/src/gmock-all.cc
+    third_party/googletest/googletest/src/gtest-all.cc
+  )
+  if(USE_CUSTOM_LIBCXX)
+    target_link_libraries(boringssl_gtest libcxx)
+  endif()
+  target_include_directories(
+      boringssl_gtest
+      PUBLIC
+      third_party/googletest/googlemock/include
+      third_party/googletest/googletest/include
+      PRIVATE
+      third_party/googletest/googlemock
+      third_party/googletest/googletest
+  )
 
-# Declare a dummy target to build all unit tests. Test targets should inject
-# themselves as dependencies next to the target definition.
-add_custom_target(all_tests)
+  # Declare a dummy target to build all unit tests. Test targets should inject
+  # themselves as dependencies next to the target definition.
+  add_custom_target(all_tests)
 
-add_subdirectory(ssl/test)
-add_subdirectory(util/fipstools)
-add_subdirectory(util/fipstools/acvp/modulewrapper)
+  add_subdirectory(ssl/test)
+endif()
 
 if(OPENSSL_ASM)
   set(CRYPTO_SOURCES_ASM_USED ${CRYPTO_SOURCES_ASM})
@@ -658,64 +661,62 @@
 add_library(decrepit ${DECREPIT_SOURCES})
 target_link_libraries(decrepit ssl crypto)
 
-add_library(test_support_lib STATIC
-            ${TEST_SUPPORT_SOURCES} ${TEST_SUPPORT_SOURCES_ASM_USED})
-if(LIBUNWIND_FOUND)
-  target_compile_options(test_support_lib PRIVATE ${LIBUNWIND_CFLAGS_OTHER})
-  target_include_directories(test_support_lib PRIVATE ${LIBUNWIND_INCLUDE_DIRS})
-  target_link_libraries(test_support_lib ${LIBUNWIND_LDFLAGS})
-endif()
-if(WIN32)
-  target_link_libraries(test_support_lib dbghelp)
-endif()
-target_link_libraries(test_support_lib boringssl_gtest crypto)
-
-# urandom_test is a separate binary because it needs to be able to observe the
-# PRNG initialisation, which means that it can't have other tests running before
-# it does.
-add_executable(urandom_test ${URANDOM_TEST_SOURCES})
-target_link_libraries(urandom_test test_support_lib boringssl_gtest crypto)
-add_dependencies(all_tests urandom_test)
-
-add_executable(crypto_test ${CRYPTO_TEST_SOURCES})
-target_link_libraries(crypto_test test_support_lib boringssl_gtest crypto)
-add_dependencies(all_tests crypto_test)
-
-add_executable(ssl_test ${SSL_TEST_SOURCES})
-target_link_libraries(ssl_test test_support_lib boringssl_gtest ssl crypto)
-add_dependencies(all_tests ssl_test)
-add_executable(decrepit_test ${DECREPIT_TEST_SOURCES})
-target_link_libraries(decrepit_test test_support_lib boringssl_gtest
-                      decrepit ssl crypto)
-add_dependencies(all_tests decrepit_test)
-
 if(APPLE)
   set(PKI_CXX_FLAGS "-fno-aligned-new")
 endif()
-
 add_library(pki ${PKI_SOURCES})
 target_link_libraries(pki crypto)
+target_compile_options(pki PRIVATE ${PKI_CXX_FLAGS})
 
-add_executable(pki_test ${PKI_TEST_SOURCES})
-target_link_libraries(pki_test test_support_lib boringssl_gtest pki crypto)
-add_dependencies(all_tests pki_test)
+if(BUILD_TESTING)
+  add_library(test_support_lib STATIC
+              ${TEST_SUPPORT_SOURCES} ${TEST_SUPPORT_SOURCES_ASM_USED})
+  if(LIBUNWIND_FOUND)
+    target_compile_options(test_support_lib PRIVATE ${LIBUNWIND_CFLAGS_OTHER})
+    target_include_directories(test_support_lib PRIVATE ${LIBUNWIND_INCLUDE_DIRS})
+    target_link_libraries(test_support_lib ${LIBUNWIND_LDFLAGS})
+  endif()
+  if(WIN32)
+    target_link_libraries(test_support_lib dbghelp)
+  endif()
+  target_link_libraries(test_support_lib boringssl_gtest crypto)
 
-set_target_properties(
-  pki pki_test
-  PROPERTIES
-  COMPILE_FLAGS "${PKI_CXX_FLAGS}")
+  # urandom_test is a separate binary because it needs to be able to observe the
+  # PRNG initialisation, which means that it can't have other tests running before
+  # it does.
+  add_executable(urandom_test ${URANDOM_TEST_SOURCES})
+  target_link_libraries(urandom_test test_support_lib boringssl_gtest crypto)
+  add_dependencies(all_tests urandom_test)
+
+  add_executable(crypto_test ${CRYPTO_TEST_SOURCES})
+  target_link_libraries(crypto_test test_support_lib boringssl_gtest crypto)
+  add_dependencies(all_tests crypto_test)
+
+  add_executable(ssl_test ${SSL_TEST_SOURCES})
+  target_link_libraries(ssl_test test_support_lib boringssl_gtest ssl crypto)
+  add_dependencies(all_tests ssl_test)
+  add_executable(decrepit_test ${DECREPIT_TEST_SOURCES})
+  target_link_libraries(decrepit_test test_support_lib boringssl_gtest
+                        decrepit ssl crypto)
+  add_dependencies(all_tests decrepit_test)
+
+  add_executable(pki_test ${PKI_TEST_SOURCES})
+  target_link_libraries(pki_test test_support_lib boringssl_gtest pki crypto)
+  target_compile_options(pki_test PRIVATE ${PKI_CXX_FLAGS})
+  add_dependencies(all_tests pki_test)
+
+  add_executable(test_fips util/fipstools/test_fips.cc)
+  target_link_libraries(test_fips crypto)
+endif()
+
+if(FIPS)
+  add_executable(modulewrapper ${MODULEWRAPPER_SOURCES})
+  target_link_libraries(modulewrapper crypto)
+endif()
 
 add_executable(bssl ${BSSL_SOURCES})
 target_link_libraries(bssl ssl crypto)
 
-# Historically, targets were built in subdirectories. For compatibility with
-# existing tools, we, for now, copy the targets into the subdirectories. This
-# will be removed sometime in 2024.
-copy_post_build(crypto crypto crypto_test urandom_test)
-copy_post_build(ssl ssl ssl_test)
-copy_post_build(decrepit decrepit decrepit_test)
-copy_post_build(tool bssl)
-
 if(FUZZ)
   if(LIBFUZZER_FROM_DEPS)
     file(GLOB LIBFUZZER_SOURCES "util/bot/libFuzzer/*.cpp")
@@ -744,56 +745,58 @@
   set(HANDSHAKER_ARGS "-handshaker-path" $<TARGET_FILE:handshaker>)
 endif()
 
-if(FIPS)
-  add_custom_target(
-    acvp_tests
-    COMMAND ${GO_EXECUTABLE} build -o ${CMAKE_CURRENT_BINARY_DIR}/acvptool
-            boringssl.googlesource.com/boringssl.git/util/fipstools/acvp/acvptool
-    COMMAND ${GO_EXECUTABLE} build -o ${CMAKE_CURRENT_BINARY_DIR}/testmodulewrapper
-            boringssl.googlesource.com/boringssl.git/util/fipstools/acvp/acvptool/testmodulewrapper
-    COMMAND cd util/fipstools/acvp/acvptool/test &&
-            ${GO_EXECUTABLE} run check_expected.go
-            -tool ${CMAKE_CURRENT_BINARY_DIR}/acvptool
-            -module-wrappers modulewrapper:$<TARGET_FILE:modulewrapper>,testmodulewrapper:${CMAKE_CURRENT_BINARY_DIR}/testmodulewrapper
-            -tests tests.json
-    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-    DEPENDS modulewrapper
-    USES_TERMINAL)
-
-  add_custom_target(
-    fips_specific_tests_if_any
-    DEPENDS acvp_tests
-  )
-else()
-  add_custom_target(fips_specific_tests_if_any)
-endif()
-
-file(STRINGS util/go_tests.txt GO_TESTS)
-set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
-             util/go_tests.txt)
-
-if(GO_EXECUTABLE)
-  add_custom_target(
-      run_tests
-      COMMAND ${CMAKE_COMMAND} -E echo "Running Go tests"
-      COMMAND ${GO_EXECUTABLE} test ${GO_TESTS}
-      COMMAND ${CMAKE_COMMAND} -E echo
-      COMMAND ${CMAKE_COMMAND} -E echo "Running unit tests"
-      COMMAND ${GO_EXECUTABLE} run util/all_tests.go -build-dir
-              ${CMAKE_CURRENT_BINARY_DIR}
-      COMMAND ${CMAKE_COMMAND} -E echo
-      COMMAND ${CMAKE_COMMAND} -E echo "Running SSL tests"
-      COMMAND cd ssl/test/runner &&
-              ${GO_EXECUTABLE} test -shim-path $<TARGET_FILE:bssl_shim>
-                ${HANDSHAKER_ARGS} ${RUNNER_ARGS}
+if(BUILD_TESTING)
+  if(FIPS)
+    add_custom_target(
+      acvp_tests
+      COMMAND ${GO_EXECUTABLE} build -o ${CMAKE_CURRENT_BINARY_DIR}/acvptool
+              boringssl.googlesource.com/boringssl.git/util/fipstools/acvp/acvptool
+      COMMAND ${GO_EXECUTABLE} build -o ${CMAKE_CURRENT_BINARY_DIR}/testmodulewrapper
+              boringssl.googlesource.com/boringssl.git/util/fipstools/acvp/acvptool/testmodulewrapper
+      COMMAND cd util/fipstools/acvp/acvptool/test &&
+              ${GO_EXECUTABLE} run check_expected.go
+              -tool ${CMAKE_CURRENT_BINARY_DIR}/acvptool
+              -module-wrappers modulewrapper:$<TARGET_FILE:modulewrapper>,testmodulewrapper:${CMAKE_CURRENT_BINARY_DIR}/testmodulewrapper
+              -tests tests.json
       WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-      DEPENDS all_tests bssl_shim handshaker fips_specific_tests_if_any
+      DEPENDS modulewrapper
       USES_TERMINAL)
-else()
-  add_custom_target(
-      run_tests
-      COMMAND ${CMAKE_COMMAND} -E echo "Running tests requires Go"
-      COMMAND ${CMAKE_COMMAND} -E false)
+
+    add_custom_target(
+      fips_specific_tests_if_any
+      DEPENDS acvp_tests
+    )
+  else()
+    add_custom_target(fips_specific_tests_if_any)
+  endif()
+
+  file(STRINGS util/go_tests.txt GO_TESTS)
+  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
+              util/go_tests.txt)
+
+  if(GO_EXECUTABLE)
+    add_custom_target(
+        run_tests
+        COMMAND ${CMAKE_COMMAND} -E echo "Running Go tests"
+        COMMAND ${GO_EXECUTABLE} test ${GO_TESTS}
+        COMMAND ${CMAKE_COMMAND} -E echo
+        COMMAND ${CMAKE_COMMAND} -E echo "Running unit tests"
+        COMMAND ${GO_EXECUTABLE} run util/all_tests.go -build-dir
+                ${CMAKE_CURRENT_BINARY_DIR}
+        COMMAND ${CMAKE_COMMAND} -E echo
+        COMMAND ${CMAKE_COMMAND} -E echo "Running SSL tests"
+        COMMAND cd ssl/test/runner &&
+                ${GO_EXECUTABLE} test -shim-path $<TARGET_FILE:bssl_shim>
+                  ${HANDSHAKER_ARGS} ${RUNNER_ARGS}
+        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+        DEPENDS all_tests bssl_shim handshaker fips_specific_tests_if_any
+        USES_TERMINAL)
+  else()
+    add_custom_target(
+        run_tests
+        COMMAND ${CMAKE_COMMAND} -E echo "Running tests requires Go"
+        COMMAND ${CMAKE_COMMAND} -E false)
+  endif()
 endif()
 
 if(INSTALL_ENABLED)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e6f9471..4d9279a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -41,7 +41,7 @@
     git push origin HEAD:refs/for/main
 
 The output will then give you a link to the change. Add `agl@google.com`,
-`davidben@google.com`, and `bbe@google.com` as reviewers.
+`davidben@google.com` as reviewers.
 
 Pushing a commit with the same Change-Id as an existing change will upload a new
 version of it. (Use the `git rebase` or `git commit --amend` commands.)
diff --git a/MODULE.bazel b/MODULE.bazel
index 7d06db0..6737496 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -16,7 +16,7 @@
 # the revision where we bump the version.
 module(
     name = "boringssl",
-    version = "0.20250311.0",
+    version = "0.20250415.0",
     compatibility_level = 2,
 )
 
diff --git a/build.json b/build.json
index cbe98b4..67d3992 100644
--- a/build.json
+++ b/build.json
@@ -204,7 +204,9 @@
             "crypto/blake2/blake2.cc",
             "crypto/bn/bn_asn1.cc",
             "crypto/bn/convert.cc",
+            "crypto/bn/div.cc",
             "crypto/bn/exponentiation.cc",
+            "crypto/bn/sqrt.cc",
             "crypto/buf/buf.cc",
             "crypto/bytestring/asn1_compat.cc",
             "crypto/bytestring/ber.cc",
@@ -1054,5 +1056,14 @@
             "rust/bssl-crypto/src/*.rs",
             "rust/bssl-crypto/src/*/*.rs"
         ]
+    },
+    "modulewrapper": {
+        "srcs": [
+            "util/fipstools/acvp/modulewrapper/main.cc",
+            "util/fipstools/acvp/modulewrapper/modulewrapper.cc"
+        ],
+        "internal_hdrs": [
+            "util/fipstools/acvp/modulewrapper/modulewrapper.h"
+        ]
     }
 }
diff --git a/crypto/bn/convert.cc b/crypto/bn/convert.cc
index 6f4d4fa..59ea6d4 100644
--- a/crypto/bn/convert.cc
+++ b/crypto/bn/convert.cc
@@ -19,6 +19,8 @@
 #include <limits.h>
 #include <stdio.h>
 
+#include <algorithm>
+
 #include <openssl/bio.h>
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
@@ -196,33 +198,32 @@
 char *BN_bn2dec(const BIGNUM *a) {
   // It is easier to print strings little-endian, so we assemble it in reverse
   // and fix at the end.
-  BIGNUM *copy = NULL;
-  CBB cbb;
-  if (!CBB_init(&cbb, 16) || //
-      !CBB_add_u8(&cbb, 0 /* trailing NUL */)) {
-    goto err;
+  bssl::ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 16) || //
+      !CBB_add_u8(cbb.get(), 0 /* trailing NUL */)) {
+    return nullptr;
   }
 
   if (BN_is_zero(a)) {
-    if (!CBB_add_u8(&cbb, '0')) {
-      goto err;
+    if (!CBB_add_u8(cbb.get(), '0')) {
+      return nullptr;
     }
   } else {
-    copy = BN_dup(a);
-    if (copy == NULL) {
-      goto err;
+    bssl::UniquePtr<BIGNUM> copy(BN_dup(a));
+    if (copy == nullptr) {
+      return nullptr;
     }
 
-    while (!BN_is_zero(copy)) {
-      BN_ULONG word = BN_div_word(copy, BN_DEC_CONV);
+    while (!BN_is_zero(copy.get())) {
+      BN_ULONG word = BN_div_word(copy.get(), BN_DEC_CONV);
       if (word == (BN_ULONG)-1) {
-        goto err;
+        return nullptr;
       }
 
-      const int add_leading_zeros = !BN_is_zero(copy);
+      const int add_leading_zeros = !BN_is_zero(copy.get());
       for (int i = 0; i < BN_DEC_NUM && (add_leading_zeros || word != 0); i++) {
-        if (!CBB_add_u8(&cbb, '0' + word % 10)) {
-          goto err;
+        if (!CBB_add_u8(cbb.get(), '0' + word % 10)) {
+          return nullptr;
         }
         word /= 10;
       }
@@ -231,30 +232,18 @@
   }
 
   if (BN_is_negative(a) && //
-      !CBB_add_u8(&cbb, '-')) {
-    goto err;
+      !CBB_add_u8(cbb.get(), '-')) {
+    return nullptr;
   }
 
   uint8_t *data;
   size_t len;
-  if (!CBB_finish(&cbb, &data, &len)) {
-    goto err;
+  if (!CBB_finish(cbb.get(), &data, &len)) {
+    return nullptr;
   }
 
-  // Reverse the buffer.
-  for (size_t i = 0; i < len / 2; i++) {
-    uint8_t tmp = data[i];
-    data[i] = data[len - 1 - i];
-    data[len - 1 - i] = tmp;
-  }
-
-  BN_free(copy);
-  return (char *)data;
-
-err:
-  BN_free(copy);
-  CBB_cleanup(&cbb);
-  return NULL;
+  std::reverse(data, data + len);
+  return reinterpret_cast<char *>(data);
 }
 
 int BN_dec2bn(BIGNUM **outp, const char *in) {
@@ -285,33 +274,28 @@
 }
 
 int BN_print(BIO *bp, const BIGNUM *a) {
-  int i, j, v, z = 0;
-  int ret = 0;
-
   if (a->neg && BIO_write(bp, "-", 1) != 1) {
-    goto end;
+    return 0;
   }
 
   if (BN_is_zero(a) && BIO_write(bp, "0", 1) != 1) {
-    goto end;
+    return 0;
   }
 
-  for (i = bn_minimal_width(a) - 1; i >= 0; i--) {
-    for (j = BN_BITS2 - 4; j >= 0; j -= 4) {
+  int z = 0;
+  for (int i = bn_minimal_width(a) - 1; i >= 0; i--) {
+    for (int j = BN_BITS2 - 4; j >= 0; j -= 4) {
       // strip leading zeros
-      v = ((int)(a->d[i] >> (long)j)) & 0x0f;
+      int v = ((int)(a->d[i] >> (long)j)) & 0x0f;
       if (z || v != 0) {
         if (BIO_write(bp, &hextable[v], 1) != 1) {
-          goto end;
+          return 0;
         }
         z = 1;
       }
     }
   }
-  ret = 1;
-
-end:
-  return ret;
+  return 1;
 }
 
 int BN_print_fp(FILE *fp, const BIGNUM *a) {
diff --git a/crypto/bn/div.cc b/crypto/bn/div.cc
new file mode 100644
index 0000000..868a2a3
--- /dev/null
+++ b/crypto/bn/div.cc
@@ -0,0 +1,100 @@
+// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <openssl/bn.h>
+
+#include <openssl/err.h>
+
+#include "../fipsmodule/bn/internal.h"
+#include "../internal.h"
+
+
+int BN_mod_pow2(BIGNUM *r, const BIGNUM *a, size_t e) {
+  if (e == 0 || a->width == 0) {
+    BN_zero(r);
+    return 1;
+  }
+
+  size_t num_words = 1 + ((e - 1) / BN_BITS2);
+
+  // If |a| definitely has less than |e| bits, just BN_copy.
+  if ((size_t)a->width < num_words) {
+    return BN_copy(r, a) != NULL;
+  }
+
+  // Otherwise, first make sure we have enough space in |r|.
+  // Note that this will fail if num_words > INT_MAX.
+  if (!bn_wexpand(r, num_words)) {
+    return 0;
+  }
+
+  // Copy the content of |a| into |r|.
+  OPENSSL_memcpy(r->d, a->d, num_words * sizeof(BN_ULONG));
+
+  // If |e| isn't word-aligned, we have to mask off some of our bits.
+  size_t top_word_exponent = e % (sizeof(BN_ULONG) * 8);
+  if (top_word_exponent != 0) {
+    r->d[num_words - 1] &= (((BN_ULONG)1) << top_word_exponent) - 1;
+  }
+
+  // Fill in the remaining fields of |r|.
+  r->neg = a->neg;
+  r->width = (int)num_words;
+  bn_set_minimal_width(r);
+  return 1;
+}
+
+int BN_nnmod_pow2(BIGNUM *r, const BIGNUM *a, size_t e) {
+  if (!BN_mod_pow2(r, a, e)) {
+    return 0;
+  }
+
+  // If the returned value was non-negative, we're done.
+  if (BN_is_zero(r) || !r->neg) {
+    return 1;
+  }
+
+  size_t num_words = 1 + (e - 1) / BN_BITS2;
+
+  // Expand |r| to the size of our modulus.
+  if (!bn_wexpand(r, num_words)) {
+    return 0;
+  }
+
+  // Clear the upper words of |r|.
+  OPENSSL_memset(&r->d[r->width], 0, (num_words - r->width) * BN_BYTES);
+
+  // Set parameters of |r|.
+  r->neg = 0;
+  r->width = (int)num_words;
+
+  // Now, invert every word. The idea here is that we want to compute 2^e-|x|,
+  // which is actually equivalent to the twos-complement representation of |x|
+  // in |e| bits, which is -x = ~x + 1.
+  for (int i = 0; i < r->width; i++) {
+    r->d[i] = ~r->d[i];
+  }
+
+  // If our exponent doesn't span the top word, we have to mask the rest.
+  size_t top_word_exponent = e % BN_BITS2;
+  if (top_word_exponent != 0) {
+    r->d[r->width - 1] &= (((BN_ULONG)1) << top_word_exponent) - 1;
+  }
+
+  // Keep the minimal-width invariant for |BIGNUM|.
+  bn_set_minimal_width(r);
+
+  // Finally, add one, for the reason described above.
+  return BN_add(r, r, BN_value_one());
+}
diff --git a/crypto/bn/exponentiation.cc b/crypto/bn/exponentiation.cc
index a6d7d9c..07f9ec6 100644
--- a/crypto/bn/exponentiation.cc
+++ b/crypto/bn/exponentiation.cc
@@ -123,62 +123,44 @@
 int BN_mod_exp_mont_word(BIGNUM *rr, BN_ULONG a, const BIGNUM *p,
                          const BIGNUM *m, BN_CTX *ctx,
                          const BN_MONT_CTX *mont) {
-  BIGNUM a_bignum;
-  BN_init(&a_bignum);
-
-  int ret = 0;
-
   // BN_mod_exp_mont requires reduced inputs.
   if (bn_minimal_width(m) == 1) {
     a %= m->d[0];
   }
 
-  if (!BN_set_word(&a_bignum, a)) {
+  bssl::UniquePtr<BIGNUM> a_bignum(BN_new());
+  if (a_bignum == nullptr || !BN_set_word(a_bignum.get(), a)) {
     OPENSSL_PUT_ERROR(BN, ERR_R_INTERNAL_ERROR);
-    goto err;
+    return 0;
   }
 
-  ret = BN_mod_exp_mont(rr, &a_bignum, p, m, ctx, mont);
-
-err:
-  BN_free(&a_bignum);
-
-  return ret;
+  return BN_mod_exp_mont(rr, a_bignum.get(), p, m, ctx, mont);
 }
 
 int BN_mod_exp2_mont(BIGNUM *rr, const BIGNUM *a1, const BIGNUM *p1,
                      const BIGNUM *a2, const BIGNUM *p2, const BIGNUM *m,
                      BN_CTX *ctx, const BN_MONT_CTX *mont) {
-  BIGNUM tmp;
-  BN_init(&tmp);
-
-  int ret = 0;
-  BN_MONT_CTX *new_mont = NULL;
-
   // Allocate a montgomery context if it was not supplied by the caller.
-  if (mont == NULL) {
-    new_mont = BN_MONT_CTX_new_for_modulus(m, ctx);
-    if (new_mont == NULL) {
-      goto err;
+  bssl::UniquePtr<BN_MONT_CTX> new_mont;
+  if (mont == nullptr) {
+    new_mont.reset(BN_MONT_CTX_new_for_modulus(m, ctx));
+    if (new_mont == nullptr) {
+      return 0;
     }
-    mont = new_mont;
+    mont = new_mont.get();
   }
 
   // BN_mod_mul_montgomery removes one Montgomery factor, so passing one
   // Montgomery-encoded and one non-Montgomery-encoded value gives a
   // non-Montgomery-encoded result.
-  if (!BN_mod_exp_mont(rr, a1, p1, m, ctx, mont) ||
-      !BN_mod_exp_mont(&tmp, a2, p2, m, ctx, mont) ||
+  bssl::UniquePtr<BIGNUM> tmp(BN_new());
+  if (tmp == nullptr ||  //
+      !BN_mod_exp_mont(rr, a1, p1, m, ctx, mont) ||
+      !BN_mod_exp_mont(tmp.get(), a2, p2, m, ctx, mont) ||
       !BN_to_montgomery(rr, rr, mont, ctx) ||
-      !BN_mod_mul_montgomery(rr, rr, &tmp, mont, ctx)) {
-    goto err;
+      !BN_mod_mul_montgomery(rr, rr, tmp.get(), mont, ctx)) {
+    return 0;
   }
 
-  ret = 1;
-
-err:
-  BN_MONT_CTX_free(new_mont);
-  BN_free(&tmp);
-
-  return ret;
+  return 1;
 }
diff --git a/crypto/bn/sqrt.cc b/crypto/bn/sqrt.cc
new file mode 100644
index 0000000..e23f1a3
--- /dev/null
+++ b/crypto/bn/sqrt.cc
@@ -0,0 +1,93 @@
+// Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <openssl/bn.h>
+
+#include <openssl/err.h>
+
+
+int BN_sqrt(BIGNUM *out_sqrt, const BIGNUM *in, BN_CTX *ctx) {
+  BIGNUM *estimate, *tmp, *delta, *last_delta, *tmp2;
+  int ok = 0, last_delta_valid = 0;
+
+  if (in->neg) {
+    OPENSSL_PUT_ERROR(BN, BN_R_NEGATIVE_NUMBER);
+    return 0;
+  }
+  if (BN_is_zero(in)) {
+    BN_zero(out_sqrt);
+    return 1;
+  }
+
+  bssl::BN_CTXScope scope(ctx);
+  if (out_sqrt == in) {
+    estimate = BN_CTX_get(ctx);
+  } else {
+    estimate = out_sqrt;
+  }
+  tmp = BN_CTX_get(ctx);
+  last_delta = BN_CTX_get(ctx);
+  delta = BN_CTX_get(ctx);
+  if (estimate == NULL || tmp == NULL || last_delta == NULL || delta == NULL) {
+    goto err;
+  }
+
+  // We estimate that the square root of an n-bit number is 2^{n/2}.
+  if (!BN_lshift(estimate, BN_value_one(), BN_num_bits(in)/2)) {
+    goto err;
+  }
+
+  // This is Newton's method for finding a root of the equation |estimate|^2 -
+  // |in| = 0.
+  for (;;) {
+    // |estimate| = 1/2 * (|estimate| + |in|/|estimate|)
+    if (!BN_div(tmp, NULL, in, estimate, ctx) ||
+        !BN_add(tmp, tmp, estimate) ||
+        !BN_rshift1(estimate, tmp) ||
+        // |tmp| = |estimate|^2
+        !BN_sqr(tmp, estimate, ctx) ||
+        // |delta| = |in| - |tmp|
+        !BN_sub(delta, in, tmp)) {
+      OPENSSL_PUT_ERROR(BN, ERR_R_BN_LIB);
+      goto err;
+    }
+
+    delta->neg = 0;
+    // The difference between |in| and |estimate| squared is required to always
+    // decrease. This ensures that the loop always terminates, but I don't have
+    // a proof that it always finds the square root for a given square.
+    if (last_delta_valid && BN_cmp(delta, last_delta) >= 0) {
+      break;
+    }
+
+    last_delta_valid = 1;
+
+    tmp2 = last_delta;
+    last_delta = delta;
+    delta = tmp2;
+  }
+
+  if (BN_cmp(tmp, in) != 0) {
+    OPENSSL_PUT_ERROR(BN, BN_R_NOT_A_SQUARE);
+    goto err;
+  }
+
+  ok = 1;
+
+err:
+  if (ok && out_sqrt == in && !BN_copy(out_sqrt, estimate)) {
+    ok = 0;
+  }
+  return ok;
+}
diff --git a/crypto/fipsmodule/bn/div.cc.inc b/crypto/fipsmodule/bn/div.cc.inc
index 374e6e1..a29bf9c 100644
--- a/crypto/fipsmodule/bn/div.cc.inc
+++ b/crypto/fipsmodule/bn/div.cc.inc
@@ -732,82 +732,3 @@
   }
   return (BN_ULONG)ret;
 }
-
-int BN_mod_pow2(BIGNUM *r, const BIGNUM *a, size_t e) {
-  if (e == 0 || a->width == 0) {
-    BN_zero(r);
-    return 1;
-  }
-
-  size_t num_words = 1 + ((e - 1) / BN_BITS2);
-
-  // If |a| definitely has less than |e| bits, just BN_copy.
-  if ((size_t)a->width < num_words) {
-    return BN_copy(r, a) != NULL;
-  }
-
-  // Otherwise, first make sure we have enough space in |r|.
-  // Note that this will fail if num_words > INT_MAX.
-  if (!bn_wexpand(r, num_words)) {
-    return 0;
-  }
-
-  // Copy the content of |a| into |r|.
-  OPENSSL_memcpy(r->d, a->d, num_words * sizeof(BN_ULONG));
-
-  // If |e| isn't word-aligned, we have to mask off some of our bits.
-  size_t top_word_exponent = e % (sizeof(BN_ULONG) * 8);
-  if (top_word_exponent != 0) {
-    r->d[num_words - 1] &= (((BN_ULONG)1) << top_word_exponent) - 1;
-  }
-
-  // Fill in the remaining fields of |r|.
-  r->neg = a->neg;
-  r->width = (int)num_words;
-  bn_set_minimal_width(r);
-  return 1;
-}
-
-int BN_nnmod_pow2(BIGNUM *r, const BIGNUM *a, size_t e) {
-  if (!BN_mod_pow2(r, a, e)) {
-    return 0;
-  }
-
-  // If the returned value was non-negative, we're done.
-  if (BN_is_zero(r) || !r->neg) {
-    return 1;
-  }
-
-  size_t num_words = 1 + (e - 1) / BN_BITS2;
-
-  // Expand |r| to the size of our modulus.
-  if (!bn_wexpand(r, num_words)) {
-    return 0;
-  }
-
-  // Clear the upper words of |r|.
-  OPENSSL_memset(&r->d[r->width], 0, (num_words - r->width) * BN_BYTES);
-
-  // Set parameters of |r|.
-  r->neg = 0;
-  r->width = (int)num_words;
-
-  // Now, invert every word. The idea here is that we want to compute 2^e-|x|,
-  // which is actually equivalent to the twos-complement representation of |x|
-  // in |e| bits, which is -x = ~x + 1.
-  for (int i = 0; i < r->width; i++) {
-    r->d[i] = ~r->d[i];
-  }
-
-  // If our exponent doesn't span the top word, we have to mask the rest.
-  size_t top_word_exponent = e % BN_BITS2;
-  if (top_word_exponent != 0) {
-    r->d[r->width - 1] &= (((BN_ULONG)1) << top_word_exponent) - 1;
-  }
-
-  // Keep the minimal-width invariant for |BIGNUM|.
-  bn_set_minimal_width(r);
-
-  // Finally, add one, for the reason described above.
-  return BN_add(r, r, BN_value_one());
-}
diff --git a/crypto/fipsmodule/bn/exponentiation.cc.inc b/crypto/fipsmodule/bn/exponentiation.cc.inc
index b719bea..205f4b4 100644
--- a/crypto/fipsmodule/bn/exponentiation.cc.inc
+++ b/crypto/fipsmodule/bn/exponentiation.cc.inc
@@ -413,7 +413,6 @@
                               const BIGNUM *m, BN_CTX *ctx,
                               const BN_MONT_CTX *mont) {
   int i, ret = 0, wvalue;
-  BN_MONT_CTX *new_mont = NULL;
 
   void *powerbuf_free = NULL;
   size_t powerbuf_len = 0;
@@ -449,12 +448,13 @@
 
   // Allocate a montgomery context if it was not supplied by the caller.
   int top, num_powers, window;
-  if (mont == NULL) {
-    new_mont = BN_MONT_CTX_new_consttime(m, ctx);
-    if (new_mont == NULL) {
+  bssl::UniquePtr<BN_MONT_CTX> new_mont;
+  if (mont == nullptr) {
+    new_mont.reset(BN_MONT_CTX_new_consttime(m, ctx));
+    if (new_mont == nullptr) {
       goto err;
     }
-    mont = new_mont;
+    mont = new_mont.get();
   }
 
   // Use the width in |mont->N|, rather than the copy in |m|. The assembly
@@ -731,7 +731,6 @@
   ret = 1;
 
 err:
-  BN_MONT_CTX_free(new_mont);
   if (powerbuf != NULL && powerbuf_free == NULL) {
     OPENSSL_cleanse(powerbuf, powerbuf_len);
   }
diff --git a/crypto/fipsmodule/bn/gcd.cc.inc b/crypto/fipsmodule/bn/gcd.cc.inc
index 6e3cc06..e876c4d 100644
--- a/crypto/fipsmodule/bn/gcd.cc.inc
+++ b/crypto/fipsmodule/bn/gcd.cc.inc
@@ -33,23 +33,20 @@
     return 0;
   }
 
-  BIGNUM *A, *B, *X, *Y;
-  int ret = 0;
   int sign;
-
-  BN_CTX_start(ctx);
-  A = BN_CTX_get(ctx);
-  B = BN_CTX_get(ctx);
-  X = BN_CTX_get(ctx);
-  Y = BN_CTX_get(ctx);
+  bssl::BN_CTXScope scope(ctx);
+  BIGNUM *A = BN_CTX_get(ctx);
+  BIGNUM *B = BN_CTX_get(ctx);
+  BIGNUM *X = BN_CTX_get(ctx);
+  BIGNUM *Y = BN_CTX_get(ctx);
   BIGNUM *R = out;
   if (Y == NULL) {
-    goto err;
+    return 0;
   }
 
   BN_zero(Y);
   if (!BN_one(X) || BN_copy(B, a) == NULL || BN_copy(A, n) == NULL) {
-    goto err;
+    return 0;
   }
   A->neg = 0;
   sign = -1;
@@ -80,17 +77,17 @@
 
       if (BN_is_odd(X)) {
         if (!BN_uadd(X, X, n)) {
-          goto err;
+          return 0;
         }
       }
       // now X is even, so we can easily divide it by two
       if (!BN_rshift1(X, X)) {
-        goto err;
+        return 0;
       }
     }
     if (shift > 0) {
       if (!BN_rshift(B, B, shift)) {
-        goto err;
+        return 0;
       }
     }
 
@@ -102,17 +99,17 @@
 
       if (BN_is_odd(Y)) {
         if (!BN_uadd(Y, Y, n)) {
-          goto err;
+          return 0;
         }
       }
       // now Y is even
       if (!BN_rshift1(Y, Y)) {
-        goto err;
+        return 0;
       }
     }
     if (shift > 0) {
       if (!BN_rshift(A, A, shift)) {
-        goto err;
+        return 0;
       }
     }
 
@@ -129,21 +126,21 @@
     if (BN_ucmp(B, A) >= 0) {
       // -sign*(X + Y)*a == B - A  (mod |n|)
       if (!BN_uadd(X, X, Y)) {
-        goto err;
+        return 0;
       }
       // NB: we could use BN_mod_add_quick(X, X, Y, n), but that
       // actually makes the algorithm slower
       if (!BN_usub(B, B, A)) {
-        goto err;
+        return 0;
       }
     } else {
       //  sign*(X + Y)*a == A - B  (mod |n|)
       if (!BN_uadd(Y, Y, X)) {
-        goto err;
+        return 0;
       }
       // as above, BN_mod_add_quick(Y, Y, X, n) would slow things down
       if (!BN_usub(A, A, B)) {
-        goto err;
+        return 0;
       }
     }
   }
@@ -151,7 +148,7 @@
   if (!BN_is_one(A)) {
     *out_no_inverse = 1;
     OPENSSL_PUT_ERROR(BN, BN_R_NO_INVERSE);
-    goto err;
+    return 0;
   }
 
   // The while loop (Euclid's algorithm) ends when
@@ -162,7 +159,7 @@
 
   if (sign < 0) {
     if (!BN_sub(Y, n, Y)) {
-      goto err;
+      return 0;
     }
   }
   // Now  Y*a  ==  A  (mod |n|).
@@ -170,61 +167,49 @@
   // Y*a == 1  (mod |n|)
   if (Y->neg || BN_ucmp(Y, n) >= 0) {
     if (!BN_nnmod(Y, Y, n, ctx)) {
-      goto err;
+      return 0;
     }
   }
   if (!BN_copy(R, Y)) {
-    goto err;
+    return 0;
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 BIGNUM *BN_mod_inverse(BIGNUM *out, const BIGNUM *a, const BIGNUM *n,
                        BN_CTX *ctx) {
-  BIGNUM *new_out = NULL;
-  if (out == NULL) {
-    new_out = BN_new();
-    if (new_out == NULL) {
-      return NULL;
+  bssl::UniquePtr<BIGNUM> new_out;
+  if (out == nullptr) {
+    new_out.reset(BN_new());
+    if (new_out == nullptr) {
+      return nullptr;
     }
-    out = new_out;
+    out = new_out.get();
   }
 
-  int ok = 0;
-  BIGNUM *a_reduced = NULL;
+  bssl::UniquePtr<BIGNUM> a_reduced;
   if (a->neg || BN_ucmp(a, n) >= 0) {
-    a_reduced = BN_dup(a);
-    if (a_reduced == NULL) {
-      goto err;
+    a_reduced.reset(BN_dup(a));
+    if (a_reduced == nullptr) {
+      return nullptr;
     }
-    if (!BN_nnmod(a_reduced, a_reduced, n, ctx)) {
-      goto err;
+    if (!BN_nnmod(a_reduced.get(), a_reduced.get(), n, ctx)) {
+      return nullptr;
     }
-    a = a_reduced;
+    a = a_reduced.get();
   }
 
   int no_inverse;
   if (!BN_is_odd(n)) {
     if (!bn_mod_inverse_consttime(out, &no_inverse, a, n, ctx)) {
-      goto err;
+      return nullptr;
     }
   } else if (!BN_mod_inverse_odd(out, &no_inverse, a, n, ctx)) {
-    goto err;
+    return nullptr;
   }
 
-  ok = 1;
-
-err:
-  if (!ok) {
-    BN_free(new_out);
-    out = NULL;
-  }
-  BN_free(a_reduced);
+  new_out.release();  // Passed to the caller via |out|.
   return out;
 }
 
@@ -240,9 +225,10 @@
     return 0;
   }
 
-  int ret = 0;
-  BIGNUM blinding_factor;
-  BN_init(&blinding_factor);
+  bssl::UniquePtr<BIGNUM> blinding_factor(BN_new());
+  if (blinding_factor == nullptr) {
+    return 0;
+  }
 
   // |BN_mod_inverse_odd| is leaky, so generate a secret blinding factor and
   // blind |a|. This works because (ar)^-1 * r = a^-1, supposing r is
@@ -253,12 +239,12 @@
   //
   // TODO(crbug.com/boringssl/677): When the PRNG output is marked secret by
   // default, the explicit |bn_secret| call can be removed.
-  if (!BN_rand_range_ex(&blinding_factor, 1, &mont->N)) {
-    goto err;
+  if (!BN_rand_range_ex(blinding_factor.get(), 1, &mont->N)) {
+    return 0;
   }
-  bn_secret(&blinding_factor);
-  if (!BN_mod_mul_montgomery(out, &blinding_factor, a, mont, ctx)) {
-    goto err;
+  bn_secret(blinding_factor.get());
+  if (!BN_mod_mul_montgomery(out, blinding_factor.get(), a, mont, ctx)) {
+    return 0;
   }
 
   // Once blinded, |out| is no longer secret, so it may be passed to a leaky
@@ -266,35 +252,27 @@
   // secret again after multiplying.
   bn_declassify(out);
   if (!BN_mod_inverse_odd(out, out_no_inverse, out, &mont->N, ctx) ||
-      !BN_mod_mul_montgomery(out, &blinding_factor, out, mont, ctx)) {
-    goto err;
+      !BN_mod_mul_montgomery(out, blinding_factor.get(), out, mont, ctx)) {
+    return 0;
   }
 
-  ret = 1;
-
-err:
-  BN_free(&blinding_factor);
-  return ret;
+  return 1;
 }
 
 int bn_mod_inverse_prime(BIGNUM *out, const BIGNUM *a, const BIGNUM *p,
                          BN_CTX *ctx, const BN_MONT_CTX *mont_p) {
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *p_minus_2 = BN_CTX_get(ctx);
-  int ok = p_minus_2 != NULL && BN_copy(p_minus_2, p) &&
-           BN_sub_word(p_minus_2, 2) &&
-           BN_mod_exp_mont(out, a, p_minus_2, p, ctx, mont_p);
-  BN_CTX_end(ctx);
-  return ok;
+  return p_minus_2 != nullptr && BN_copy(p_minus_2, p) &&
+         BN_sub_word(p_minus_2, 2) &&
+         BN_mod_exp_mont(out, a, p_minus_2, p, ctx, mont_p);
 }
 
 int bn_mod_inverse_secret_prime(BIGNUM *out, const BIGNUM *a, const BIGNUM *p,
                                 BN_CTX *ctx, const BN_MONT_CTX *mont_p) {
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *p_minus_2 = BN_CTX_get(ctx);
-  int ok = p_minus_2 != NULL && BN_copy(p_minus_2, p) &&
-           BN_sub_word(p_minus_2, 2) &&
-           BN_mod_exp_mont_consttime(out, a, p_minus_2, p, ctx, mont_p);
-  BN_CTX_end(ctx);
-  return ok;
+  return p_minus_2 != nullptr && BN_copy(p_minus_2, p) &&
+         BN_sub_word(p_minus_2, 2) &&
+         BN_mod_exp_mont_consttime(out, a, p_minus_2, p, ctx, mont_p);
 }
diff --git a/crypto/fipsmodule/bn/gcd_extra.cc.inc b/crypto/fipsmodule/bn/gcd_extra.cc.inc
index d8642cd..dc0f072 100644
--- a/crypto/fipsmodule/bn/gcd_extra.cc.inc
+++ b/crypto/fipsmodule/bn/gcd_extra.cc.inc
@@ -56,19 +56,18 @@
   }
 
   // This is a constant-time implementation of Stein's algorithm (binary GCD).
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *u = BN_CTX_get(ctx);
   BIGNUM *v = BN_CTX_get(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
   unsigned x_bits, y_bits, num_iters, shift;
-  if (u == NULL || v == NULL || tmp == NULL ||  //
+  if (u == nullptr || v == nullptr || tmp == nullptr ||  //
       !BN_copy(u, x) ||                         //
       !BN_copy(v, y) ||                         //
       !bn_resize_words(u, width) ||             //
       !bn_resize_words(v, width) ||             //
       !bn_resize_words(tmp, width)) {
-    goto err;
+    return 0;
   }
 
   // Each loop iteration halves at least one of |u| and |v|. Thus we need at
@@ -78,7 +77,7 @@
   num_iters = x_bits + y_bits;
   if (num_iters < x_bits) {
     OPENSSL_PUT_ERROR(BN, BN_R_BIGNUM_TOO_LONG);
-    goto err;
+    return 0;
   }
 
   shift = 0;
@@ -114,11 +113,7 @@
   }
 
   *out_shift = shift;
-  ret = bn_set_words(r, v->d, width);
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return bn_set_words(r, v->d, width);
 }
 
 int BN_gcd(BIGNUM *r, const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx) {
@@ -128,12 +123,11 @@
 
 int bn_is_relatively_prime(int *out_relatively_prime, const BIGNUM *x,
                            const BIGNUM *y, BN_CTX *ctx) {
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   unsigned shift;
   BIGNUM *gcd = BN_CTX_get(ctx);
-  if (gcd == NULL || !bn_gcd_consttime(gcd, &shift, x, y, ctx)) {
-    goto err;
+  if (gcd == nullptr || !bn_gcd_consttime(gcd, &shift, x, y, ctx)) {
+    return 0;
   }
 
   // Check that 2^|shift| * |gcd| is one.
@@ -146,25 +140,20 @@
     }
     *out_relatively_prime = mask == 0;
   }
-  ret = 1;
 
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 int bn_lcm_consttime(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx) {
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   unsigned shift;
   BIGNUM *gcd = BN_CTX_get(ctx);
-  int ret = gcd != NULL &&  //
-            bn_mul_consttime(r, a, b, ctx) &&
-            bn_gcd_consttime(gcd, &shift, a, b, ctx) &&
-            // |gcd| has a secret bit width.
-            bn_div_consttime(r, NULL, r, gcd, /*divisor_min_bits=*/0, ctx) &&
-            bn_rshift_secret_shift(r, r, shift, ctx);
-  BN_CTX_end(ctx);
-  return ret;
+  return gcd != nullptr &&  //
+         bn_mul_consttime(r, a, b, ctx) &&
+         bn_gcd_consttime(gcd, &shift, a, b, ctx) &&
+         // |gcd| has a secret bit width.
+         bn_div_consttime(r, nullptr, r, gcd, /*divisor_min_bits=*/0, ctx) &&
+         bn_rshift_secret_shift(r, r, shift, ctx);
 }
 
 int bn_mod_inverse_consttime(BIGNUM *r, int *out_no_inverse, const BIGNUM *a,
@@ -207,8 +196,7 @@
     a_width = n_width;
   }
 
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *u = BN_CTX_get(ctx);
   BIGNUM *v = BN_CTX_get(ctx);
   BIGNUM *A = BN_CTX_get(ctx);
@@ -218,14 +206,14 @@
   BIGNUM *tmp = BN_CTX_get(ctx);
   BIGNUM *tmp2 = BN_CTX_get(ctx);
   size_t a_bits, num_iters, n_bits;
-  if (u == NULL ||       //
-      v == NULL ||       //
-      A == NULL ||       //
-      B == NULL ||       //
-      C == NULL ||       //
-      D == NULL ||       //
-      tmp == NULL ||     //
-      tmp2 == NULL ||    //
+  if (u == nullptr ||       //
+      v == nullptr ||       //
+      A == nullptr ||       //
+      B == nullptr ||       //
+      C == nullptr ||       //
+      D == nullptr ||       //
+      tmp == nullptr ||     //
+      tmp2 == nullptr ||    //
       !BN_copy(u, a) ||  //
       !BN_copy(v, n) ||  //
       !BN_one(A) ||      //
@@ -242,7 +230,7 @@
       // |tmp| and |tmp2| may be used at either size.
       !bn_resize_words(tmp, n_width) ||  //
       !bn_resize_words(tmp2, n_width)) {
-    goto err;
+    return 0;
   }
 
   // Each loop iteration halves at least one of |u| and |v|. Thus we need at
@@ -254,7 +242,7 @@
   num_iters = a_bits + n_bits;
   if (num_iters < a_bits) {
     OPENSSL_PUT_ERROR(BN, BN_R_BIGNUM_TOO_LONG);
-    goto err;
+    return 0;
   }
 
   // Before and after each loop iteration, the following hold:
@@ -328,12 +316,8 @@
   if (constant_time_declassify_int(!BN_is_one(u))) {
     *out_no_inverse = 1;
     OPENSSL_PUT_ERROR(BN, BN_R_NO_INVERSE);
-    goto err;
+    return 0;
   }
 
-  ret = BN_copy(r, A) != NULL;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return BN_copy(r, A) != nullptr;
 }
diff --git a/crypto/fipsmodule/bn/jacobi.cc.inc b/crypto/fipsmodule/bn/jacobi.cc.inc
index 0ac6f67..1c1c51f 100644
--- a/crypto/fipsmodule/bn/jacobi.cc.inc
+++ b/crypto/fipsmodule/bn/jacobi.cc.inc
@@ -42,32 +42,29 @@
     return -2;
   }
 
-  int ret = -2;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *A = BN_CTX_get(ctx);
   BIGNUM *B = BN_CTX_get(ctx);
   if (B == NULL) {
-    goto end;
+    return -2;
   }
 
   if (!BN_copy(A, a) ||
       !BN_copy(B, b)) {
-    goto end;
+    return -2;
   }
 
   // Adapted from logic to compute the Kronecker symbol, originally implemented
   // according to Henri Cohen, "A Course in Computational Algebraic Number
   // Theory" (algorithm 1.4.10).
 
-  ret = 1;
-
+  int ret = 1;
   while (1) {
     // Cohen's step 3:
 
     // B is positive and odd
     if (BN_is_zero(A)) {
-      ret = BN_is_one(B) ? ret : 0;
-      goto end;
+      return BN_is_one(B) ? ret : 0;
     }
 
     // now A is non-zero
@@ -76,8 +73,7 @@
       i++;
     }
     if (!BN_rshift(A, A, i)) {
-      ret = -2;
-      goto end;
+      return -2;
     }
     if (i & 1) {
       // i is odd
@@ -93,16 +89,11 @@
 
     // (A, B) := (B mod |A|, |A|)
     if (!BN_nnmod(B, B, A, ctx)) {
-      ret = -2;
-      goto end;
+      return -2;
     }
     BIGNUM *tmp = A;
     A = B;
     B = tmp;
     tmp->neg = 0;
   }
-
-end:
-  BN_CTX_end(ctx);
-  return ret;
 }
diff --git a/crypto/fipsmodule/bn/montgomery.cc.inc b/crypto/fipsmodule/bn/montgomery.cc.inc
index c0adcca..4bafb11 100644
--- a/crypto/fipsmodule/bn/montgomery.cc.inc
+++ b/crypto/fipsmodule/bn/montgomery.cc.inc
@@ -245,21 +245,13 @@
 
 int BN_from_montgomery(BIGNUM *r, const BIGNUM *a, const BN_MONT_CTX *mont,
                        BN_CTX *ctx) {
-  int ret = 0;
-  BIGNUM *t;
-
-  BN_CTX_start(ctx);
-  t = BN_CTX_get(ctx);
+  bssl::BN_CTXScope scope(ctx);
+  BIGNUM *t = BN_CTX_get(ctx);
   if (t == NULL || !BN_copy(t, a)) {
-    goto err;
+    return 0;
   }
 
-  ret = BN_from_montgomery_word(r, t, mont);
-
-err:
-  BN_CTX_end(ctx);
-
-  return ret;
+  return BN_from_montgomery_word(r, t, mont);
 }
 
 int bn_one_to_montgomery(BIGNUM *r, const BN_MONT_CTX *mont, BN_CTX *ctx) {
@@ -286,34 +278,28 @@
                                           const BIGNUM *b,
                                           const BN_MONT_CTX *mont,
                                           BN_CTX *ctx) {
-  int ret = 0;
-
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
   if (tmp == NULL) {
-    goto err;
+    return 0;
   }
 
   if (a == b) {
     if (!bn_sqr_consttime(tmp, a, ctx)) {
-      goto err;
+      return 0;
     }
   } else {
     if (!bn_mul_consttime(tmp, a, b, ctx)) {
-      goto err;
+      return 0;
     }
   }
 
   // reduce from aRR to aR
   if (!BN_from_montgomery_word(r, tmp, mont)) {
-    goto err;
+    return 0;
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 int BN_mod_mul_montgomery(BIGNUM *r, const BIGNUM *a, const BIGNUM *b,
diff --git a/crypto/fipsmodule/bn/mul.cc.inc b/crypto/fipsmodule/bn/mul.cc.inc
index 4d7a919..9f75dde 100644
--- a/crypto/fipsmodule/bn/mul.cc.inc
+++ b/crypto/fipsmodule/bn/mul.cc.inc
@@ -137,15 +137,14 @@
   int cl = a->width < b->width ? a->width : b->width;
   int dl = a->width - b->width;
   int r_len = a->width < b->width ? b->width : a->width;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
-  int ok = tmp != NULL && bn_wexpand(r, r_len) && bn_wexpand(tmp, r_len);
-  if (ok) {
-    bn_abs_sub_part_words(r->d, a->d, b->d, cl, dl, tmp->d);
-    r->width = r_len;
+  if (tmp == nullptr || !bn_wexpand(r, r_len) || !bn_wexpand(tmp, r_len)) {
+    return 0;
   }
-  BN_CTX_end(ctx);
-  return ok;
+  bn_abs_sub_part_words(r->d, a->d, b->d, cl, dl, tmp->d);
+  r->width = r_len;
+  return 1;
 }
 
 // Karatsuba recursive multiplication algorithm
@@ -377,13 +376,13 @@
     return 1;
   }
 
-  int ret = 0, i, top;
+  int i, top;
   BIGNUM *rr;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   if (r == a || r == b) {
     rr = BN_CTX_get(ctx);
     if (rr == NULL) {
-      goto err;
+      return 0;
     }
   } else {
     rr = r;
@@ -394,7 +393,7 @@
   if (i == 0) {
     if (al == 8) {
       if (!bn_wexpand(rr, 16)) {
-        goto err;
+        return 0;
       }
       rr->width = 16;
       bn_mul_comba8(rr->d, a->d, b->d);
@@ -421,7 +420,7 @@
       assert(j <= al || j <= bl);
       BIGNUM *t = BN_CTX_get(ctx);
       if (t == NULL) {
-        goto err;
+        return 0;
       }
       if (al > j || bl > j) {
         // We know |al| and |bl| are at most one from each other, so if al > j,
@@ -432,7 +431,7 @@
         // https://boringssl-review.googlesource.com/q/I0bd604e2cd6a75c266f64476c23a730ca1721ea6
         assert(al >= j && bl >= j);
         if (!bn_wexpand(t, j * 8) || !bn_wexpand(rr, j * 4)) {
-          goto err;
+          return 0;
         }
         bn_mul_part_recursive(rr->d, a->d, b->d, j, al - j, bl - j, t->d);
       } else {
@@ -440,7 +439,7 @@
         // of al - j or bl - j is zero. The other, by the bound on |i| above, is
         // zero or -1. Thus, we can use |bn_mul_recursive|.
         if (!bn_wexpand(t, j * 4) || !bn_wexpand(rr, j * 2)) {
-          goto err;
+          return 0;
         }
         bn_mul_recursive(rr->d, a->d, b->d, j, al - j, bl - j, t->d);
       }
@@ -450,20 +449,16 @@
   }
 
   if (!bn_wexpand(rr, top)) {
-    goto err;
+    return 0;
   }
   rr->width = top;
   bn_mul_normal(rr->d, a->d, al, b->d, bl);
 
 end:
   if (r != rr && !BN_copy(r, rr)) {
-    goto err;
+    return 0;
   }
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx) {
@@ -627,17 +622,16 @@
     return 1;
   }
 
-  int ret = 0, max;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *rr = (a != r) ? r : BN_CTX_get(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
   if (!rr || !tmp) {
-    goto err;
+    return 0;
   }
 
-  max = 2 * al;  // Non-zero (from above)
+  int max = 2 * al;  // Non-zero (from above)
   if (!bn_wexpand(rr, max)) {
-    goto err;
+    return 0;
   }
 
   if (al == 4) {
@@ -658,12 +652,12 @@
       // cryptographic primitives.
       if (al != 0 && (al & (al - 1)) == 0) {
         if (!bn_wexpand(tmp, al * 4)) {
-          goto err;
+          return 0;
         }
         bn_sqr_recursive(rr->d, a->d, al, tmp->d);
       } else {
         if (!bn_wexpand(tmp, max)) {
-          goto err;
+          return 0;
         }
         bn_sqr_normal(rr->d, a->d, al, tmp->d);
       }
@@ -674,13 +668,9 @@
   rr->width = max;
 
   if (rr != r && !BN_copy(r, rr)) {
-    goto err;
+    return 0;
   }
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 int BN_sqr(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx) {
diff --git a/crypto/fipsmodule/bn/prime.cc.inc b/crypto/fipsmodule/bn/prime.cc.inc
index 0471246..fabb545 100644
--- a/crypto/fipsmodule/bn/prime.cc.inc
+++ b/crypto/fipsmodule/bn/prime.cc.inc
@@ -290,9 +290,7 @@
 int BN_generate_prime_ex(BIGNUM *ret, int bits, int safe, const BIGNUM *add,
                          const BIGNUM *rem, BN_GENCB *cb) {
   BIGNUM *t;
-  int found = 0;
   int i, j, c1 = 0;
-  BN_CTX *ctx;
   int checks = BN_prime_checks_for_size(bits);
 
   if (bits < 2) {
@@ -305,43 +303,43 @@
     return 0;
   }
 
-  ctx = BN_CTX_new();
-  if (ctx == NULL) {
-    goto err;
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
+    return 0;
   }
-  BN_CTX_start(ctx);
-  t = BN_CTX_get(ctx);
+  bssl::BN_CTXScope scope(ctx.get());
+  t = BN_CTX_get(ctx.get());
   if (!t) {
-    goto err;
+    return 0;
   }
 
 loop:
   // make a random number and set the top and bottom bits
   if (add == NULL) {
     if (!probable_prime(ret, bits)) {
-      goto err;
+      return 0;
     }
   } else {
     if (safe) {
-      if (!probable_prime_dh_safe(ret, bits, add, rem, ctx)) {
-        goto err;
+      if (!probable_prime_dh_safe(ret, bits, add, rem, ctx.get())) {
+        return 0;
       }
     } else {
-      if (!probable_prime_dh(ret, bits, add, rem, ctx)) {
-        goto err;
+      if (!probable_prime_dh(ret, bits, add, rem, ctx.get())) {
+        return 0;
       }
     }
   }
 
   if (!BN_GENCB_call(cb, BN_GENCB_GENERATED, c1++)) {
     // aborted
-    goto err;
+    return 0;
   }
 
   if (!safe) {
-    i = BN_is_prime_fasttest_ex(ret, checks, ctx, 0, cb);
+    i = BN_is_prime_fasttest_ex(ret, checks, ctx.get(), 0, cb);
     if (i == -1) {
-      goto err;
+      return 0;
     } else if (i == 0) {
       goto loop;
     }
@@ -349,7 +347,7 @@
     // for "safe prime" generation, check that (p-1)/2 is prime. Since a prime
     // is odd, We just need to divide by 2
     if (!BN_rshift1(t, ret)) {
-      goto err;
+      return 0;
     }
 
     // Interleave |ret| and |t|'s primality tests to avoid paying the full
@@ -358,37 +356,29 @@
     // TODO(davidben): This doesn't quite work because an iteration count of 1
     // still runs the blinding mechanism.
     for (i = 0; i < checks; i++) {
-      j = BN_is_prime_fasttest_ex(ret, 1, ctx, 0, NULL);
+      j = BN_is_prime_fasttest_ex(ret, 1, ctx.get(), 0, NULL);
       if (j == -1) {
-        goto err;
+        return 0;
       } else if (j == 0) {
         goto loop;
       }
 
-      j = BN_is_prime_fasttest_ex(t, 1, ctx, 0, NULL);
+      j = BN_is_prime_fasttest_ex(t, 1, ctx.get(), 0, NULL);
       if (j == -1) {
-        goto err;
+        return 0;
       } else if (j == 0) {
         goto loop;
       }
 
       if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, i)) {
-        goto err;
+        return 0;
       }
       // We have a safe prime test pass
     }
   }
 
   // we have a prime :-)
-  found = 1;
-
-err:
-  if (ctx != NULL) {
-    BN_CTX_end(ctx);
-    BN_CTX_free(ctx);
-  }
-
-  return found;
+  return 1;
 }
 
 static int bn_trial_division(uint16_t *out, const BIGNUM *bn) {
@@ -453,8 +443,7 @@
                               int *out_is_possibly_prime, const BIGNUM *b,
                               const BN_MONT_CTX *mont, BN_CTX *ctx) {
   // This function corresponds to steps 4.3 through 4.5 of FIPS 186-4, C.3.1.
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
 
   // Step 4.3. We use Montgomery-encoding for better performance and to avoid
   // timing leaks.
@@ -464,7 +453,7 @@
   if (z == NULL ||
       !BN_mod_exp_mont_consttime(z, b, miller_rabin->m, w, ctx, mont) ||
       !BN_to_montgomery(z, z, mont, ctx)) {
-    goto err;
+    return 0;
   }
 
   // is_possibly_prime is all ones if we have determined |b| is not a composite
@@ -493,7 +482,7 @@
 
     // Step 4.5.1.
     if (!BN_mod_mul_montgomery(z, z, z, mont, ctx)) {
-      goto err;
+      return 0;
     }
 
     // Step 4.5.2. If z = w-1 and the loop is not done, this is not a composite
@@ -513,11 +502,7 @@
   }
 
   *out_is_possibly_prime = constant_time_declassify_w(is_possibly_prime) & 1;
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 int BN_primality_test(int *out_is_probably_prime, const BIGNUM *w, int checks,
@@ -598,26 +583,25 @@
     checks = BN_prime_checks_for_size(BN_num_bits(w));
   }
 
-  BN_CTX *new_ctx = NULL;
-  if (ctx == NULL) {
-    new_ctx = BN_CTX_new();
-    if (new_ctx == NULL) {
+  bssl::UniquePtr<BN_CTX> new_ctx;
+  if (ctx == nullptr) {
+    new_ctx.reset(BN_CTX_new());
+    if (new_ctx == nullptr) {
       return 0;
     }
-    ctx = new_ctx;
+    ctx = new_ctx.get();
   }
 
   // See C.3.1 from FIPS 186-4.
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *b = BN_CTX_get(ctx);
-  BN_MONT_CTX *mont = BN_MONT_CTX_new_consttime(w, ctx);
+  bssl::UniquePtr<BN_MONT_CTX> mont(BN_MONT_CTX_new_consttime(w, ctx));
   BN_MILLER_RABIN miller_rabin;
   crypto_word_t uniform_iterations = 0;
-  if (b == NULL || mont == NULL ||
+  if (b == nullptr || mont == nullptr ||
       // Steps 1-3.
-      !bn_miller_rabin_init(&miller_rabin, mont, ctx)) {
-    goto err;
+      !bn_miller_rabin_init(&miller_rabin, mont.get(), ctx)) {
+    return 0;
   }
 
   // The following loop performs in inner iteration of the Miller-Rabin
@@ -657,39 +641,32 @@
     // Step 4.1-4.2
     int is_uniform;
     if (!bn_rand_secret_range(b, &is_uniform, 2, miller_rabin.w1)) {
-      goto err;
+      return 0;
     }
     uniform_iterations += is_uniform;
 
     // Steps 4.3-4.5
     int is_possibly_prime = 0;
-    if (!bn_miller_rabin_iteration(&miller_rabin, &is_possibly_prime, b, mont,
-                                   ctx)) {
-      goto err;
+    if (!bn_miller_rabin_iteration(&miller_rabin, &is_possibly_prime, b,
+                                   mont.get(), ctx)) {
+      return 0;
     }
 
     if (!is_possibly_prime) {
       // Step 4.6. We did not see z = w-1 before z = 1, so w must be composite.
       *out_is_probably_prime = 0;
-      ret = 1;
-      goto err;
+      return 1;
     }
 
     // Step 4.7
     if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, i - 1)) {
-      goto err;
+      return 0;
     }
   }
 
   declassify_assert(uniform_iterations >= (crypto_word_t)checks);
   *out_is_probably_prime = 1;
-  ret = 1;
-
-err:
-  BN_MONT_CTX_free(mont);
-  BN_CTX_end(ctx);
-  BN_CTX_free(new_ctx);
-  return ret;
+  return 1;
 }
 
 int BN_is_prime_ex(const BIGNUM *candidate, int checks, BN_CTX *ctx,
@@ -720,41 +697,36 @@
     checks = BN_prime_checks_for_size(BN_num_bits(w));
   }
 
-  int ret = 0;
-  BN_MONT_CTX *mont = NULL;
-
-  BN_CTX_start(ctx);
-
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *w1 = BN_CTX_get(ctx);
-  BIGNUM *b, *g, *z, *x, *x1, *m;
-  int a;
-  if (w1 == NULL || !BN_copy(w1, w) || !BN_sub_word(w1, 1)) {
-    goto err;
+  if (w1 == nullptr || !BN_copy(w1, w) || !BN_sub_word(w1, 1)) {
+    return 0;
   }
 
   // Write w1 as m*2^a (Steps 1 and 2).
-  a = 0;
+  int a = 0;
   while (!BN_is_bit_set(w1, a)) {
     a++;
   }
-  m = BN_CTX_get(ctx);
-  if (m == NULL || !BN_rshift(m, w1, a)) {
-    goto err;
+  BIGNUM *m = BN_CTX_get(ctx);
+  if (m == nullptr || !BN_rshift(m, w1, a)) {
+    return 0;
   }
 
-  b = BN_CTX_get(ctx);
-  g = BN_CTX_get(ctx);
-  z = BN_CTX_get(ctx);
-  x = BN_CTX_get(ctx);
-  x1 = BN_CTX_get(ctx);
-  if (b == NULL || g == NULL || z == NULL || x == NULL || x1 == NULL) {
-    goto err;
+  BIGNUM *b = BN_CTX_get(ctx);
+  BIGNUM *g = BN_CTX_get(ctx);
+  BIGNUM *z = BN_CTX_get(ctx);
+  BIGNUM *x = BN_CTX_get(ctx);
+  BIGNUM *x1 = BN_CTX_get(ctx);
+  if (b == nullptr || g == nullptr || z == nullptr || x == nullptr ||
+      x1 == nullptr) {
+    return 0;
   }
 
   // Montgomery setup for computations mod w
-  mont = BN_MONT_CTX_new_for_modulus(w, ctx);
-  if (mont == NULL) {
-    goto err;
+  bssl::UniquePtr<BN_MONT_CTX> mont(BN_MONT_CTX_new_for_modulus(w, ctx));
+  if (mont == nullptr) {
+    return 0;
   }
 
   // The following loop performs in inner iteration of the Enhanced Miller-Rabin
@@ -762,22 +734,21 @@
   for (int i = 1; i <= checks; i++) {
     // Step 4.1-4.2
     if (!BN_rand_range_ex(b, 2, w1)) {
-      goto err;
+      return 0;
     }
 
     // Step 4.3-4.4
     if (!BN_gcd(g, b, w, ctx)) {
-      goto err;
+      return 0;
     }
     if (BN_cmp_word(g, 1) > 0) {
       *out_result = bn_composite;
-      ret = 1;
-      goto err;
+      return 1;
     }
 
     // Step 4.5
-    if (!BN_mod_exp_mont(z, b, m, w, ctx, mont)) {
-      goto err;
+    if (!BN_mod_exp_mont(z, b, m, w, ctx, mont.get())) {
+      return 0;
     }
 
     // Step 4.6
@@ -788,7 +759,7 @@
     // Step 4.7
     for (int j = 1; j < a; j++) {
       if (!BN_copy(x, z) || !BN_mod_mul(z, x, x, w, ctx)) {
-        goto err;
+        return 0;
       }
       if (BN_cmp(z, w1) == 0) {
         goto loop;
@@ -800,18 +771,18 @@
 
     // Step 4.8-4.9
     if (!BN_copy(x, z) || !BN_mod_mul(z, x, x, w, ctx)) {
-      goto err;
+      return 0;
     }
 
     // Step 4.10-4.11
     if (!BN_is_one(z) && !BN_copy(x, z)) {
-      goto err;
+      return 0;
     }
 
   composite:
     // Step 4.12-4.14
     if (!BN_copy(x1, x) || !BN_sub_word(x1, 1) || !BN_gcd(g, x1, w, ctx)) {
-      goto err;
+      return 0;
     }
     if (BN_cmp_word(g, 1) > 0) {
       *out_result = bn_composite;
@@ -819,24 +790,17 @@
       *out_result = bn_non_prime_power_composite;
     }
 
-    ret = 1;
-    goto err;
+    return 1;
 
   loop:
     // Step 4.15
     if (!BN_GENCB_call(cb, BN_GENCB_PRIME_TEST, i - 1)) {
-      goto err;
+      return 0;
     }
   }
 
   *out_result = bn_probably_prime;
-  ret = 1;
-
-err:
-  BN_MONT_CTX_free(mont);
-  BN_CTX_end(ctx);
-
-  return ret;
+  return 1;
 }
 
 static int probable_prime(BIGNUM *rnd, int bits) {
@@ -850,111 +814,99 @@
 
 static int probable_prime_dh(BIGNUM *rnd, int bits, const BIGNUM *add,
                              const BIGNUM *rem, BN_CTX *ctx) {
-  int ret = 0;
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *t1;
-
-  BN_CTX_start(ctx);
-  size_t num_primes;
-  if ((t1 = BN_CTX_get(ctx)) == NULL) {
-    goto err;
+  if ((t1 = BN_CTX_get(ctx)) == nullptr) {
+    return 0;
   }
 
   if (!BN_rand(rnd, bits, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ODD)) {
-    goto err;
+    return 0;
   }
 
   // we need ((rnd-rem) % add) == 0
-
   if (!BN_mod(t1, rnd, add, ctx)) {
-    goto err;
+    return 0;
   }
   if (!BN_sub(rnd, rnd, t1)) {
-    goto err;
+    return 0;
   }
-  if (rem == NULL) {
+  if (rem == nullptr) {
     if (!BN_add_word(rnd, 1)) {
-      goto err;
+      return 0;
     }
   } else {
     if (!BN_add(rnd, rnd, rem)) {
-      goto err;
+      return 0;
     }
   }
   // we now have a random number 'rand' to test.
 
-  num_primes = num_trial_division_primes(rnd);
+  size_t num_primes = num_trial_division_primes(rnd);
 loop:
   for (size_t i = 1; i < num_primes; i++) {
     // check that rnd is a prime
     if (bn_mod_u16_consttime(rnd, kPrimes[i]) <= 1) {
       if (!BN_add(rnd, rnd, add)) {
-        goto err;
+        return 0;
       }
       goto loop;
     }
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 static int probable_prime_dh_safe(BIGNUM *p, int bits, const BIGNUM *padd,
                                   const BIGNUM *rem, BN_CTX *ctx) {
-  int ret = 0;
-  BIGNUM *t1, *qadd, *q;
-
   bits--;
-  BN_CTX_start(ctx);
-  t1 = BN_CTX_get(ctx);
-  q = BN_CTX_get(ctx);
-  qadd = BN_CTX_get(ctx);
-  size_t num_primes;
+  bssl::BN_CTXScope scope(ctx);
+  BIGNUM *t1 = BN_CTX_get(ctx);
+  BIGNUM *q = BN_CTX_get(ctx);
+  BIGNUM *qadd = BN_CTX_get(ctx);
   if (qadd == NULL) {
-    goto err;
+    return 0;
   }
 
   if (!BN_rshift1(qadd, padd)) {
-    goto err;
+    return 0;
   }
 
   if (!BN_rand(q, bits, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ODD)) {
-    goto err;
+    return 0;
   }
 
   // we need ((rnd-rem) % add) == 0
   if (!BN_mod(t1, q, qadd, ctx)) {
-    goto err;
+    return 0;
   }
 
   if (!BN_sub(q, q, t1)) {
-    goto err;
+    return 0;
   }
 
   if (rem == NULL) {
     if (!BN_add_word(q, 1)) {
-      goto err;
+      return 0;
     }
   } else {
     if (!BN_rshift1(t1, rem)) {
-      goto err;
+      return 0;
     }
     if (!BN_add(q, q, t1)) {
-      goto err;
+      return 0;
     }
   }
 
   // we now have a random number 'rand' to test.
   if (!BN_lshift1(p, q)) {
-    goto err;
+    return 0;
   }
   if (!BN_add_word(p, 1)) {
-    goto err;
+    return 0;
   }
 
-  num_primes = num_trial_division_primes(p);
+  size_t num_primes = num_trial_division_primes(p);
 loop:
   for (size_t i = 1; i < num_primes; i++) {
     // check that p and q are prime
@@ -963,18 +915,14 @@
     if (bn_mod_u16_consttime(p, kPrimes[i]) == 0 ||
         bn_mod_u16_consttime(q, kPrimes[i]) == 0) {
       if (!BN_add(p, p, padd)) {
-        goto err;
+        return 0;
       }
       if (!BN_add(q, q, qadd)) {
-        goto err;
+        return 0;
       }
       goto loop;
     }
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
diff --git a/crypto/fipsmodule/bn/shift.cc.inc b/crypto/fipsmodule/bn/shift.cc.inc
index 2739c5a..491c796 100644
--- a/crypto/fipsmodule/bn/shift.cc.inc
+++ b/crypto/fipsmodule/bn/shift.cc.inc
@@ -129,12 +129,11 @@
 
 int bn_rshift_secret_shift(BIGNUM *r, const BIGNUM *a, unsigned n,
                            BN_CTX *ctx) {
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
   unsigned max_bits;
   if (tmp == NULL || !BN_copy(r, a) || !bn_wexpand(tmp, r->width)) {
-    goto err;
+    return 0;
   }
 
   // Shift conditionally by powers of two.
@@ -147,11 +146,7 @@
                     r->d /* ignore shift */, r->width);
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 void bn_rshift1_words(BN_ULONG *r, const BN_ULONG *a, size_t num) {
diff --git a/crypto/fipsmodule/bn/sqrt.cc.inc b/crypto/fipsmodule/bn/sqrt.cc.inc
index cb70f19..56d89e0 100644
--- a/crypto/fipsmodule/bn/sqrt.cc.inc
+++ b/crypto/fipsmodule/bn/sqrt.cc.inc
@@ -63,7 +63,7 @@
     return ret;
   }
 
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   A = BN_CTX_get(ctx);
   b = BN_CTX_get(ctx);
   q = BN_CTX_get(ctx);
@@ -378,82 +378,5 @@
     }
     ret = NULL;
   }
-  BN_CTX_end(ctx);
   return ret;
 }
-
-int BN_sqrt(BIGNUM *out_sqrt, const BIGNUM *in, BN_CTX *ctx) {
-  BIGNUM *estimate, *tmp, *delta, *last_delta, *tmp2;
-  int ok = 0, last_delta_valid = 0;
-
-  if (in->neg) {
-    OPENSSL_PUT_ERROR(BN, BN_R_NEGATIVE_NUMBER);
-    return 0;
-  }
-  if (BN_is_zero(in)) {
-    BN_zero(out_sqrt);
-    return 1;
-  }
-
-  BN_CTX_start(ctx);
-  if (out_sqrt == in) {
-    estimate = BN_CTX_get(ctx);
-  } else {
-    estimate = out_sqrt;
-  }
-  tmp = BN_CTX_get(ctx);
-  last_delta = BN_CTX_get(ctx);
-  delta = BN_CTX_get(ctx);
-  if (estimate == NULL || tmp == NULL || last_delta == NULL || delta == NULL) {
-    goto err;
-  }
-
-  // We estimate that the square root of an n-bit number is 2^{n/2}.
-  if (!BN_lshift(estimate, BN_value_one(), BN_num_bits(in)/2)) {
-    goto err;
-  }
-
-  // This is Newton's method for finding a root of the equation |estimate|^2 -
-  // |in| = 0.
-  for (;;) {
-    // |estimate| = 1/2 * (|estimate| + |in|/|estimate|)
-    if (!BN_div(tmp, NULL, in, estimate, ctx) ||
-        !BN_add(tmp, tmp, estimate) ||
-        !BN_rshift1(estimate, tmp) ||
-        // |tmp| = |estimate|^2
-        !BN_sqr(tmp, estimate, ctx) ||
-        // |delta| = |in| - |tmp|
-        !BN_sub(delta, in, tmp)) {
-      OPENSSL_PUT_ERROR(BN, ERR_R_BN_LIB);
-      goto err;
-    }
-
-    delta->neg = 0;
-    // The difference between |in| and |estimate| squared is required to always
-    // decrease. This ensures that the loop always terminates, but I don't have
-    // a proof that it always finds the square root for a given square.
-    if (last_delta_valid && BN_cmp(delta, last_delta) >= 0) {
-      break;
-    }
-
-    last_delta_valid = 1;
-
-    tmp2 = last_delta;
-    last_delta = delta;
-    delta = tmp2;
-  }
-
-  if (BN_cmp(tmp, in) != 0) {
-    OPENSSL_PUT_ERROR(BN, BN_R_NOT_A_SQUARE);
-    goto err;
-  }
-
-  ok = 1;
-
-err:
-  if (ok && out_sqrt == in && !BN_copy(out_sqrt, estimate)) {
-    ok = 0;
-  }
-  BN_CTX_end(ctx);
-  return ok;
-}
diff --git a/crypto/fipsmodule/dh/check.cc.inc b/crypto/fipsmodule/dh/check.cc.inc
index c15b858..4d023d5 100644
--- a/crypto/fipsmodule/dh/check.cc.inc
+++ b/crypto/fipsmodule/dh/check.cc.inc
@@ -55,13 +55,11 @@
     return 0;
   }
 
-  BN_CTX *ctx = BN_CTX_new();
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
   if (ctx == NULL) {
     return 0;
   }
-  BN_CTX_start(ctx);
-
-  int ok = 0;
+  bssl::BN_CTXScope scope(ctx.get());
 
   // Check |pub_key| is greater than 1.
   if (BN_cmp(pub_key, BN_value_one()) <= 0) {
@@ -69,11 +67,11 @@
   }
 
   // Check |pub_key| is less than |dh->p| - 1.
-  BIGNUM *tmp = BN_CTX_get(ctx);
+  BIGNUM *tmp = BN_CTX_get(ctx.get());
   if (tmp == NULL ||
       !BN_copy(tmp, dh->p) ||
       !BN_sub_word(tmp, 1)) {
-    goto err;
+    return 0;
   }
   if (BN_cmp(pub_key, tmp) >= 0) {
     *out_flags |= DH_CHECK_PUBKEY_TOO_LARGE;
@@ -83,23 +81,17 @@
     // Check |pub_key|^|dh->q| is 1 mod |dh->p|. This is necessary for RFC 5114
     // groups which are not safe primes but pick a generator on a prime-order
     // subgroup of size |dh->q|.
-    if (!BN_mod_exp_mont(tmp, pub_key, dh->q, dh->p, ctx, NULL)) {
-      goto err;
+    if (!BN_mod_exp_mont(tmp, pub_key, dh->q, dh->p, ctx.get(), NULL)) {
+      return 0;
     }
     if (!BN_is_one(tmp)) {
       *out_flags |= DH_CHECK_PUBKEY_INVALID;
     }
   }
 
-  ok = 1;
-
-err:
-  BN_CTX_end(ctx);
-  BN_CTX_free(ctx);
-  return ok;
+  return 1;
 }
 
-
 int DH_check(const DH *dh, int *out_flags) {
   *out_flags = 0;
   if (!dh_check_params_fast(dh)) {
@@ -112,23 +104,18 @@
   //   for 3, p mod 12 == 5
   //   for 5, p mod 10 == 3 or 7
   // should hold.
-  int ok = 0, r;
-  BN_CTX *ctx = NULL;
-  BN_ULONG l;
-  BIGNUM *t1 = NULL, *t2 = NULL;
-
-  ctx = BN_CTX_new();
-  if (ctx == NULL) {
-    goto err;
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
+    return 0;
   }
-  BN_CTX_start(ctx);
-  t1 = BN_CTX_get(ctx);
-  if (t1 == NULL) {
-    goto err;
+  bssl::BN_CTXScope scope(ctx.get());
+  BIGNUM *t1 = BN_CTX_get(ctx.get());
+  if (t1 == nullptr) {
+    return 0;
   }
-  t2 = BN_CTX_get(ctx);
-  if (t2 == NULL) {
-    goto err;
+  BIGNUM *t2 = BN_CTX_get(ctx.get());
+  if (t2 == nullptr) {
+    return 0;
   }
 
   if (dh->q) {
@@ -138,39 +125,40 @@
       *out_flags |= DH_CHECK_NOT_SUITABLE_GENERATOR;
     } else {
       // Check g^q == 1 mod p
-      if (!BN_mod_exp_mont(t1, dh->g, dh->q, dh->p, ctx, NULL)) {
-        goto err;
+      if (!BN_mod_exp_mont(t1, dh->g, dh->q, dh->p, ctx.get(), nullptr)) {
+        return 0;
       }
       if (!BN_is_one(t1)) {
         *out_flags |= DH_CHECK_NOT_SUITABLE_GENERATOR;
       }
     }
-    r = BN_is_prime_ex(dh->q, BN_prime_checks_for_validation, ctx, NULL);
+    int r = BN_is_prime_ex(dh->q, BN_prime_checks_for_validation, ctx.get(),
+                           nullptr);
     if (r < 0) {
-      goto err;
+      return 0;
     }
     if (!r) {
       *out_flags |= DH_CHECK_Q_NOT_PRIME;
     }
     // Check p == 1 mod q  i.e. q divides p - 1
-    if (!BN_div(t1, t2, dh->p, dh->q, ctx)) {
-      goto err;
+    if (!BN_div(t1, t2, dh->p, dh->q, ctx.get())) {
+      return 0;
     }
     if (!BN_is_one(t2)) {
       *out_flags |= DH_CHECK_INVALID_Q_VALUE;
     }
   } else if (BN_is_word(dh->g, DH_GENERATOR_2)) {
-    l = BN_mod_word(dh->p, 24);
+    BN_ULONG l = BN_mod_word(dh->p, 24);
     if (l == (BN_ULONG)-1) {
-      goto err;
+      return 0;
     }
     if (l != 11) {
       *out_flags |= DH_CHECK_NOT_SUITABLE_GENERATOR;
     }
   } else if (BN_is_word(dh->g, DH_GENERATOR_5)) {
-    l = BN_mod_word(dh->p, 10);
+    BN_ULONG l = BN_mod_word(dh->p, 10);
     if (l == (BN_ULONG)-1) {
-      goto err;
+      return 0;
     }
     if (l != 3 && l != 7) {
       *out_flags |= DH_CHECK_NOT_SUITABLE_GENERATOR;
@@ -179,30 +167,24 @@
     *out_flags |= DH_CHECK_UNABLE_TO_CHECK_GENERATOR;
   }
 
-  r = BN_is_prime_ex(dh->p, BN_prime_checks_for_validation, ctx, NULL);
+  int r =
+      BN_is_prime_ex(dh->p, BN_prime_checks_for_validation, ctx.get(), nullptr);
   if (r < 0) {
-    goto err;
+    return 0;
   }
   if (!r) {
     *out_flags |= DH_CHECK_P_NOT_PRIME;
   } else if (!dh->q) {
     if (!BN_rshift1(t1, dh->p)) {
-      goto err;
+      return 0;
     }
-    r = BN_is_prime_ex(t1, BN_prime_checks_for_validation, ctx, NULL);
+    r = BN_is_prime_ex(t1, BN_prime_checks_for_validation, ctx.get(), nullptr);
     if (r < 0) {
-      goto err;
+      return 0;
     }
     if (!r) {
       *out_flags |= DH_CHECK_P_NOT_SAFE_PRIME;
     }
   }
-  ok = 1;
-
-err:
-  if (ctx != NULL) {
-    BN_CTX_end(ctx);
-    BN_CTX_free(ctx);
-  }
-  return ok;
+  return 1;
 }
diff --git a/crypto/fipsmodule/dh/dh.cc.inc b/crypto/fipsmodule/dh/dh.cc.inc
index 6f1267f..0a62215 100644
--- a/crypto/fipsmodule/dh/dh.cc.inc
+++ b/crypto/fipsmodule/dh/dh.cc.inc
@@ -147,28 +147,27 @@
   }
 
   int ok = 0;
-  int generate_new_key = 0;
-  BN_CTX *ctx = NULL;
-  BIGNUM *pub_key = NULL, *priv_key = NULL, *priv_key_limit = NULL;
+  bool generate_new_key = false;
+  BIGNUM *pub_key = nullptr, *priv_key = nullptr;
 
-  ctx = BN_CTX_new();
-  if (ctx == NULL) {
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
     goto err;
   }
 
-  if (dh->priv_key == NULL) {
+  if (dh->priv_key == nullptr) {
     priv_key = BN_new();
-    if (priv_key == NULL) {
+    if (priv_key == nullptr) {
       goto err;
     }
-    generate_new_key = 1;
+    generate_new_key = true;
   } else {
     priv_key = dh->priv_key;
   }
 
-  if (dh->pub_key == NULL) {
+  if (dh->pub_key == nullptr) {
     pub_key = BN_new();
-    if (pub_key == NULL) {
+    if (pub_key == nullptr) {
       goto err;
     }
   } else {
@@ -176,7 +175,7 @@
   }
 
   if (!BN_MONT_CTX_set_locked(&dh->method_mont_p, &dh->method_mont_p_lock,
-                              dh->p, ctx)) {
+                              dh->p, ctx.get())) {
     goto err;
   }
 
@@ -202,30 +201,30 @@
       // clamp |dh->priv_length| before invoking the algorithm.
 
       // Compute M = min(2^N, q).
-      priv_key_limit = BN_new();
-      if (priv_key_limit == NULL) {
+      bssl::UniquePtr<BIGNUM> priv_key_limit(BN_new());
+      if (priv_key_limit == nullptr) {
         goto err;
       }
       if (dh->priv_length == 0 || dh->priv_length >= BN_num_bits(dh->p) - 1) {
         // M = q = (p - 1) / 2.
-        if (!BN_rshift1(priv_key_limit, dh->p)) {
+        if (!BN_rshift1(priv_key_limit.get(), dh->p)) {
           goto err;
         }
       } else {
         // M = 2^N.
-        if (!BN_set_bit(priv_key_limit, dh->priv_length)) {
+        if (!BN_set_bit(priv_key_limit.get(), dh->priv_length)) {
           goto err;
         }
       }
 
       // Choose a private key uniformly from [1, M-1].
-      if (!BN_rand_range_ex(priv_key, 1, priv_key_limit)) {
+      if (!BN_rand_range_ex(priv_key, 1, priv_key_limit.get())) {
         goto err;
       }
     }
   }
 
-  if (!BN_mod_exp_mont_consttime(pub_key, dh->g, priv_key, dh->p, ctx,
+  if (!BN_mod_exp_mont_consttime(pub_key, dh->g, priv_key, dh->p, ctx.get(),
                                  dh->method_mont_p)) {
     goto err;
   }
@@ -239,14 +238,12 @@
     OPENSSL_PUT_ERROR(DH, ERR_R_BN_LIB);
   }
 
-  if (dh->pub_key == NULL) {
+  if (dh->pub_key == nullptr) {
     BN_free(pub_key);
   }
-  if (dh->priv_key == NULL) {
+  if (dh->priv_key == nullptr) {
     BN_free(priv_key);
   }
-  BN_free(priv_key_limit);
-  BN_CTX_free(ctx);
   return ok;
 }
 
@@ -267,56 +264,46 @@
     return 0;
   }
 
-  int ret = 0;
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *p_minus_1 = BN_CTX_get(ctx);
-
   if (!p_minus_1 ||
       !BN_MONT_CTX_set_locked(&dh->method_mont_p, &dh->method_mont_p_lock,
                               dh->p, ctx)) {
-    goto err;
+    return 0;
   }
 
   if (!BN_mod_exp_mont_consttime(out_shared_key, peers_key, dh->priv_key, dh->p,
                                  ctx, dh->method_mont_p) ||
       !BN_copy(p_minus_1, dh->p) || !BN_sub_word(p_minus_1, 1)) {
     OPENSSL_PUT_ERROR(DH, ERR_R_BN_LIB);
-    goto err;
+    return 0;
   }
 
   // This performs the check required by SP 800-56Ar3 section 5.7.1.1 step two.
   if (BN_cmp_word(out_shared_key, 1) <= 0 ||
       BN_cmp(out_shared_key, p_minus_1) == 0) {
     OPENSSL_PUT_ERROR(DH, DH_R_INVALID_PUBKEY);
-    goto err;
+    return 0;
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 int dh_compute_key_padded_no_self_test(unsigned char *out,
                                        const BIGNUM *peers_key, DH *dh) {
-  BN_CTX *ctx = BN_CTX_new();
-  if (ctx == NULL) {
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
     return -1;
   }
-  BN_CTX_start(ctx);
-
+  bssl::BN_CTXScope scope(ctx.get());
   int dh_size = DH_size(dh);
-  int ret = -1;
-  BIGNUM *shared_key = BN_CTX_get(ctx);
-  if (shared_key && dh_compute_key(dh, shared_key, peers_key, ctx) &&
-      BN_bn2bin_padded(out, dh_size, shared_key)) {
-    ret = dh_size;
+  BIGNUM *shared_key = BN_CTX_get(ctx.get());
+  if (shared_key == nullptr ||
+      !dh_compute_key(dh, shared_key, peers_key, ctx.get()) ||
+      !BN_bn2bin_padded(out, dh_size, shared_key)) {
+    return -1;
   }
-
-  BN_CTX_end(ctx);
-  BN_CTX_free(ctx);
-  return ret;
+  return dh_size;
 }
 
 int DH_compute_key_padded(unsigned char *out, const BIGNUM *peers_key, DH *dh) {
@@ -328,22 +315,18 @@
 int DH_compute_key(unsigned char *out, const BIGNUM *peers_key, DH *dh) {
   boringssl_ensure_ffdh_self_test();
 
-  BN_CTX *ctx = BN_CTX_new();
-  if (ctx == NULL) {
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
     return -1;
   }
-  BN_CTX_start(ctx);
-
-  int ret = -1;
-  BIGNUM *shared_key = BN_CTX_get(ctx);
-  if (shared_key && dh_compute_key(dh, shared_key, peers_key, ctx)) {
-    // A |BIGNUM|'s byte count fits in |int|.
-    ret = (int)BN_bn2bin(shared_key, out);
+  bssl::BN_CTXScope scope(ctx.get());
+  BIGNUM *shared_key = BN_CTX_get(ctx.get());
+  if (shared_key == nullptr ||
+      !dh_compute_key(dh, shared_key, peers_key, ctx.get())) {
+    return -1;
   }
-
-  BN_CTX_end(ctx);
-  BN_CTX_free(ctx);
-  return ret;
+  // A |BIGNUM|'s byte count fits in |int|.
+  return static_cast<int>(BN_bn2bin(shared_key, out));
 }
 
 int DH_compute_key_hashed(DH *dh, uint8_t *out, size_t *out_len,
@@ -415,29 +398,27 @@
       TOBN(0xadf85458, 0xa2bb4a9a), TOBN(0xffffffff, 0xffffffff),
   };
 
-  BIGNUM *const ffdhe2048_p = BN_new();
-  BIGNUM *const ffdhe2048_q = BN_new();
-  BIGNUM *const ffdhe2048_g = BN_new();
-  DH *const dh = DH_new();
-
+  bssl::UniquePtr<BIGNUM> ffdhe2048_p(BN_new());
+  bssl::UniquePtr<BIGNUM> ffdhe2048_q(BN_new());
+  bssl::UniquePtr<BIGNUM> ffdhe2048_g(BN_new());
+  bssl::UniquePtr<DH> dh(DH_new());
   if (!ffdhe2048_p || !ffdhe2048_q || !ffdhe2048_g || !dh) {
-    goto err;
+    return nullptr;
   }
 
-  bn_set_static_words(ffdhe2048_p, kFFDHE2048Data,
+  bn_set_static_words(ffdhe2048_p.get(), kFFDHE2048Data,
                       OPENSSL_ARRAY_SIZE(kFFDHE2048Data));
 
-  if (!BN_rshift1(ffdhe2048_q, ffdhe2048_p) || !BN_set_word(ffdhe2048_g, 2) ||
-      !DH_set0_pqg(dh, ffdhe2048_p, ffdhe2048_q, ffdhe2048_g)) {
-    goto err;
+  if (!BN_rshift1(ffdhe2048_q.get(), ffdhe2048_p.get()) ||
+      !BN_set_word(ffdhe2048_g.get(), 2) ||
+      !DH_set0_pqg(dh.get(), ffdhe2048_p.get(), ffdhe2048_q.get(),
+                   ffdhe2048_g.get())) {
+    return nullptr;
   }
+  // |DH_set0_pqg| takes ownership on success.
+  ffdhe2048_p.release();
+  ffdhe2048_q.release();
+  ffdhe2048_g.release();
 
-  return dh;
-
-err:
-  BN_free(ffdhe2048_p);
-  BN_free(ffdhe2048_q);
-  BN_free(ffdhe2048_g);
-  DH_free(dh);
-  return NULL;
+  return dh.release();
 }
diff --git a/crypto/fipsmodule/ec/ec.cc.inc b/crypto/fipsmodule/ec/ec.cc.inc
index f1409d9..14916ee 100644
--- a/crypto/fipsmodule/ec/ec.cc.inc
+++ b/crypto/fipsmodule/ec/ec.cc.inc
@@ -655,12 +655,10 @@
   ERR_clear_error();
 
   // This is an unusual input, so we do not guarantee constant-time processing.
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
-  int ok = tmp != NULL && BN_nnmod(tmp, in, EC_GROUP_get0_order(group), ctx) &&
-           ec_bignum_to_scalar(group, out, tmp);
-  BN_CTX_end(ctx);
-  return ok;
+  return tmp != nullptr && BN_nnmod(tmp, in, EC_GROUP_get0_order(group), ctx) &&
+         ec_bignum_to_scalar(group, out, tmp);
 }
 
 int ec_point_mul_no_self_test(const EC_GROUP *group, EC_POINT *r,
diff --git a/crypto/fipsmodule/rsa/rsa.cc.inc b/crypto/fipsmodule/rsa/rsa.cc.inc
index 0240e8a..ebcd558 100644
--- a/crypto/fipsmodule/rsa/rsa.cc.inc
+++ b/crypto/fipsmodule/rsa/rsa.cc.inc
@@ -718,15 +718,15 @@
   // Note |bn_mul_consttime| and |bn_div_consttime| do not scale linearly, but
   // checking |ainv| is in range bounds the running time, assuming |m|'s bounds
   // were checked by the caller.
-  BN_CTX_start(ctx);
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
-  int ret = tmp != NULL && bn_mul_consttime(tmp, a, ainv, ctx) &&
-            bn_div_consttime(NULL, tmp, tmp, m, m_min_bits, ctx);
-  if (ret) {
-    *out_ok = constant_time_declassify_int(BN_is_one(tmp));
+  if (tmp == nullptr ||  //
+      !bn_mul_consttime(tmp, a, ainv, ctx) ||
+      !bn_div_consttime(NULL, tmp, tmp, m, m_min_bits, ctx)) {
+    return 0;
   }
-  BN_CTX_end(ctx);
-  return ret;
+  *out_ok = constant_time_declassify_int(BN_is_one(tmp));
+  return 1;
 }
 
 int RSA_check_key(const RSA *key) {
diff --git a/crypto/fipsmodule/rsa/rsa_impl.cc.inc b/crypto/fipsmodule/rsa/rsa_impl.cc.inc
index 5aad8e9..e5c34f1 100644
--- a/crypto/fipsmodule/rsa/rsa_impl.cc.inc
+++ b/crypto/fipsmodule/rsa/rsa_impl.cc.inc
@@ -446,8 +446,6 @@
   }
 
   const unsigned rsa_size = RSA_size(rsa);
-  BIGNUM *f, *result;
-
   if (max_out < rsa_size) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_OUTPUT_BUFFER_TOO_SMALL);
     return 0;
@@ -458,18 +456,17 @@
     return 0;
   }
 
-  BN_CTX *ctx = BN_CTX_new();
-  if (ctx == NULL) {
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
     return 0;
   }
 
   int ret = 0;
-  uint8_t *buf = NULL;
-
-  BN_CTX_start(ctx);
-  f = BN_CTX_get(ctx);
-  result = BN_CTX_get(ctx);
-  if (f == NULL || result == NULL) {
+  uint8_t *buf = nullptr;
+  bssl::BN_CTXScope scope(ctx.get());
+  BIGNUM *f = BN_CTX_get(ctx.get());
+  BIGNUM *result = BN_CTX_get(ctx.get());
+  if (f == nullptr || result == nullptr) {
     goto err;
   }
 
@@ -478,12 +475,12 @@
   } else {
     // Allocate a temporary buffer to hold the padded plaintext.
     buf = reinterpret_cast<uint8_t *>(OPENSSL_malloc(rsa_size));
-    if (buf == NULL) {
+    if (buf == nullptr) {
       goto err;
     }
   }
 
-  if (BN_bin2bn(in, in_len, f) == NULL) {
+  if (BN_bin2bn(in, in_len, f) == nullptr) {
     goto err;
   }
 
@@ -492,8 +489,9 @@
     goto err;
   }
 
-  if (!BN_MONT_CTX_set_locked(&rsa->mont_n, &rsa->lock, rsa->n, ctx) ||
-      !BN_mod_exp_mont(result, f, rsa->e, &rsa->mont_n->N, ctx, rsa->mont_n)) {
+  if (!BN_MONT_CTX_set_locked(&rsa->mont_n, &rsa->lock, rsa->n, ctx.get()) ||
+      !BN_mod_exp_mont(result, f, rsa->e, &rsa->mont_n->N, ctx.get(),
+                       rsa->mont_n)) {
     goto err;
   }
 
@@ -522,8 +520,6 @@
   }
 
 err:
-  BN_CTX_end(ctx);
-  BN_CTX_free(ctx);
   if (buf != out) {
     OPENSSL_free(buf);
   }
@@ -539,32 +535,28 @@
 
 int rsa_default_private_transform(RSA *rsa, uint8_t *out, const uint8_t *in,
                                   size_t len) {
-  if (rsa->n == NULL || rsa->d == NULL) {
+  if (rsa->n == nullptr || rsa->d == nullptr) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_VALUE_MISSING);
     return 0;
   }
 
-  BIGNUM *f, *result;
-  BN_CTX *ctx = NULL;
-  size_t blinding_index = 0;
-  BN_BLINDING *blinding = NULL;
-  int ret = 0, do_blinding;
-
-  ctx = BN_CTX_new();
-  if (ctx == NULL) {
-    goto err;
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
+    return 0;
   }
-  BN_CTX_start(ctx);
-  f = BN_CTX_get(ctx);
-  result = BN_CTX_get(ctx);
-
-  if (f == NULL || result == NULL) {
+  size_t blinding_index = 0;
+  BN_BLINDING *blinding = nullptr;
+  int ret = 0, do_blinding;
+  bssl::BN_CTXScope scope(ctx.get());
+  BIGNUM *f = BN_CTX_get(ctx.get());
+  BIGNUM *result = BN_CTX_get(ctx.get());
+  if (f == nullptr || result == nullptr) {
     goto err;
   }
 
   // The caller should have ensured this.
   assert(len == BN_num_bytes(rsa->n));
-  if (BN_bin2bn(in, len, f) == NULL) {
+  if (BN_bin2bn(in, len, f) == nullptr) {
     goto err;
   }
 
@@ -576,7 +568,7 @@
     goto err;
   }
 
-  if (!freeze_private_key(rsa, ctx)) {
+  if (!freeze_private_key(rsa, ctx.get())) {
     OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
     goto err;
   }
@@ -584,7 +576,7 @@
   do_blinding =
       (rsa->flags & (RSA_FLAG_NO_BLINDING | RSA_FLAG_NO_PUBLIC_EXPONENT)) == 0;
 
-  if (rsa->e == NULL && do_blinding) {
+  if (rsa->e == nullptr && do_blinding) {
     // We cannot do blinding or verification without |e|, and continuing without
     // those countermeasures is dangerous. However, the Java/Android RSA API
     // requires support for keys where only |d| and |n| (and not |e|) are known.
@@ -598,29 +590,29 @@
   }
 
   if (do_blinding) {
-    blinding = rsa_blinding_get(rsa, &blinding_index, ctx);
-    if (blinding == NULL) {
+    blinding = rsa_blinding_get(rsa, &blinding_index, ctx.get());
+    if (blinding == nullptr) {
       OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
       goto err;
     }
-    if (!BN_BLINDING_convert(f, blinding, rsa->e, rsa->mont_n, ctx)) {
+    if (!BN_BLINDING_convert(f, blinding, rsa->e, rsa->mont_n, ctx.get())) {
       goto err;
     }
   }
 
-  if (rsa->p != NULL && rsa->q != NULL && rsa->e != NULL && rsa->dmp1 != NULL &&
-      rsa->dmq1 != NULL && rsa->iqmp != NULL &&
+  if (rsa->p != nullptr && rsa->q != nullptr && rsa->e != nullptr &&
+      rsa->dmp1 != nullptr && rsa->dmq1 != nullptr && rsa->iqmp != nullptr &&
       // Require that we can reduce |f| by |rsa->p| and |rsa->q| in constant
       // time, which requires primes be the same size, rounded to the Montgomery
       // coefficient. (See |mod_montgomery|.) This is not required by RFC 8017,
       // but it is true for keys generated by us and all common implementations.
       bn_less_than_montgomery_R(rsa->q, rsa->mont_p) &&
       bn_less_than_montgomery_R(rsa->p, rsa->mont_q)) {
-    if (!rsa_mod_exp_crt(result, f, rsa, ctx)) {
+    if (!rsa_mod_exp_crt(result, f, rsa, ctx.get())) {
       goto err;
     }
-  } else if (!BN_mod_exp_mont_consttime(result, f, rsa->d_fixed, rsa->n, ctx,
-                                        rsa->mont_n)) {
+  } else if (!BN_mod_exp_mont_consttime(result, f, rsa->d_fixed, rsa->n,
+                                        ctx.get(), rsa->mont_n)) {
     goto err;
   }
 
@@ -634,17 +626,19 @@
   //
   // This check is cheap assuming |e| is small, which we require in
   // |rsa_check_public_key|.
-  if (rsa->e != NULL) {
-    BIGNUM *vrfy = BN_CTX_get(ctx);
-    if (vrfy == NULL ||
-        !BN_mod_exp_mont(vrfy, result, rsa->e, rsa->n, ctx, rsa->mont_n) ||
+  if (rsa->e != nullptr) {
+    BIGNUM *vrfy = BN_CTX_get(ctx.get());
+    if (vrfy == nullptr ||
+        !BN_mod_exp_mont(vrfy, result, rsa->e, rsa->n, ctx.get(),
+                         rsa->mont_n) ||
         !constant_time_declassify_int(BN_equal_consttime(vrfy, f))) {
       OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
       goto err;
     }
   }
 
-  if (do_blinding && !BN_BLINDING_invert(result, blinding, rsa->mont_n, ctx)) {
+  if (do_blinding &&
+      !BN_BLINDING_invert(result, blinding, rsa->mont_n, ctx.get())) {
     goto err;
   }
 
@@ -663,11 +657,7 @@
   ret = 1;
 
 err:
-  if (ctx != NULL) {
-    BN_CTX_end(ctx);
-    BN_CTX_free(ctx);
-  }
-  if (blinding != NULL) {
+  if (blinding != nullptr) {
     rsa_blinding_release(rsa, blinding, blinding_index);
   }
 
@@ -717,23 +707,19 @@
   assert(rsa->dmq1 != NULL);
   assert(rsa->iqmp != NULL);
 
-  BIGNUM *r1, *m1;
-  int ret = 0;
-
-  BN_CTX_start(ctx);
-  r1 = BN_CTX_get(ctx);
-  m1 = BN_CTX_get(ctx);
-  BIGNUM *n, *p, *q;
+  bssl::BN_CTXScope scope(ctx);
+  BIGNUM *r1 = BN_CTX_get(ctx);
+  BIGNUM *m1 = BN_CTX_get(ctx);
   if (r1 == NULL || m1 == NULL) {
-    goto err;
+    return 0;
   }
 
   // Use the minimal-width versions of |n|, |p|, and |q|. Either works, but if
   // someone gives us non-minimal values, these will be slightly more efficient
   // on the non-Montgomery operations.
-  n = &rsa->mont_n->N;
-  p = &rsa->mont_p->N;
-  q = &rsa->mont_q->N;
+  BIGNUM *n = &rsa->mont_n->N;
+  BIGNUM *p = &rsa->mont_p->N;
+  BIGNUM *q = &rsa->mont_q->N;
 
   // This is a pre-condition for |mod_montgomery|. It was already checked by the
   // caller.
@@ -766,7 +752,7 @@
       // [0, n).
       !bn_mul_consttime(r0, r0, q, ctx) ||  //
       !bn_uadd_consttime(r0, r0, m1)) {
-    goto err;
+    return 0;
   }
 
   // The result should be bounded by |n|, but fixed-width operations may
@@ -776,14 +762,10 @@
   declassify_assert(BN_cmp(r0, n) < 0);
   bn_assert_fits_in_bytes(r0, BN_num_bytes(n));
   if (!bn_resize_words(r0, n->width)) {
-    goto err;
+    return 0;
   }
 
-  ret = 1;
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
+  return 1;
 }
 
 static int ensure_bignum(BIGNUM **out) {
@@ -914,11 +896,11 @@
   }
   int limit = BN_is_word(e, 3) ? bits * 8 : bits * 5;
 
-  int ret = 0, tries = 0, rand_tries = 0;
-  BN_CTX_start(ctx);
+  int tries = 0, rand_tries = 0;
+  bssl::BN_CTXScope scope(ctx);
   BIGNUM *tmp = BN_CTX_get(ctx);
   if (tmp == NULL) {
-    goto err;
+    return 0;
   }
 
   for (;;) {
@@ -927,13 +909,13 @@
     // bound checked below in steps 4.4 and 5.5).
     if (!BN_rand(out, bits, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ODD) ||
         !BN_GENCB_call(cb, BN_GENCB_GENERATED, rand_tries++)) {
-      goto err;
+      return 0;
     }
 
     if (p != NULL) {
       // If |p| and |out| are too close, try again (step 5.4).
       if (!bn_abs_sub_consttime(tmp, out, p, ctx)) {
-        goto err;
+        return 0;
       }
       if (BN_cmp(tmp, pow2_bits_100) <= 0) {
         continue;
@@ -963,18 +945,17 @@
       int relatively_prime;
       if (!bn_usub_consttime(tmp, out, BN_value_one()) ||
           !bn_is_relatively_prime(&relatively_prime, tmp, e, ctx)) {
-        goto err;
+        return 0;
       }
       if (constant_time_declassify_int(relatively_prime)) {
         // Test |out| for primality (steps 4.5.1 and 5.6.1).
         int is_probable_prime;
         if (!BN_primality_test(&is_probable_prime, out,
                                BN_prime_checks_for_generation, ctx, 0, cb)) {
-          goto err;
+          return 0;
         }
         if (is_probable_prime) {
-          ret = 1;
-          goto err;
+          return 1;
         }
       }
     }
@@ -984,16 +965,12 @@
     tries++;
     if (tries >= limit) {
       OPENSSL_PUT_ERROR(RSA, RSA_R_TOO_MANY_ITERATIONS);
-      goto err;
+      return 0;
     }
     if (!BN_GENCB_call(cb, 2, tries)) {
-      goto err;
+      return 0;
     }
   }
-
-err:
-  BN_CTX_end(ctx);
-  return ret;
 }
 
 // rsa_generate_key_impl generates an RSA key using a generalized version of
@@ -1029,29 +1006,31 @@
     return 0;
   }
 
-  int ret = 0;
-  int prime_bits = bits / 2;
-  BN_CTX *ctx = BN_CTX_new();
-  BIGNUM *totient, *pm1, *qm1, *sqrt2, *pow2_prime_bits_100, *pow2_prime_bits;
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
   int sqrt2_bits;
-  if (ctx == NULL) {
-    goto bn_err;
-  }
-  BN_CTX_start(ctx);
-  totient = BN_CTX_get(ctx);
-  pm1 = BN_CTX_get(ctx);
-  qm1 = BN_CTX_get(ctx);
-  sqrt2 = BN_CTX_get(ctx);
-  pow2_prime_bits_100 = BN_CTX_get(ctx);
-  pow2_prime_bits = BN_CTX_get(ctx);
-  if (totient == NULL || pm1 == NULL || qm1 == NULL || sqrt2 == NULL ||
-      pow2_prime_bits_100 == NULL || pow2_prime_bits == NULL ||
-      !BN_set_bit(pow2_prime_bits_100, prime_bits - 100) ||
-      !BN_set_bit(pow2_prime_bits, prime_bits)) {
-    goto bn_err;
+  if (ctx == nullptr) {
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
   }
 
-  // We need the RSA components non-NULL.
+  int prime_bits = bits / 2;
+  bssl::BN_CTXScope scope(ctx.get());
+  BIGNUM *totient = BN_CTX_get(ctx.get());
+  BIGNUM *pm1 = BN_CTX_get(ctx.get());
+  BIGNUM *qm1 = BN_CTX_get(ctx.get());
+  BIGNUM *sqrt2 = BN_CTX_get(ctx.get());
+  BIGNUM *pow2_prime_bits_100 = BN_CTX_get(ctx.get());
+  BIGNUM *pow2_prime_bits = BN_CTX_get(ctx.get());
+  if (totient == nullptr || pm1 == nullptr || qm1 == nullptr ||
+      sqrt2 == nullptr || pow2_prime_bits_100 == nullptr ||
+      pow2_prime_bits == nullptr ||
+      !BN_set_bit(pow2_prime_bits_100, prime_bits - 100) ||
+      !BN_set_bit(pow2_prime_bits, prime_bits)) {
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
+  }
+
+  // We need the RSA components non-null.
   if (!ensure_bignum(&rsa->n) ||     //
       !ensure_bignum(&rsa->d) ||     //
       !ensure_bignum(&rsa->e) ||     //
@@ -1060,16 +1039,19 @@
       !ensure_bignum(&rsa->dmp1) ||  //
       !ensure_bignum(&rsa->dmq1) ||  //
       !ensure_bignum(&rsa->iqmp)) {
-    goto bn_err;
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
   }
 
   if (!BN_copy(rsa->e, e_value)) {
-    goto bn_err;
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
   }
 
   // Compute sqrt2 >= ⌊2^(prime_bits-1)×√2⌋.
   if (!bn_set_words(sqrt2, kBoringSSLRSASqrtTwo, kBoringSSLRSASqrtTwoLen)) {
-    goto bn_err;
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
   }
   sqrt2_bits = kBoringSSLRSASqrtTwoLen * BN_BITS2;
   assert(sqrt2_bits == (int)BN_num_bits(sqrt2));
@@ -1077,14 +1059,16 @@
     // For key sizes up to 4096 (prime_bits = 2048), this is exactly
     // ⌊2^(prime_bits-1)×√2⌋.
     if (!BN_rshift(sqrt2, sqrt2, sqrt2_bits - prime_bits)) {
-      goto bn_err;
+      OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+      return 0;
     }
   } else if (prime_bits > sqrt2_bits) {
     // For key sizes beyond 4096, this is approximate. We err towards retrying
     // to ensure our key is the right size and round up.
     if (!BN_add_word(sqrt2, 1) ||
         !BN_lshift(sqrt2, sqrt2, prime_bits - sqrt2_bits)) {
-      goto bn_err;
+      OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+      return 0;
     }
   }
   assert(prime_bits == (int)BN_num_bits(sqrt2));
@@ -1095,13 +1079,14 @@
     //
     // Each call to |generate_prime| fails with probability p = 2^-21. The
     // probability that either call fails is 1 - (1-p)^2, which is around 2^-20.
-    if (!generate_prime(rsa->p, prime_bits, rsa->e, NULL, sqrt2,
-                        pow2_prime_bits_100, ctx, cb) ||
+    if (!generate_prime(rsa->p, prime_bits, rsa->e, nullptr, sqrt2,
+                        pow2_prime_bits_100, ctx.get(), cb) ||
         !BN_GENCB_call(cb, 3, 0) ||
         !generate_prime(rsa->q, prime_bits, rsa->e, rsa->p, sqrt2,
-                        pow2_prime_bits_100, ctx, cb) ||
+                        pow2_prime_bits_100, ctx.get(), cb) ||
         !BN_GENCB_call(cb, 3, 1)) {
-      goto bn_err;
+      OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+      return 0;
     }
 
     if (BN_cmp(rsa->p, rsa->q) < 0) {
@@ -1120,9 +1105,11 @@
     int no_inverse;
     if (!bn_usub_consttime(pm1, rsa->p, BN_value_one()) ||
         !bn_usub_consttime(qm1, rsa->q, BN_value_one()) ||
-        !bn_lcm_consttime(totient, pm1, qm1, ctx) ||
-        !bn_mod_inverse_consttime(rsa->d, &no_inverse, rsa->e, totient, ctx)) {
-      goto bn_err;
+        !bn_lcm_consttime(totient, pm1, qm1, ctx.get()) ||
+        !bn_mod_inverse_consttime(rsa->d, &no_inverse, rsa->e, totient,
+                                  ctx.get())) {
+      OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+      return 0;
     }
 
     // Retry if |rsa->d| <= 2^|prime_bits|. See appendix B.3.1's guidance on
@@ -1133,12 +1120,15 @@
   assert(BN_num_bits(pm1) == (unsigned)prime_bits);
   assert(BN_num_bits(qm1) == (unsigned)prime_bits);
   if (  // Calculate n.
-      !bn_mul_consttime(rsa->n, rsa->p, rsa->q, ctx) ||
+      !bn_mul_consttime(rsa->n, rsa->p, rsa->q, ctx.get()) ||
       // Calculate d mod (p-1).
-      !bn_div_consttime(NULL, rsa->dmp1, rsa->d, pm1, prime_bits, ctx) ||
+      !bn_div_consttime(nullptr, rsa->dmp1, rsa->d, pm1, prime_bits,
+                        ctx.get()) ||
       // Calculate d mod (q-1)
-      !bn_div_consttime(NULL, rsa->dmq1, rsa->d, qm1, prime_bits, ctx)) {
-    goto bn_err;
+      !bn_div_consttime(nullptr, rsa->dmq1, rsa->d, qm1, prime_bits,
+                        ctx.get())) {
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
   }
   bn_set_minimal_width(rsa->n);
 
@@ -1146,41 +1136,30 @@
   bn_declassify(rsa->n);
 
   // Calculate q^-1 mod p.
-  rsa->mont_p = BN_MONT_CTX_new_consttime(rsa->p, ctx);
-  if (rsa->mont_p == NULL ||  //
-      !bn_mod_inverse_secret_prime(rsa->iqmp, rsa->q, rsa->p, ctx,
+  rsa->mont_p = BN_MONT_CTX_new_consttime(rsa->p, ctx.get());
+  if (rsa->mont_p == nullptr ||  //
+      !bn_mod_inverse_secret_prime(rsa->iqmp, rsa->q, rsa->p, ctx.get(),
                                    rsa->mont_p)) {
-    goto bn_err;
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    return 0;
   }
 
   // Sanity-check that |rsa->n| has the specified size. This is implied by
   // |generate_prime|'s bounds.
   if (BN_num_bits(rsa->n) != (unsigned)bits) {
     OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
-    goto err;
+    return 0;
   }
 
   // The key generation process is complex and thus error-prone. It could be
   // disastrous to generate and then use a bad key so double-check that the key
   // makes sense. Also, while |rsa| is mutable, fill in the cached components.
-  if (!RSA_check_key(rsa) ||
-      !freeze_private_key(rsa, ctx)) {
+  if (!RSA_check_key(rsa) || !freeze_private_key(rsa, ctx.get())) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_INTERNAL_ERROR);
-    goto err;
+    return 0;
   }
 
-  ret = 1;
-
-bn_err:
-  if (!ret) {
-    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
-  }
-err:
-  if (ctx != NULL) {
-    BN_CTX_end(ctx);
-    BN_CTX_free(ctx);
-  }
-  return ret;
+  return 1;
 }
 
 static void replace_bignum(BIGNUM **out, BIGNUM **in) {
diff --git a/crypto/rsa/rsa_crypt.cc b/crypto/rsa/rsa_crypt.cc
index acf207e..ce4ae7d 100644
--- a/crypto/rsa/rsa_crypt.cc
+++ b/crypto/rsa/rsa_crypt.cc
@@ -349,25 +349,21 @@
   }
 
   const unsigned rsa_size = RSA_size(rsa);
-  BIGNUM *f, *result;
-  uint8_t *buf = NULL;
-  BN_CTX *ctx = NULL;
-  int i, ret = 0;
-
   if (max_out < rsa_size) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_OUTPUT_BUFFER_TOO_SMALL);
     return 0;
   }
 
-  ctx = BN_CTX_new();
-  if (ctx == NULL) {
-    goto err;
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  if (ctx == nullptr) {
+    return 0;
   }
 
-  BN_CTX_start(ctx);
-  f = BN_CTX_get(ctx);
-  result = BN_CTX_get(ctx);
-  buf = reinterpret_cast<uint8_t *>(OPENSSL_malloc(rsa_size));
+  bssl::BN_CTXScope scope(ctx.get());
+  BIGNUM *f = BN_CTX_get(ctx.get());
+  BIGNUM *result = BN_CTX_get(ctx.get());
+  uint8_t *buf = reinterpret_cast<uint8_t *>(OPENSSL_malloc(rsa_size));
+  int i, ret = 0;
   if (!f || !result || !buf) {
     goto err;
   }
@@ -378,8 +374,8 @@
       break;
     case RSA_PKCS1_OAEP_PADDING:
       // Use the default parameters: SHA-1 for both hashes and no label.
-      i = RSA_padding_add_PKCS1_OAEP_mgf1(buf, rsa_size, in, in_len, NULL, 0,
-                                          NULL, NULL);
+      i = RSA_padding_add_PKCS1_OAEP_mgf1(buf, rsa_size, in, in_len, nullptr, 0,
+                                          nullptr, nullptr);
       break;
     case RSA_NO_PADDING:
       i = RSA_padding_add_none(buf, rsa_size, in, in_len);
@@ -393,7 +389,7 @@
     goto err;
   }
 
-  if (BN_bin2bn(buf, rsa_size, f) == NULL) {
+  if (BN_bin2bn(buf, rsa_size, f) == nullptr) {
     goto err;
   }
 
@@ -403,8 +399,9 @@
     goto err;
   }
 
-  if (!BN_MONT_CTX_set_locked(&rsa->mont_n, &rsa->lock, rsa->n, ctx) ||
-      !BN_mod_exp_mont(result, f, rsa->e, &rsa->mont_n->N, ctx, rsa->mont_n)) {
+  if (!BN_MONT_CTX_set_locked(&rsa->mont_n, &rsa->lock, rsa->n, ctx.get()) ||
+      !BN_mod_exp_mont(result, f, rsa->e, &rsa->mont_n->N, ctx.get(),
+                       rsa->mont_n)) {
     goto err;
   }
 
@@ -419,12 +416,7 @@
   ret = 1;
 
 err:
-  if (ctx != NULL) {
-    BN_CTX_end(ctx);
-    BN_CTX_free(ctx);
-  }
   OPENSSL_free(buf);
-
   return ret;
 }
 
diff --git a/gen/sources.bzl b/gen/sources.bzl
index 03ce8b8..758f8e7 100644
--- a/gen/sources.bzl
+++ b/gen/sources.bzl
@@ -303,7 +303,9 @@
     "crypto/blake2/blake2.cc",
     "crypto/bn/bn_asn1.cc",
     "crypto/bn/convert.cc",
+    "crypto/bn/div.cc",
     "crypto/bn/exponentiation.cc",
+    "crypto/bn/sqrt.cc",
     "crypto/buf/buf.cc",
     "crypto/bytestring/asn1_compat.cc",
     "crypto/bytestring/ber.cc",
@@ -1122,6 +1124,15 @@
     "fuzz/verify_name_match_verifynameinsubtree_fuzzer.cc",
 ]
 
+modulewrapper_sources = [
+    "util/fipstools/acvp/modulewrapper/main.cc",
+    "util/fipstools/acvp/modulewrapper/modulewrapper.cc",
+]
+
+modulewrapper_internal_headers = [
+    "util/fipstools/acvp/modulewrapper/modulewrapper.h",
+]
+
 pki_sources = [
     "pki/cert_error_id.cc",
     "pki/cert_error_params.cc",
diff --git a/gen/sources.cmake b/gen/sources.cmake
index 5ae6e0f..32aaf2b 100644
--- a/gen/sources.cmake
+++ b/gen/sources.cmake
@@ -317,7 +317,9 @@
   crypto/blake2/blake2.cc
   crypto/bn/bn_asn1.cc
   crypto/bn/convert.cc
+  crypto/bn/div.cc
   crypto/bn/exponentiation.cc
+  crypto/bn/sqrt.cc
   crypto/buf/buf.cc
   crypto/bytestring/asn1_compat.cc
   crypto/bytestring/ber.cc
@@ -1157,6 +1159,19 @@
 )
 
 set(
+  MODULEWRAPPER_SOURCES
+
+  util/fipstools/acvp/modulewrapper/main.cc
+  util/fipstools/acvp/modulewrapper/modulewrapper.cc
+)
+
+set(
+  MODULEWRAPPER_INTERNAL_HEADERS
+
+  util/fipstools/acvp/modulewrapper/modulewrapper.h
+)
+
+set(
   PKI_SOURCES
 
   pki/cert_error_id.cc
diff --git a/gen/sources.gni b/gen/sources.gni
index e4079f1..cd760e2 100644
--- a/gen/sources.gni
+++ b/gen/sources.gni
@@ -303,7 +303,9 @@
   "crypto/blake2/blake2.cc",
   "crypto/bn/bn_asn1.cc",
   "crypto/bn/convert.cc",
+  "crypto/bn/div.cc",
   "crypto/bn/exponentiation.cc",
+  "crypto/bn/sqrt.cc",
   "crypto/buf/buf.cc",
   "crypto/bytestring/asn1_compat.cc",
   "crypto/bytestring/ber.cc",
@@ -1122,6 +1124,15 @@
   "fuzz/verify_name_match_verifynameinsubtree_fuzzer.cc",
 ]
 
+modulewrapper_sources = [
+  "util/fipstools/acvp/modulewrapper/main.cc",
+  "util/fipstools/acvp/modulewrapper/modulewrapper.cc",
+]
+
+modulewrapper_internal_headers = [
+  "util/fipstools/acvp/modulewrapper/modulewrapper.h",
+]
+
 pki_sources = [
   "pki/cert_error_id.cc",
   "pki/cert_error_params.cc",
diff --git a/gen/sources.json b/gen/sources.json
index 8f4e1a4..fb27719 100644
--- a/gen/sources.json
+++ b/gen/sources.json
@@ -287,7 +287,9 @@
       "crypto/blake2/blake2.cc",
       "crypto/bn/bn_asn1.cc",
       "crypto/bn/convert.cc",
+      "crypto/bn/div.cc",
       "crypto/bn/exponentiation.cc",
+      "crypto/bn/sqrt.cc",
       "crypto/buf/buf.cc",
       "crypto/bytestring/asn1_compat.cc",
       "crypto/bytestring/ber.cc",
@@ -1104,6 +1106,15 @@
       "fuzz/verify_name_match_verifynameinsubtree_fuzzer.cc"
     ]
   },
+  "modulewrapper": {
+    "srcs": [
+      "util/fipstools/acvp/modulewrapper/main.cc",
+      "util/fipstools/acvp/modulewrapper/modulewrapper.cc"
+    ],
+    "internal_hdrs": [
+      "util/fipstools/acvp/modulewrapper/modulewrapper.h"
+    ]
+  },
   "pki": {
     "srcs": [
       "pki/cert_error_id.cc",
diff --git a/gen/sources.mk b/gen/sources.mk
index 02effd9..b6888fa 100644
--- a/gen/sources.mk
+++ b/gen/sources.mk
@@ -297,7 +297,9 @@
   crypto/blake2/blake2.cc \
   crypto/bn/bn_asn1.cc \
   crypto/bn/convert.cc \
+  crypto/bn/div.cc \
   crypto/bn/exponentiation.cc \
+  crypto/bn/sqrt.cc \
   crypto/buf/buf.cc \
   crypto/bytestring/asn1_compat.cc \
   crypto/bytestring/ber.cc \
@@ -1105,6 +1107,13 @@
   fuzz/verify_name_match_normalizename_fuzzer.cc \
   fuzz/verify_name_match_verifynameinsubtree_fuzzer.cc
 
+boringssl_modulewrapper_sources := \
+  util/fipstools/acvp/modulewrapper/main.cc \
+  util/fipstools/acvp/modulewrapper/modulewrapper.cc
+
+boringssl_modulewrapper_internal_headers := \
+  util/fipstools/acvp/modulewrapper/modulewrapper.h
+
 boringssl_pki_sources := \
   pki/cert_error_id.cc \
   pki/cert_error_params.cc \
diff --git a/util/fipstools/CMakeLists.txt b/util/fipstools/CMakeLists.txt
deleted file mode 100644
index ff33b4f..0000000
--- a/util/fipstools/CMakeLists.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-add_executable(
-  test_fips
-
-  test_fips.cc
-)
-target_link_libraries(test_fips crypto)
diff --git a/util/fipstools/acvp/acvptool/acvp.go b/util/fipstools/acvp/acvptool/acvp.go
index dcaf542..7a5c43c 100644
--- a/util/fipstools/acvp/acvptool/acvp.go
+++ b/util/fipstools/acvp/acvptool/acvp.go
@@ -51,7 +51,7 @@
 	runFlag         = flag.String("run", "", "Name of primitive to run tests for")
 	fetchFlag       = flag.String("fetch", "", "Name of primitive to fetch vectors for")
 	expectedOutFlag = flag.String("expected-out", "", "Name of a file to write the expected results to")
-	wrapperPath     = flag.String("wrapper", "../../../../build/util/fipstools/acvp/modulewrapper/modulewrapper", "Path to the wrapper binary")
+	wrapperPath     = flag.String("wrapper", "../../../../build/modulewrapper", "Path to the wrapper binary")
 )
 
 type Config struct {
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index f550cf0..78e69ce 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -371,8 +371,6 @@
 					m.supportsFlush = true
 				}
 			}
-		} else if _, ok := m.primitives[algo.Algorithm]; !ok {
-			return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
 		}
 	}
 
diff --git a/util/fipstools/acvp/acvptool/test/expected/CMAC-AES.bz2 b/util/fipstools/acvp/acvptool/test/expected/CMAC-AES.bz2
index f80e502..5c71478 100644
--- a/util/fipstools/acvp/acvptool/test/expected/CMAC-AES.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/CMAC-AES.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA-1.bz2 b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA-1.bz2
index a95786d..de6c60f 100644
--- a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA-1.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA-1.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-224.bz2 b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-224.bz2
index c8ab1f9..8a14531 100644
--- a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-224.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-224.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-256.bz2 b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-256.bz2
index 1090835..6639cf0 100644
--- a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-256.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-256.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-384.bz2 b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-384.bz2
index 5b445a5..8d799b7 100644
--- a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-384.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-384.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512-256.bz2 b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512-256.bz2
index 1fdfa42..5f869cd 100644
--- a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512-256.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512-256.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512.bz2 b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512.bz2
index 3e46724..a1b2fd4 100644
--- a/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/HMAC-SHA2-512.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/trim_vectors.go b/util/fipstools/acvp/acvptool/test/trim_vectors.go
index 33dc947..59e5791 100644
--- a/util/fipstools/acvp/acvptool/test/trim_vectors.go
+++ b/util/fipstools/acvp/acvptool/test/trim_vectors.go
@@ -15,11 +15,13 @@
 //go:build ignore
 
 // trimvectors takes an ACVP vector set file and discards all but a single test
-// from each test group. This hope is that this achieves good coverage without
-// having to check in megabytes worth of JSON files.
+// from each test group, and also discards any test that serializes to more than
+// 4096 bytes. This hope is that this achieves good coverage without having to
+// check in megabytes worth of JSON files.
 package main
 
 import (
+	"bytes"
 	"encoding/json"
 	"os"
 )
@@ -39,12 +41,22 @@
 			testGroup := testGroupInterface.(map[string]any)
 			tests := testGroup["tests"].([]any)
 
-			keepIndex := 10
-			if keepIndex >= len(tests) {
-				keepIndex = len(tests) - 1
+			var keptTests []any
+			for _, test := range tests {
+				var b bytes.Buffer
+				encoder := json.NewEncoder(&b)
+				if err := encoder.Encode(test); err != nil {
+					panic(err)
+				}
+				if b.Len() <= 4096 {
+					keptTests = append(keptTests, test)
+				}
+				// We only keep the first test that meets the size criteria.
+				if len(keptTests) >= 1 {
+					break
+				}
 			}
-
-			testGroup["tests"] = []any{tests[keepIndex]}
+			testGroup["tests"] = keptTests
 		}
 	}
 
diff --git a/util/fipstools/acvp/acvptool/test/vectors/CMAC-AES.bz2 b/util/fipstools/acvp/acvptool/test/vectors/CMAC-AES.bz2
index ff34573..f38d822 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/CMAC-AES.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/CMAC-AES.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA-1.bz2 b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA-1.bz2
index d427f2e..b379dee 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA-1.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA-1.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-224.bz2 b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-224.bz2
index dd67b61..62b52d4 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-224.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-224.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-256.bz2 b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-256.bz2
index b137466..5cb072a 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-256.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-256.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-384.bz2 b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-384.bz2
index 2c1b317..6d1eb6d 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-384.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-384.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512-256.bz2 b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512-256.bz2
index d981300..68b34e0 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512-256.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512-256.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512.bz2 b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512.bz2
index a3ffe61..5ce2742 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/HMAC-SHA2-512.bz2
Binary files differ
diff --git a/util/fipstools/acvp/modulewrapper/CMakeLists.txt b/util/fipstools/acvp/modulewrapper/CMakeLists.txt
deleted file mode 100644
index 7938d39..0000000
--- a/util/fipstools/acvp/modulewrapper/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-if(FIPS)
-  add_executable(
-    modulewrapper
-
-    main.cc
-    modulewrapper.cc
-  )
-  target_link_libraries(modulewrapper crypto)
-endif()
diff --git a/util/fipstools/break-tests.sh b/util/fipstools/break-tests.sh
index e056e8f..111f120 100644
--- a/util/fipstools/break-tests.sh
+++ b/util/fipstools/break-tests.sh
@@ -128,7 +128,7 @@
 }
 
 if [ "$MODE" = "local" ]; then
-  TEST_FIPS_BIN=${TEST_FIPS_BIN:-build/util/fipstools/test_fips}
+  TEST_FIPS_BIN=${TEST_FIPS_BIN:-build/test_fips}
   TEST_FIPS_BREAK_BIN=${TEST_FIPS_BREAK_BIN:-./test_fips_break}
   check_file "$TEST_FIPS_BIN"
   check_file "$TEST_FIPS_BREAK_BIN"
diff --git a/util/fipstools/test-break-kat.sh b/util/fipstools/test-break-kat.sh
index b3b6283..01c13c3 100644
--- a/util/fipstools/test-break-kat.sh
+++ b/util/fipstools/test-break-kat.sh
@@ -18,7 +18,7 @@
 set -x
 set -e
 
-TEST_FIPS_BIN="build/util/fipstools/test_fips"
+TEST_FIPS_BIN="build/test_fips"
 
 if [ ! -f $TEST_FIPS_BIN ]; then
   echo "$TEST_FIPS_BIN is missing. Run this script from the top level of a"