Project import generated by Copybara.

GitOrigin-RevId: d155d244483601de47cc7fa92377e0c0f64ee50f
Change-Id: I1e5ee744d3750f570848892200c4b610455a08c4
diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock
index 31aaf06..25a6104 100644
--- a/nearby/Cargo.lock
+++ b/nearby/Cargo.lock
@@ -74,9 +74,9 @@
 
 [[package]]
 name = "anstream"
-version = "0.3.1"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450"
+checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -123,9 +123,9 @@
 
 [[package]]
 name = "anyhow"
-version = "1.0.70"
+version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 
 [[package]]
 name = "array_ref"
@@ -221,9 +221,9 @@
 
 [[package]]
 name = "bumpalo"
-version = "3.12.1"
+version = "3.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
+checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
 
 [[package]]
 name = "byteorder"
@@ -272,9 +272,9 @@
 
 [[package]]
 name = "ciborium"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
+checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
 dependencies = [
  "ciborium-io",
  "ciborium-ll",
@@ -283,15 +283,15 @@
 
 [[package]]
 name = "ciborium-io"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
+checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
 
 [[package]]
 name = "ciborium-ll"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
+checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
 dependencies = [
  "ciborium-io",
  "half",
@@ -309,9 +309,9 @@
 
 [[package]]
 name = "clap"
-version = "3.2.23"
+version = "3.2.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
 dependencies = [
  "bitflags",
  "clap_lex 0.2.4",
@@ -321,9 +321,9 @@
 
 [[package]]
 name = "clap"
-version = "4.2.4"
+version = "4.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
+checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -332,9 +332,9 @@
 
 [[package]]
 name = "clap_builder"
-version = "4.2.4"
+version = "4.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
+checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
 dependencies = [
  "anstream",
  "anstyle",
@@ -420,7 +420,7 @@
  "atty",
  "cast",
  "ciborium",
- "clap 3.2.23",
+ "clap 3.2.25",
  "criterion-plot",
  "itertools",
  "lazy_static",
@@ -491,9 +491,9 @@
 
 [[package]]
 name = "crypto-bigint"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7"
+checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15"
 dependencies = [
  "generic-array",
  "rand_core 0.6.4",
@@ -519,15 +519,9 @@
  "criterion",
  "crypto_provider_openssl",
  "crypto_provider_rustcrypto",
- "hex",
  "hex-literal",
  "rand",
  "rand_ext",
- "rstest",
- "rstest_reuse",
- "sha2",
- "test_helper",
- "wycheproof",
 ]
 
 [[package]]
@@ -556,6 +550,7 @@
 dependencies = [
  "cfg-if",
  "crypto_provider",
+ "crypto_provider_test",
  "hex-literal",
  "openssl",
  "ouroboros",
@@ -573,6 +568,7 @@
  "cfg-if",
  "crypto_provider",
  "crypto_provider_rustcrypto",
+ "crypto_provider_test",
  "ctr",
  "ed25519-dalek",
  "hex",
@@ -596,6 +592,21 @@
 ]
 
 [[package]]
+name = "crypto_provider_test"
+version = "0.1.0"
+dependencies = [
+ "crypto_provider",
+ "hex",
+ "hex-literal",
+ "rand",
+ "rand_ext",
+ "rstest",
+ "rstest_reuse",
+ "test_helper",
+ "wycheproof",
+]
+
+[[package]]
 name = "ctr"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -621,9 +632,9 @@
 
 [[package]]
 name = "der"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11"
+checksum = "05e58dffcdcc8ee7b22f0c1f71a69243d7c2d9ad87b5a14361f2424a1565c219"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -754,9 +765,9 @@
 
 [[package]]
 name = "flate2"
-version = "1.0.25"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
+checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
 dependencies = [
  "crc32fast",
  "miniz_oxide",
@@ -1082,9 +1093,9 @@
 
 [[package]]
 name = "js-sys"
-version = "0.3.61"
+version = "0.3.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1106,7 +1117,7 @@
  "anyhow",
  "base64 0.21.0",
  "blake2",
- "clap 4.2.4",
+ "clap 4.2.7",
  "criterion",
  "crypto_provider",
  "crypto_provider_default",
@@ -1158,9 +1169,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.142"
+version = "0.2.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
 
 [[package]]
 name = "libm"
@@ -1170,9 +1181,9 @@
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.3.4"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
+checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
 
 [[package]]
 name = "lock_api"
@@ -1216,9 +1227,9 @@
 
 [[package]]
 name = "miniz_oxide"
-version = "0.6.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
 dependencies = [
  "adler",
 ]
@@ -1310,9 +1321,9 @@
 
 [[package]]
 name = "openssl"
-version = "0.10.51"
+version = "0.10.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3"
+checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56"
 dependencies = [
  "bitflags",
  "cfg-if",
@@ -1336,9 +1347,9 @@
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.86"
+version = "0.9.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69"
+checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e"
 dependencies = [
  "bssl-sys",
  "cc",
@@ -1420,9 +1431,9 @@
 
 [[package]]
 name = "pkg-config"
-version = "0.3.26"
+version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
 [[package]]
 name = "platforms"
@@ -1571,9 +1582,9 @@
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
 dependencies = [
  "proc-macro2",
 ]
@@ -1642,6 +1653,12 @@
 ]
 
 [[package]]
+name = "raw-parts"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e22ce49f28be0887a992cf42172c8c75facdb74e3e1a7eb0f459cf2fcc95d7"
+
+[[package]]
 name = "rayon"
 version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1738,9 +1755,9 @@
 
 [[package]]
 name = "rustix"
-version = "0.37.14"
+version = "0.37.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
+checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
 dependencies = [
  "bitflags",
  "errno",
@@ -1792,18 +1809,18 @@
 
 [[package]]
 name = "serde"
-version = "1.0.160"
+version = "1.0.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.160"
+version = "1.0.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1864,9 +1881,9 @@
 
 [[package]]
 name = "spki"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e"
+checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
 dependencies = [
  "base64ct",
  "der",
@@ -1980,6 +1997,7 @@
  "log",
  "rand",
  "rand_chacha",
+ "raw-parts",
  "spin 0.9.8",
  "ukey2_connections",
  "ukey2_rs",
@@ -2047,7 +2065,7 @@
 name = "ukey2_shell"
 version = "0.1.0"
 dependencies = [
- "clap 4.2.4",
+ "clap 4.2.7",
  "crypto_provider_rustcrypto",
  "ukey2_connections",
  "ukey2_rs",
@@ -2105,9 +2123,9 @@
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.84"
+version = "0.2.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -2115,24 +2133,24 @@
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.84"
+version = "0.2.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.15",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.84"
+version = "0.2.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -2140,28 +2158,28 @@
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.84"
+version = "0.2.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.15",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.84"
+version = "0.2.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb"
 
 [[package]]
 name = "web-sys"
-version = "0.3.61"
+version = "0.3.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml
index 8ede0df..3cd9715 100644
--- a/nearby/Cargo.toml
+++ b/nearby/Cargo.toml
@@ -6,7 +6,6 @@
     "connections/ukey2/ukey2_jni",
     "connections/ukey2/ukey2_proto",
     "connections/ukey2/ukey2_shell",
-    # placeholder bssl-crypto crate
     "crypto/bssl-crypto",
     "crypto/crypto_provider",
     "crypto/crypto_provider_openssl",
@@ -36,6 +35,7 @@
 crypto_provider_rustcrypto = { path = "crypto/crypto_provider_rustcrypto" }
 crypto_provider_stubs = { path = "crypto/crypto_provider_stubs" }
 crypto_provider_default = {path = "crypto/crypto_provider_default" }
+crypto_provider_test = { path = "crypto/crypto_provider_test" }
 rand_core_05_adapter = { path = "crypto/rand_core_05_adapter" }
 rand_ext = { path = "presence/rand_ext" }
 test_helper = { path = "presence/test_helper" }
@@ -84,6 +84,9 @@
 hdrhistogram = "7.5.0"
 regex = "1.7.0"
 xts-mode = "0.5.1"
+rstest = "0.16.0"
+rstest_reuse = "0.5.0"
+wycheproof = "0.4.0"
 
 [workspace.package]
 version = "0.1.0"
diff --git a/nearby/Dockerfile b/nearby/Dockerfile
index 7a32b21..b4fdab0 100644
--- a/nearby/Dockerfile
+++ b/nearby/Dockerfile
@@ -31,7 +31,9 @@
     --rev f6affa68e950275f9fd773f2646ab7ee4db82897 \
     --color never 2>&1
 # needed for generating boringssl bindings
-RUN cargo install bindgen-cli
+# Must use 0.64.0, as version >= 0.65.0 removes the option "--size_t-is-usize", an option
+# used by boringssl when generating rust bindings
+RUN cargo install bindgen-cli --version 0.64.0
 RUN rustup toolchain add nightly
 RUN curl -L https://go.dev/dl/go1.20.2.linux-amd64.tar.gz | tar -C /usr/local -xz
 ENV PATH="$PATH:/usr/local/go/bin"
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
index ec58e9a..9a9e43a 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
@@ -16,6 +16,7 @@
 rand.workspace = true
 rand_chacha.workspace = true
 spin.workspace = true
+raw-parts = "1.1.2"
 
 [features]
 default = ["rustcrypto"]
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt
new file mode 100644
index 0000000..79cdcac
--- /dev/null
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#      http://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.
+
+cmake_minimum_required(VERSION 3.14)
+
+enable_testing()
+
+include_directories(
+    ${CMAKE_SOURCE_DIR}/ukey2_c_ffi/cpp/)
+
+include(ExternalProject)
+set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/target/tmp)
+ExternalProject_Add(
+    ukey2_c_ffi
+    DOWNLOAD_COMMAND ""
+    CONFIGURE_COMMAND ""
+    BUILD_COMMAND cargo build COMMAND cargo build --release --lib
+    BINARY_DIR "${CMAKE_SOURCE_DIR}/ukey2_c_ffi"
+    INSTALL_COMMAND "")
+
+# GoogleTest requires at least C++14
+set(CMAKE_CXX_STANDARD 14)
+add_compile_options(-Wall -Werror -Wextra -Wimplicit-fallthrough -Wextra-semi
+        -Wunreachable-code-aggressive -Wthread-safety
+        -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi
+        -Wloop-analysis -Wno-unneeded-internal-declaration
+        -Wenum-compare-conditional -Wno-ignored-pragma-optimize
+        -Wno-bitfield-constant-conversion -Wno-deprecated-this-capture -Wshadow
+        -Wsign-compare)
+
+include(FetchContent)
+FetchContent_Declare(
+  googletest
+  GIT_REPOSITORY https://github.com/google/googletest.git
+  GIT_TAG release-1.12.1
+)
+FetchContent_MakeAvailable(googletest)
+
+add_executable(ffi_test
+  ukey2_test.cc
+  ukey2_glue.cc
+  ukey2_ffi.h
+  ukey2_bindings.h)
+
+target_link_libraries(ffi_test
+    "${CMAKE_SOURCE_DIR}/../../../../target/release/libukey2_c_ffi${CMAKE_SHARED_LIBRARY_SUFFIX}"
+    GTest::gtest_main
+    dl pthread)
+
+include(GoogleTest)
+gtest_discover_tests(ffi_test)
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/Makefile b/nearby/connections/ukey2/ukey2_c_ffi/cpp/Makefile
deleted file mode 100644
index b5fd106..0000000
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/Makefile
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2023 Google LLC
-#
-# 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
-#
-#     http://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.
-
-C_SRCS := ukey2_glue.cc
-CC = clang++
-UNAME := $(shell uname -s)
-ifeq ($(UNAME),Darwin)
-    FFI_LIB = ../../../../target/release/libukey2_c_ffi.dylib
-else
-    FFI_LIB = ../../../../target/release/libukey2_c_ffi.so
-endif
-ukey2:
-	cargo build --release
-	rm -rf build
-	mkdir build
-	$(CC) $(C_SRCS) $(FFI_LIB) -o build/ukey2
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_bindings.h b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_bindings.h
index a0cb87f..b14450c 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_bindings.h
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_bindings.h
@@ -29,6 +29,7 @@
 typedef struct {
   uint8_t* handle;
   size_t len;
+  size_t cap;
 } RustFFIByteArray;
 
 typedef struct {
@@ -59,7 +60,6 @@
 // Common handshake methods
 bool is_handshake_complete(Ukey2HandshakeContextHandle handle);
 RustFFIByteArray get_next_handshake_message(Ukey2HandshakeContextHandle handle);
-bool can_send_payload_in_handshake_message(Ukey2HandshakeContextHandle handle);
 RustFFIByteArray parse_handshake_message(Ukey2HandshakeContextHandle handle, CFFIByteArray message);
 Ukey2ConnectionContextHandle to_connection_context(Ukey2HandshakeContextHandle handle);
 RustFFIByteArray get_verification_string(Ukey2HandshakeContextHandle handle, size_t output_length);
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
index e96ae1c..2beae24 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
@@ -50,7 +50,7 @@
 
     private:
         friend class Ukey2Handshake;
-        D2DConnectionContextV1(Ukey2ConnectionContextHandle handle) : handle_(handle) {};
+        D2DConnectionContextV1(Ukey2ConnectionContextHandle handle) : handle_(handle) {}
         const Ukey2ConnectionContextHandle handle_;
 };
 
@@ -68,12 +68,10 @@
         static Ukey2Handshake ForInitiator();
         // Returns true if the handshake is complete, false otherwise.
         bool IsHandshakeComplete();
-        // Return if the handshake message can carry a payload.
-        bool CanSendPayloadInHandshakeMessage();
         // Returns raw byte data with the message to send over the wire.
         std::string GetNextHandshakeMessage();
         // Parses the raw handshake message received over the wire.
-        std::string ParseHandshakeMessage(std::string message);
+        void ParseHandshakeMessage(std::string message);
         // Returns the authentication string of length output_length to be confirmed on both devices.
         std::string GetVerificationString(size_t output_length);
         // Turns this Ukey2Handshake instance into a D2DConnectionContextV1. This method once called,
@@ -81,6 +79,6 @@
         D2DConnectionContextV1 ToConnectionContext();
 
     private:
-        Ukey2Handshake(Ukey2HandshakeContextHandle handle) : handle_(handle) {};
+        Ukey2Handshake(Ukey2HandshakeContextHandle handle) : handle_(handle) {}
         const Ukey2HandshakeContextHandle handle_;
 };
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_glue.cc b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_glue.cc
index e013b2a..6f4a396 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_glue.cc
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_glue.cc
@@ -15,17 +15,10 @@
 #include "ukey2_bindings.h"
 #include "ukey2_ffi.h"
 
+#include <cassert>
 #include <cstring>
-#include <iostream>
 #include <string>
 
-CFFIByteArray messageToByteArray(const std::string message) {
-    return {
-        .handle = (uint8_t*) (new std::string(message))->c_str(),
-        .len = message.length(),
-    };
-}
-
 CFFIByteArray nullByteArray() {
     return {
         .handle = nullptr,
@@ -46,10 +39,6 @@
     return is_handshake_complete(handle_);
 }
 
-bool Ukey2Handshake::CanSendPayloadInHandshakeMessage() {
-    return can_send_payload_in_handshake_message(handle_);
-}
-
 std::string Ukey2Handshake::GetNextHandshakeMessage() {
     RustFFIByteArray array = get_next_handshake_message(handle_);
     std::string ret = std::string((const char*) array.handle, array.len);
@@ -57,11 +46,12 @@
     return ret;
 }
 
-std::string Ukey2Handshake::ParseHandshakeMessage(std::string message) {
-    RustFFIByteArray array = parse_handshake_message(handle_, messageToByteArray(message));
-    std::string ret = std::string((const char*) array.handle, array.len);
-    rust_dealloc_ffi_byte_array(array);
-    return ret;
+void Ukey2Handshake::ParseHandshakeMessage(std::string message) {
+    CFFIByteArray messageRaw{
+        .handle = (uint8_t*)message.c_str(),
+        .len = message.length(),
+    };
+    parse_handshake_message(handle_, messageRaw);
 }
 
 std::string Ukey2Handshake::GetVerificationString(size_t output_length) {
@@ -77,14 +67,35 @@
 }
 
 std::string D2DConnectionContextV1::DecodeMessageFromPeer(std::string message, std::string associated_data) {
-    RustFFIByteArray array = decode_message_from_peer(handle_, messageToByteArray(message), messageToByteArray(associated_data));
+    CFFIByteArray messageRaw{
+        .handle = (uint8_t*)message.c_str(),
+        .len = message.length(),
+    };
+    CFFIByteArray associatedDataRaw{
+        .handle = (uint8_t*)associated_data.c_str(),
+        .len = associated_data.length(),
+    };
+    RustFFIByteArray array =
+        decode_message_from_peer(handle_, messageRaw, associatedDataRaw);
+    if (array.handle == nullptr) {
+        return "";
+    }
     std::string ret = std::string((const char*) array.handle, array.len);
     rust_dealloc_ffi_byte_array(array);
     return ret;
 }
 
 std::string D2DConnectionContextV1::EncodeMessageToPeer(std::string message, std::string associated_data) {
-    RustFFIByteArray array = encode_message_to_peer(handle_, messageToByteArray(message), messageToByteArray(associated_data));
+    CFFIByteArray messageRaw{
+        .handle = (uint8_t*)message.c_str(),
+        .len = message.length(),
+    };
+    CFFIByteArray associatedDataRaw{
+        .handle = (uint8_t*)associated_data.c_str(),
+        .len = associated_data.length(),
+    };
+    RustFFIByteArray array =
+        encode_message_to_peer(handle_, messageRaw, associatedDataRaw);
     std::string ret = std::string((const char*) array.handle, array.len);
     rust_dealloc_ffi_byte_array(array);
     return ret;
@@ -113,7 +124,11 @@
 }
 
 D2DRestoreConnectionContextV1Result D2DConnectionContextV1::FromSavedSession(std::string data) {
-    auto result = from_saved_session(messageToByteArray(data));
+    CFFIByteArray arr{
+        .handle = (uint8_t*)data.c_str(),
+        .len = data.length(),
+    };
+    auto result = from_saved_session(arr);
     return {
         D2DConnectionContextV1(result.handle),
         result.status,
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc
new file mode 100644
index 0000000..98ea07e
--- /dev/null
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2023 Google LLC
+//
+// 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 <string>
+
+#include <gtest/gtest.h>
+#include "ukey2_ffi.h"
+
+namespace {
+TEST(Ukey2RustTest, HandshakeStartsIncomplete) {
+  Ukey2Handshake responder_handle = Ukey2Handshake::ForResponder();
+  Ukey2Handshake initiator_handle = Ukey2Handshake::ForInitiator();
+  ASSERT_FALSE(responder_handle.IsHandshakeComplete());
+  ASSERT_FALSE(initiator_handle.IsHandshakeComplete());
+}
+
+TEST(Ukey2RustTest, HandshakeComplete) {
+  Ukey2Handshake responder_handle = Ukey2Handshake::ForResponder();
+  Ukey2Handshake initiator_handle = Ukey2Handshake::ForInitiator();
+  responder_handle.ParseHandshakeMessage(
+      initiator_handle.GetNextHandshakeMessage());
+  initiator_handle.ParseHandshakeMessage(
+      responder_handle.GetNextHandshakeMessage());
+  responder_handle.ParseHandshakeMessage(
+      initiator_handle.GetNextHandshakeMessage());
+  ASSERT_TRUE(responder_handle.IsHandshakeComplete());
+  ASSERT_TRUE(initiator_handle.IsHandshakeComplete());
+}
+
+TEST(Ukey2RustTest, CanSendReceiveMessage) {
+  Ukey2Handshake responder_handle = Ukey2Handshake::ForResponder();
+  Ukey2Handshake initiator_handle = Ukey2Handshake::ForInitiator();
+  responder_handle.ParseHandshakeMessage(
+      initiator_handle.GetNextHandshakeMessage());
+  initiator_handle.ParseHandshakeMessage(
+      responder_handle.GetNextHandshakeMessage());
+  responder_handle.ParseHandshakeMessage(
+      initiator_handle.GetNextHandshakeMessage());
+  ASSERT_TRUE(responder_handle.IsHandshakeComplete());
+  ASSERT_TRUE(initiator_handle.IsHandshakeComplete());
+  D2DConnectionContextV1 responder_connection =
+      responder_handle.ToConnectionContext();
+  D2DConnectionContextV1 initiator_connection =
+      initiator_handle.ToConnectionContext();
+  std::string message = "hello world";
+  auto encoded = responder_connection.EncodeMessageToPeer(message, "assocdata");
+  ASSERT_NE(encoded, "");
+  auto decoded =
+      initiator_connection.DecodeMessageFromPeer(encoded, "assocdata");
+  EXPECT_EQ(message, decoded);
+}
+
+TEST(Ukey2RustTest, TestSaveRestoreSession) {
+  Ukey2Handshake responder_handle = Ukey2Handshake::ForResponder();
+  Ukey2Handshake initiator_handle = Ukey2Handshake::ForInitiator();
+  responder_handle.ParseHandshakeMessage(
+      initiator_handle.GetNextHandshakeMessage());
+  initiator_handle.ParseHandshakeMessage(
+      responder_handle.GetNextHandshakeMessage());
+  responder_handle.ParseHandshakeMessage(
+      initiator_handle.GetNextHandshakeMessage());
+  ASSERT_TRUE(responder_handle.IsHandshakeComplete());
+  ASSERT_TRUE(initiator_handle.IsHandshakeComplete());
+  D2DConnectionContextV1 responder_connection =
+      responder_handle.ToConnectionContext();
+  D2DConnectionContextV1 initiator_connection =
+      initiator_handle.ToConnectionContext();
+  auto saved_responder = responder_connection.SaveSession();
+  D2DRestoreConnectionContextV1Result restore_result =
+      D2DConnectionContextV1::FromSavedSession(saved_responder);
+  ASSERT_EQ(restore_result.status,
+            CD2DRestoreConnectionContextV1Status::STATUS_GOOD);
+  auto new_responder = restore_result.handle;
+  std::string encoded = new_responder.EncodeMessageToPeer("hello world", "");
+  std::string decoded = initiator_connection.DecodeMessageFromPeer(encoded, "");
+  EXPECT_EQ("hello world", decoded);
+}
+
+}  // namespace
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs b/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
index 68d42c9..a044471 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
+++ b/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
@@ -13,13 +13,13 @@
 // limitations under the License.
 
 use std::collections::HashMap;
-use std::ffi::CString;
 use std::ptr::null_mut;
 
 use lazy_static::lazy_static;
 use rand::Rng;
 use rand_chacha::rand_core::SeedableRng;
 use rand_chacha::ChaCha20Rng;
+use raw_parts::RawParts;
 use spin::Mutex;
 
 use ukey2_connections::{
@@ -38,6 +38,7 @@
 pub struct RustFFIByteArray {
     ptr: *mut u8,
     len: usize,
+    cap: usize,
 }
 
 #[repr(C)]
@@ -83,7 +84,12 @@
 #[no_mangle]
 pub unsafe extern "C" fn rust_dealloc_ffi_byte_array(arr: RustFFIByteArray) {
     if !arr.ptr.is_null() {
-        let _ = Vec::from_raw_parts(arr.ptr, arr.len, arr.len);
+        let raw_parts = RawParts {
+            ptr: arr.ptr,
+            length: arr.len,
+            capacity: arr.cap,
+        };
+        let _ = RawParts::into_vec(raw_parts);
     }
 }
 
@@ -104,16 +110,21 @@
         .get(&handle)
         .and_then(|c| c.get_next_handshake_message());
     if let Some(msg) = opt_msg {
-        let ret_len = msg.len();
-        let data: CString = unsafe { CString::from_vec_unchecked(msg) };
+        let RawParts {
+            ptr,
+            length,
+            capacity,
+        } = RawParts::from_vec(msg);
         RustFFIByteArray {
-            ptr: data.into_raw() as *mut u8,
-            len: ret_len,
+            ptr,
+            len: length,
+            cap: capacity,
         }
     } else {
         RustFFIByteArray {
             ptr: null_mut(),
             len: usize::MAX,
+            cap: usize::MAX,
         }
     }
 }
@@ -125,19 +136,20 @@
     handle: u64,
     arr: CFFIByteArray,
 ) -> RustFFIByteArray {
-    let msg = Vec::<u8>::from_raw_parts(arr.ptr, arr.len, arr.len);
+    let msg = std::slice::from_raw_parts(arr.ptr, arr.len);
     // TODO error handling
     let result = HANDLE_MAPPING
         .lock()
         .get_mut(&handle)
         .unwrap()
-        .handle_handshake_message(msg.as_slice());
+        .handle_handshake_message(msg);
     if let Err(error) = result {
         log::error!("{:?}", error);
     }
     RustFFIByteArray {
         ptr: null_mut(),
         len: usize::MAX,
+        cap: usize::MAX,
     }
 }
 
@@ -153,10 +165,15 @@
                 .auth_string::<CryptoProvider>()
                 .derive_vec(length)
                 .unwrap();
-            let vec_len = auth_vec.len();
+            let RawParts {
+                ptr,
+                length,
+                capacity,
+            } = RawParts::from_vec(auth_vec);
             RustFFIByteArray {
-                ptr: auth_vec.leak().as_mut_ptr(),
-                len: vec_len,
+                ptr,
+                len: length,
+                cap: capacity,
             }
         })
         .unwrap()
@@ -213,6 +230,7 @@
         return RustFFIByteArray {
             ptr: null_mut(),
             len: usize::MAX,
+            cap: usize::MAX,
         };
     }
     let msg = std::slice::from_raw_parts(msg.ptr, msg.len);
@@ -229,16 +247,22 @@
         .get_mut(&handle)
         .map(|c| c.encode_message_to_peer::<CryptoProvider, _>(msg, associated_data));
     if let Some(msg) = ret {
-        let len = msg.len();
+        let RawParts {
+            ptr,
+            length,
+            capacity,
+        } = RawParts::from_vec(msg);
         RustFFIByteArray {
-            ptr: msg.leak().as_mut_ptr(),
-            len,
+            ptr,
+            len: length,
+            cap: capacity,
         }
     } else {
         log::error!("Was unable to find handle!");
         RustFFIByteArray {
             ptr: null_mut(),
             len: usize::MAX,
+            cap: usize::MAX,
         }
     }
 }
@@ -248,13 +272,14 @@
 #[no_mangle]
 pub unsafe extern "C" fn decode_message_from_peer(
     handle: u64,
-    msg: RustFFIByteArray,
+    msg: CFFIByteArray,
     associated_data: CFFIByteArray,
 ) -> RustFFIByteArray {
     if msg.len == 0 {
         return RustFFIByteArray {
             ptr: null_mut(),
             len: usize::MAX,
+            cap: usize::MAX,
         };
     }
     let msg = std::slice::from_raw_parts(msg.ptr, msg.len);
@@ -272,15 +297,21 @@
         .unwrap()
         .decode_message_from_peer::<CryptoProvider, _>(msg, associated_data);
     if let Ok(decoded) = ret {
-        let len = decoded.len();
+        let RawParts {
+            ptr,
+            length,
+            capacity,
+        } = RawParts::from_vec(decoded);
         RustFFIByteArray {
-            ptr: decoded.leak().as_mut_ptr(),
-            len,
+            ptr,
+            len: length,
+            cap: capacity,
         }
     } else {
         RustFFIByteArray {
             ptr: null_mut(),
             len: usize::MAX,
+            cap: usize::MAX,
         }
     }
 }
@@ -292,10 +323,15 @@
         .get(&handle)
         .unwrap()
         .get_session_unique::<CryptoProvider>();
-    let handle_size = session_unique_bytes.len();
+    let RawParts {
+        ptr,
+        length,
+        capacity,
+    } = RawParts::from_vec(session_unique_bytes);
     RustFFIByteArray {
-        ptr: session_unique_bytes.leak().as_mut_ptr(),
-        len: handle_size,
+        ptr,
+        len: length,
+        cap: capacity,
     }
 }
 
@@ -324,10 +360,15 @@
         .get(&handle)
         .unwrap()
         .save_session();
-    let handle_size = key.len();
+    let RawParts {
+        ptr,
+        length,
+        capacity,
+    } = RawParts::from_vec(key);
     RustFFIByteArray {
-        ptr: key.leak().as_mut_ptr(),
-        len: handle_size,
+        ptr,
+        len: length,
+        cap: capacity,
     }
 }
 
diff --git a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
index 5c10cb6..2e61f80 100644
--- a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
+++ b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
@@ -197,9 +197,9 @@
 
 [[package]]
 name = "clap"
-version = "3.2.23"
+version = "3.2.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
+checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83"
 dependencies = [
  "bitflags",
  "clap_lex",
@@ -386,9 +386,9 @@
 
 [[package]]
 name = "der"
-version = "0.7.4"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11"
+checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -456,9 +456,9 @@
 
 [[package]]
 name = "elliptic-curve"
-version = "0.13.4"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7"
+checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d"
 dependencies = [
  "base16ct",
  "crypto-bigint",
@@ -521,9 +521,9 @@
 
 [[package]]
 name = "generic-array"
-version = "0.14.7"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
 dependencies = [
  "typenum",
  "version_check",
@@ -820,9 +820,9 @@
 
 [[package]]
 name = "p256"
-version = "0.13.2"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+checksum = "7270da3e5caa82afd3deb054cc237905853813aea3859544bc082c3fe55b8d47"
 dependencies = [
  "elliptic-curve",
  "primeorder",
@@ -892,9 +892,9 @@
 
 [[package]]
 name = "primeorder"
-version = "0.13.1"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5"
+checksum = "7613fdcc0831c10060fa69833ea8fa2caa94b6456f51e25356a885b530a2e3d0"
 dependencies = [
  "elliptic-curve",
 ]
@@ -1083,9 +1083,9 @@
 
 [[package]]
 name = "sec1"
-version = "0.7.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e"
 dependencies = [
  "base16ct",
  "der",
diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml
index 69f7572..53eb974 100644
--- a/nearby/crypto/crypto_provider/Cargo.toml
+++ b/nearby/crypto/crypto_provider/Cargo.toml
@@ -4,42 +4,19 @@
 edition.workspace = true
 publish.workspace = true
 
-[dependencies]
-hex-literal = { workspace = true, optional = true }
-rand = { workspace = true, optional = true }
-rstest = { version = "0.16.0", optional = true }
-rstest_reuse = { version = "0.5.0", optional = true }
-wycheproof = { version = "0.4.0", optional = true }
-hex = { workspace = true, optional = true }
-test_helper = { workspace = true, optional = true }
-
 [dev-dependencies]
+criterion.workspace = true
+hex-literal.workspace = true
 crypto_provider_openssl.workspace = true
 crypto_provider_rustcrypto.workspace = true
-wycheproof = "0.4.0"
-hex-literal.workspace = true
-sha2.workspace = true
-criterion.workspace = true
 rand_ext.workspace = true
-hex.workspace = true
+rand.workspace = true
 
 [features]
 default = ["alloc", "gcm_siv"]
 std = []
 alloc = []
 gcm_siv = []
-testing = [
-    "dep:hex-literal",
-    "dep:rstest",
-    "dep:rstest_reuse",
-    "dep:wycheproof",
-    "dep:test_helper",
-    "std",
-    "rand",
-    "rand/std",
-    "rand/std_rng",
-    "dep:hex",
-]
 
 [[bench]]
 name = "hmac_bench"
diff --git a/nearby/crypto/crypto_provider/src/aes/cbc.rs b/nearby/crypto/crypto_provider/src/aes/cbc.rs
index d4211a6..e32c588 100644
--- a/nearby/crypto/crypto_provider/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider/src/aes/cbc.rs
@@ -43,56 +43,3 @@
     /// correctly. Exposing padding errors can cause a padding oracle vulnerability.
     BadPadding,
 }
-
-/// Module for testing implementations of this crate.
-#[cfg(feature = "testing")]
-pub mod testing {
-    use super::{Aes256Key, AesCbcIv, AesCbcPkcs7Padded};
-    pub use crate::testing::prelude::*;
-    use core::marker::PhantomData;
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// Tests for AES-256-CBC encryption
-    pub fn aes_256_cbc_test_encrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
-        // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632
-        let key: Aes256Key =
-            hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
-        let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
-        let msg = hex!("3f56935def3f");
-        let expected_ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
-        assert_eq!(A::encrypt(&key, &iv, &msg), expected_ciphertext);
-    }
-
-    /// Tests for AES-256-CBC decryption
-    pub fn aes_256_cbc_test_decrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
-        // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632
-        let key: Aes256Key =
-            hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
-        let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
-        let ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
-        let expected_msg = hex!("3f56935def3f");
-        assert_eq!(A::decrypt(&key, &iv, &ciphertext).unwrap(), expected_msg);
-    }
-
-    /// Generates the test cases to validate the AES-256-CBC implementation.
-    /// For example, to test `MyAesCbc256Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::cbc::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_256_cbc_test_cases)]
-    ///     fn aes_256_cbc_tests(
-    ///             testcase: CryptoProviderTestCases<PhantomData<MyAesCbc256Impl>>) {
-    ///         testcase(PhantomData);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_256_cbc_test_encrypt)]
-    #[case::decrypt(aes_256_cbc_test_decrypt)]
-    fn aes_256_cbc_test_cases<A: AesCbcPkcs7Padded>(#[case] testcase: CryptoProviderTestCases<F>) {}
-}
diff --git a/nearby/crypto/crypto_provider/src/aes/ctr.rs b/nearby/crypto/crypto_provider/src/aes/ctr.rs
index c7f5d5f..acfe4ef 100644
--- a/nearby/crypto/crypto_provider/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider/src/aes/ctr.rs
@@ -33,178 +33,3 @@
     /// Decrypt the data in place, advancing the counter state appropriately.
     fn decrypt(&mut self, data: &mut [u8]);
 }
-
-/// Module for testing implementations of this crate.
-#[cfg(feature = "testing")]
-pub mod testing {
-    use super::AesCtr;
-    use crate::aes::{Aes128Key, Aes256Key};
-    pub use crate::testing::prelude;
-    use core::marker;
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// Test AES-128-CTR encryption
-    pub fn aes_128_ctr_test_encrypt<A: AesCtr<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.1
-        let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
-        let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
-        let mut block: [u8; 16];
-        let mut cipher = A::new(&key, iv);
-
-        block = hex!("6bc1bee22e409f96e93d7e117393172a");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_1 = hex!("874d6191b620e3261bef6864990db6ce");
-        assert_eq!(expected_ciphertext_1, block);
-
-        block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_2 = hex!("9806f66b7970fdff8617187bb9fffdff");
-        assert_eq!(expected_ciphertext_2, block);
-
-        block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_3 = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
-        assert_eq!(expected_ciphertext_3, block);
-
-        block = hex!("f69f2445df4f9b17ad2b417be66c3710");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_3 = hex!("1e031dda2fbe03d1792170a0f3009cee");
-        assert_eq!(expected_ciphertext_3, block);
-    }
-
-    /// Test AES-128-CTR decryption
-    pub fn aes_128_ctr_test_decrypt<A: AesCtr<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.2
-        let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
-        let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
-        let mut block: [u8; 16];
-        let mut cipher = A::new(&key, iv);
-
-        block = hex!("874d6191b620e3261bef6864990db6ce");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
-        assert_eq!(expected_plaintext_1, block);
-
-        block = hex!("9806f66b7970fdff8617187bb9fffdff");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-        assert_eq!(expected_plaintext_2, block);
-
-        block = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-        assert_eq!(expected_plaintext_3, block);
-
-        block = hex!("1e031dda2fbe03d1792170a0f3009cee");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
-        assert_eq!(expected_plaintext_3, block);
-    }
-
-    /// Test AES-256-CTR encryption
-    pub fn aes_256_ctr_test_encrypt<A: AesCtr<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.5
-        let key: Aes256Key =
-            hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
-        let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
-        let mut block: [u8; 16];
-        let mut cipher = A::new(&key, iv);
-
-        block = hex!("6bc1bee22e409f96e93d7e117393172a");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_1 = hex!("601ec313775789a5b7a7f504bbf3d228");
-        assert_eq!(expected_ciphertext_1, block);
-
-        block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_2 = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
-        assert_eq!(expected_ciphertext_2, block);
-
-        block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_3 = hex!("2b0930daa23de94ce87017ba2d84988d");
-        assert_eq!(expected_ciphertext_3, block);
-
-        block = hex!("f69f2445df4f9b17ad2b417be66c3710");
-        cipher.encrypt(&mut block);
-        let expected_ciphertext_3 = hex!("dfc9c58db67aada613c2dd08457941a6");
-        assert_eq!(expected_ciphertext_3, block);
-    }
-
-    /// Test AES-256-CTR decryption
-    pub fn aes_256_ctr_test_decrypt<A: AesCtr<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.6
-        let key: Aes256Key =
-            hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
-        let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
-        let mut block: [u8; 16];
-        let mut cipher = A::new(&key, iv);
-
-        block = hex!("601ec313775789a5b7a7f504bbf3d228");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
-        assert_eq!(expected_plaintext_1, block);
-
-        block = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-        assert_eq!(expected_plaintext_2, block);
-
-        block = hex!("2b0930daa23de94ce87017ba2d84988d");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-        assert_eq!(expected_plaintext_3, block);
-
-        block = hex!("dfc9c58db67aada613c2dd08457941a6");
-        cipher.encrypt(&mut block);
-        let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
-        assert_eq!(expected_plaintext_3, block);
-    }
-
-    /// Generates the test cases to validate the AES-128-CTR implementation.
-    /// For example, to test `MyAesCtr128Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::ctr::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_128_ctr_test_cases)]
-    ///     fn aes_128_ctr_tests(testcase: CryptoProviderTestCase<MyAesCtr128Impl>) {
-    ///         testcase(MyAesCtr128Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_128_ctr_test_encrypt)]
-    #[case::decrypt(aes_128_ctr_test_decrypt)]
-    fn aes_128_ctr_test_cases<F: AesCtrFactory<Key = Aes128Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-
-    /// Generates the test cases to validate the AES-256-CTR implementation.
-    /// For example, to test `MyAesCtr256Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::ctr::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_256_ctr_test_cases_impl)]
-    ///     fn aes_256_ctr_tests(testcase: CryptoProviderTestCase<MyAesCtr256Impl>) {
-    ///         testcase(MyAesCtr256Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_256_ctr_test_encrypt)]
-    #[case::decrypt(aes_256_ctr_test_decrypt)]
-    fn aes_256_ctr_test_cases<F: AesCtrFactory<Key = Aes256Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/aes/gcm_siv.rs b/nearby/crypto/crypto_provider/src/aes/gcm_siv.rs
index 2d683b2..dabb10d 100644
--- a/nearby/crypto/crypto_provider/src/aes/gcm_siv.rs
+++ b/nearby/crypto/crypto_provider/src/aes/gcm_siv.rs
@@ -46,119 +46,3 @@
     /// in order to properly decrypt the message.
     fn decrypt(&self, data: &mut Vec<u8>, aad: &[u8], nonce: &[u8]) -> Result<(), GcmSivError>;
 }
-
-/// Module for testing implementations of this crate.
-#[cfg(feature = "testing")]
-pub mod testing {
-    extern crate alloc;
-
-    use alloc::vec::Vec;
-    use core::marker;
-
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    use crate::aes::{Aes128Key, Aes256Key};
-    pub use crate::testing::prelude;
-
-    use super::AesGcmSiv;
-
-    /// Test AES-GCM-SIV-128 encryption/decryption
-    pub fn aes_128_gcm_siv_test<A: AesGcmSiv<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
-        // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
-        // TC1
-        let test_key = hex!("01000000000000000000000000000000");
-        let nonce = hex!("030000000000000000000000");
-        let aes = A::new(&test_key.into());
-        let msg = hex!("");
-        let mut buf = Vec::from(msg.as_slice());
-        let tag = hex!("dc20e2d83f25705bb49e439eca56de25");
-        assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..], &tag);
-        // TC2
-        let msg = hex!("0100000000000000");
-        let ct = hex!("b5d839330ac7b786");
-        let tag = hex!("578782fff6013b815b287c22493a364c");
-        let mut buf = Vec::from(msg.as_slice());
-        assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..8], &ct);
-        assert_eq!(&buf[8..], &tag);
-        assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..], &msg);
-    }
-
-    /// Test AES-256-GCM-SIV encryption/decryption
-    pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
-        // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
-        // TC77
-        let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
-        let nonce = hex!("030000000000000000000000");
-        let aes = A::new(&test_key.into());
-        let msg = hex!("0100000000000000");
-        let mut buf = Vec::new();
-        buf.extend_from_slice(&msg);
-        let ct = hex!("c2ef328e5c71c83b");
-        let tag = hex!("843122130f7364b761e0b97427e3df28");
-        assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..8], &ct);
-        assert_eq!(&buf[8..], &tag);
-        assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..], &msg);
-        // TC78
-        let msg = hex!("010000000000000000000000");
-        let ct = hex!("9aab2aeb3faa0a34aea8e2b1");
-        let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e");
-        let mut buf = Vec::from(msg.as_slice());
-        assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..12], &ct);
-        assert_eq!(&buf[12..], &tag);
-        assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
-        assert_eq!(&buf[..], &msg);
-    }
-
-    /// Generates the test cases to validate the AES-128-GCM-SIV implementation.
-    /// For example, to test `MyAesGcmSiv128Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::gcm_siv::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_128_gcm_siv_test_cases)]
-    ///     fn aes_128_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSivImpl>) {
-    ///         testcase(MyAesGcmSiv128Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_128_gcm_siv_test)]
-    #[case::decrypt(aes_128_gcm_siv_test)]
-    fn aes_128_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes128Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-
-    /// Generates the test cases to validate the AES-256-GCM-SIV implementation.
-    /// For example, to test `MyAesGcmSiv256Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::gcm_siv::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_256_gcm_siv_test_cases)]
-    ///     fn aes_256_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSiv256Impl>) {
-    ///         testcase(MyAesGcmSiv256Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_256_gcm_siv_test)]
-    #[case::decrypt(aes_256_gcm_siv_test)]
-    fn aes_256_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes256Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/aes/mod.rs b/nearby/crypto/crypto_provider/src/aes/mod.rs
index 240f11d..9526488 100644
--- a/nearby/crypto/crypto_provider/src/aes/mod.rs
+++ b/nearby/crypto/crypto_provider/src/aes/mod.rs
@@ -161,207 +161,3 @@
         Self { key: arr }
     }
 }
-
-/// Module for testing implementations of this crate.
-#[cfg(feature = "testing")]
-pub mod testing {
-    use super::*;
-    pub use crate::testing::prelude::*;
-    use core::marker;
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// Test encryption with AES-128
-    pub fn aes_128_test_encrypt<A: AesEncryptCipher<Key = Aes128Key>>(
-        _marker: marker::PhantomData<A>,
-    ) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.1
-        let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
-        let mut block = [0_u8; 16];
-        let enc_cipher = A::new(&key);
-
-        block.copy_from_slice(&hex!("6bc1bee22e409f96e93d7e117393172a"));
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("3ad77bb40d7a3660a89ecaf32466ef97"), block);
-
-        block.copy_from_slice(&hex!("ae2d8a571e03ac9c9eb76fac45af8e51"));
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("f5d3d58503b9699de785895a96fdbaaf"), block);
-
-        block.copy_from_slice(&hex!("30c81c46a35ce411e5fbc1191a0a52ef"));
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("43b1cd7f598ece23881b00e3ed030688"), block);
-
-        block.copy_from_slice(&hex!("f69f2445df4f9b17ad2b417be66c3710"));
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("7b0c785e27e8ad3f8223207104725dd4"), block);
-    }
-
-    /// Test decryption with AES-128
-    pub fn aes_128_test_decrypt<A: AesDecryptCipher<Key = Aes128Key>>(
-        _marker: marker::PhantomData<A>,
-    ) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.2
-        let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
-        let mut block = [0_u8; 16];
-        let dec_cipher = A::new(&key);
-
-        block.copy_from_slice(&hex!("3ad77bb40d7a3660a89ecaf32466ef97"));
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("6bc1bee22e409f96e93d7e117393172a"), block);
-
-        block.copy_from_slice(&hex!("f5d3d58503b9699de785895a96fdbaaf"));
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("ae2d8a571e03ac9c9eb76fac45af8e51"), block);
-
-        block.copy_from_slice(&hex!("43b1cd7f598ece23881b00e3ed030688"));
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("30c81c46a35ce411e5fbc1191a0a52ef"), block);
-
-        block.copy_from_slice(&hex!("7b0c785e27e8ad3f8223207104725dd4"));
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("f69f2445df4f9b17ad2b417be66c3710"), block);
-    }
-
-    /// Test encryption with AES-256
-    pub fn aes_256_test_encrypt<A: AesEncryptCipher<Key = Aes256Key>>(
-        _marker: marker::PhantomData<A>,
-    ) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.5
-        let key: Aes256Key =
-            hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
-        let mut block: [u8; 16];
-        let enc_cipher = A::new(&key);
-
-        block = hex!("6bc1bee22e409f96e93d7e117393172a");
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("f3eed1bdb5d2a03c064b5a7e3db181f8"), block);
-
-        block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("591ccb10d410ed26dc5ba74a31362870"), block);
-
-        block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("b6ed21b99ca6f4f9f153e7b1beafed1d"), block);
-
-        block = hex!("f69f2445df4f9b17ad2b417be66c3710");
-        enc_cipher.encrypt(&mut block);
-        assert_eq!(hex!("23304b7a39f9f3ff067d8d8f9e24ecc7"), block);
-    }
-
-    /// Test decryption with AES-256
-    pub fn aes_256_test_decrypt<A: AesDecryptCipher<Key = Aes256Key>>(
-        _marker: marker::PhantomData<A>,
-    ) {
-        // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.6
-        let key: Aes256Key =
-            hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
-        let mut block: [u8; 16];
-        let dec_cipher = A::new(&key);
-
-        block = hex!("f3eed1bdb5d2a03c064b5a7e3db181f8");
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("6bc1bee22e409f96e93d7e117393172a"), block);
-
-        block = hex!("591ccb10d410ed26dc5ba74a31362870");
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("ae2d8a571e03ac9c9eb76fac45af8e51"), block);
-
-        block = hex!("b6ed21b99ca6f4f9f153e7b1beafed1d");
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("30c81c46a35ce411e5fbc1191a0a52ef"), block);
-
-        block = hex!("23304b7a39f9f3ff067d8d8f9e24ecc7");
-        dec_cipher.decrypt(&mut block);
-        assert_eq!(hex!("f69f2445df4f9b17ad2b417be66c3710"), block);
-    }
-
-    /// Generates the test cases to validate the AES-128 implementation.
-    /// For example, to test `MyAes128Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_128_encrypt_test_cases)]
-    ///     fn aes_128_tests(f: CryptoProviderTestCase<MyAes128Impl>) {
-    ///         f(MyAes128Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_128_test_encrypt)]
-    fn aes_128_encrypt_test_cases<A: AesFactory<Key = Aes128Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-
-    /// Generates the test cases to validate the AES-128 implementation.
-    /// For example, to test `MyAes128Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_128_decrypt_test_cases)]
-    ///     fn aes_128_tests(f: CryptoProviderTestCase<MyAes128Impl>) {
-    ///         f(MyAes128Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::decrypt(aes_128_test_decrypt)]
-    fn aes_128_decrypt_test_cases<F: AesFactory<Key = Aes128Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-
-    /// Generates the test cases to validate the AES-256 implementation.
-    /// For example, to test `MyAes256Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_256_encrypt_test_cases)]
-    ///     fn aes_256_tests(f: CryptoProviderTestCase<MyAes256Impl>) {
-    ///         f(MyAes256Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::encrypt(aes_256_test_encrypt)]
-    fn aes_256_encrypt_test_cases<F: AesFactory<Key = Aes256Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-
-    /// Generates the test cases to validate the AES-256 implementation.
-    /// For example, to test `MyAes256Impl`:
-    ///
-    /// ```
-    /// use crypto_provider::aes::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(aes_256_decrypt_test_cases)]
-    ///     fn aes_256_tests(f: CryptoProviderTestCase<MyAes256Impl>) {
-    ///         f(MyAes256Impl);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::decrypt(aes_256_test_decrypt)]
-    fn aes_256_decrypt_test_cases<F: AesFactory<Key = Aes256Key>>(
-        #[case] testcase: CryptoProviderTestCase<F>,
-    ) {
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/ed25519.rs b/nearby/crypto/crypto_provider/src/ed25519.rs
index 4cee2da..e8fc176 100644
--- a/nearby/crypto/crypto_provider/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider/src/ed25519.rs
@@ -112,146 +112,3 @@
 /// Error returned if invalid signature bytes are provided
 #[derive(Debug)]
 pub struct InvalidSignature;
-
-#[cfg(feature = "testing")]
-/// Utilities for testing. Implementations can use the test cases and functions provided to test
-/// their implementation.
-pub mod testing {
-    extern crate alloc;
-    extern crate std;
-
-    use crate::ed25519::{Ed25519Provider, KeyPair, PublicKey, Signature};
-    use alloc::borrow::ToOwned;
-    use alloc::string::String;
-    use alloc::vec::Vec;
-    use wycheproof::TestResult;
-
-    // These are test vectors from the creators of Ed25519: https://ed25519.cr.yp.to/ which are referenced
-    // as the SOT for the test vectors in the RFC: https://www.rfc-editor.org/rfc/rfc8032#section-7.1
-    // The vectors have been formatted into a easily parsable/readable format by libgcrypt which is
-    // also used for test cases in the above RFC:
-    // https://dev.gnupg.org/source/libgcrypt/browse/master/tests/t-ed25519.inp
-    const PATH_TO_RFC_VECTORS_FILE: &str =
-        "crypto/crypto_provider/src/testdata/ecdsa/rfc_test_vectors.txt";
-
-    /// Runs set of Ed25519 wycheproof test vectors against a provided ed25519 implementation
-    /// Tests vectors from Project Wycheproof: <https://github.com/google/wycheproof>
-    pub fn run_wycheproof_test_vectors<E>()
-    where
-        E: Ed25519Provider,
-    {
-        let test_set = wycheproof::eddsa::TestSet::load(wycheproof::eddsa::TestName::Ed25519)
-            .expect("should be able to load test set");
-
-        for test_group in test_set.test_groups {
-            let key_pair = test_group.key;
-            let public_key = key_pair.pk;
-            let secret_key = key_pair.sk;
-
-            for test in test_group.tests {
-                let tc_id = test.tc_id;
-                let comment = test.comment;
-                let sig = test.sig;
-                let msg = test.msg;
-
-                let valid = match test.result {
-                    TestResult::Invalid => false,
-                    TestResult::Valid | TestResult::Acceptable => true,
-                };
-                let result = run_test::<E>(
-                    public_key.clone(),
-                    secret_key.clone(),
-                    sig.clone(),
-                    msg.clone(),
-                );
-                if valid {
-                    if let Err(desc) = result {
-                        panic!(
-                            "\n\
-                         Failed test {}: {}\n\
-                         msg:\t{:?}\n\
-                         sig:\t{:?}\n\
-                         comment:\t{:?}\n",
-                            tc_id, desc, msg, sig, comment,
-                        );
-                    }
-                } else {
-                    assert!(result.is_err())
-                }
-            }
-        }
-    }
-
-    /// Runs the RFC specified test vectors against an Ed25519 implementation
-    pub fn run_rfc_test_vectors<E>()
-    where
-        E: Ed25519Provider,
-    {
-        let file_contents =
-            std::fs::read_to_string(test_helper::get_data_file(PATH_TO_RFC_VECTORS_FILE))
-                .expect("should be able to read file");
-
-        let mut split_cases: Vec<&str> = file_contents.as_str().split("\n\n").collect();
-        // remove the comments
-        split_cases.remove(0);
-        for case in split_cases {
-            let test_case: Vec<&str> = case.split('\n').collect();
-
-            let tc_id = extract_string(test_case[0]);
-            let sk = extract_hex(test_case[1]);
-            let pk = extract_hex(test_case[2]);
-            let msg = extract_hex(test_case[3]);
-            let sig = extract_hex(test_case[4]);
-
-            let result = run_test::<E>(pk.clone(), sk.clone(), sig.clone(), msg.clone());
-            if let Err(desc) = result {
-                panic!(
-                    "\n\
-                         Failed test {}: {}\n\
-                         msg:\t{:?}\n\
-                         sig:\t{:?}\n\"",
-                    tc_id, desc, msg, sig,
-                );
-            }
-        }
-    }
-
-    fn extract_hex(line: &str) -> Vec<u8> {
-        test_helper::string_to_hex(extract_string(line).as_str())
-    }
-
-    fn extract_string(line: &str) -> String {
-        line.split(':').collect::<Vec<&str>>()[1].trim().to_owned()
-    }
-
-    fn run_test<E>(
-        pub_key: Vec<u8>,
-        secret_key: Vec<u8>,
-        sig: Vec<u8>,
-        msg: Vec<u8>,
-    ) -> Result<(), &'static str>
-    where
-        E: Ed25519Provider,
-    {
-        let kp_bytes: [u8; 64] = [secret_key.as_slice(), pub_key.as_slice()]
-            .concat()
-            .try_into()
-            .map_err(|_| "invalid length keypair")?;
-        let kp = E::KeyPair::from_bytes(kp_bytes)
-            .map_err(|_| "Should be able to create Keypair from bytes")?;
-
-        let sig_result = kp.sign(msg.as_slice());
-        (sig.as_slice() == sig_result.to_bytes())
-            .then_some(())
-            .ok_or("sig not matching expected")?;
-        let signature = E::Signature::from_bytes(sig.as_slice())
-            .map_err(|_| "unable to parse sign from test case")?;
-
-        let pub_key = kp.public();
-        pub_key
-            .verify_strict(msg.as_slice(), &signature)
-            .map_err(|_| "verify failed")?;
-
-        Ok(())
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/elliptic_curve.rs b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
index 75b12b1..d06a21d 100644
--- a/nearby/crypto/crypto_provider/src/elliptic_curve.rs
+++ b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
@@ -57,18 +57,6 @@
     ) -> Result<<Self::Impl as EcdhProvider<C>>::SharedSecret, Self::Error>;
 }
 
-/// Trait for an ephemeral secret for functions used in testing.
-#[cfg(feature = "testing")]
-pub trait EphemeralSecretForTesting<C: Curve>: EphemeralSecret<C> {
-    /// Creates an ephemeral secret from the given private and public components.
-    fn from_private_components(
-        _private_bytes: &[u8; 32],
-        _public_key: &<Self::Impl as EcdhProvider<C>>::PublicKey,
-    ) -> Result<Self, Self::Error>
-    where
-        Self: Sized;
-}
-
 /// Trait for a public key used for elliptic curve diffie hellman.
 pub trait PublicKey<E: Curve>: Sized + PartialEq + Debug {
     /// The error type associated with Public Key.
diff --git a/nearby/crypto/crypto_provider/src/hkdf.rs b/nearby/crypto/crypto_provider/src/hkdf.rs
index c905302..dab4e13 100644
--- a/nearby/crypto/crypto_provider/src/hkdf.rs
+++ b/nearby/crypto/crypto_provider/src/hkdf.rs
@@ -33,292 +33,3 @@
 /// an invalid length
 #[derive(Debug)]
 pub struct InvalidLength;
-
-/// Test cases exported for testing specific hkdf implementations
-#[cfg(feature = "testing")]
-pub mod testing {
-    extern crate alloc;
-    use crate::hkdf::Hkdf;
-    pub use crate::testing::prelude::*;
-    use crate::CryptoProvider;
-    use alloc::vec;
-    use alloc::vec::Vec;
-    use core::iter;
-    use core::marker::PhantomData;
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// Generates the test cases to validate the hkdf implementation.
-    /// For example, to test `MyCryptoProvider`:
-    ///
-    /// ```
-    /// mod tests {
-    ///     use std::marker::PhantomData;
-    ///     use crypto_provider::testing::CryptoProviderTestCase;
-    ///     #[apply(hkdf_test_cases)]
-    ///     fn hkdf_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>){
-    ///         testcase(PhantomData::<MyCryptoProvider>);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::basic_test_hkdf(basic_test_hkdf)]
-    #[case::test_rfc5869_sha256(test_rfc5869_sha256)]
-    #[case::test_lengths(test_lengths)]
-    #[case::test_max_length(test_max_length)]
-    #[case::test_max_length_exceeded(test_max_length_exceeded)]
-    #[case::test_unsupported_length(test_unsupported_length)]
-    #[case::test_expand_multi_info(test_expand_multi_info)]
-    #[case::run_hkdf_sha256_vectors(run_hkdf_sha256_vectors)]
-    #[case::run_hkdf_sha512_vectors(run_hkdf_sha512_vectors)]
-    fn hkdf_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
-
-    const MAX_SHA256_LENGTH: usize = 255 * (256 / 8); // =8160
-
-    ///
-    pub struct Test<'a> {
-        ikm: &'a [u8],
-        salt: &'a [u8],
-        info: &'a [u8],
-        okm: &'a [u8],
-    }
-
-    /// data taken from sample code in Readme of crates.io page
-    pub fn basic_test_hkdf<C: CryptoProvider>(_: PhantomData<C>) {
-        let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
-        let salt = hex!("000102030405060708090a0b0c");
-        let info = hex!("f0f1f2f3f4f5f6f7f8f9");
-
-        let hk = C::HkdfSha256::new(Some(&salt[..]), &ikm);
-        let mut okm = [0u8; 42];
-        hk.expand(&info, &mut okm)
-            .expect("42 is a valid length for Sha256 to output");
-
-        let expected = hex!(
-            "
-        3cb25f25faacd57a90434f64d0362f2a
-        2d2d0a90cf1a5a4c5db02d56ecc4c5bf
-        34007208d5b887185865
-        "
-        );
-        assert_eq!(okm, expected);
-    }
-
-    // Test Vectors from https://tools.ietf.org/html/rfc5869.
-    #[rustfmt::skip]
-    ///
-    pub fn test_rfc5869_sha256<C: CryptoProvider>(_: PhantomData<C>) {
-        let tests = [
-            Test {
-                // Test Case 1
-                ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
-                salt: &hex!("000102030405060708090a0b0c"),
-                info: &hex!("f0f1f2f3f4f5f6f7f8f9"),
-                okm: &hex!("
-                    3cb25f25faacd57a90434f64d0362f2a
-                    2d2d0a90cf1a5a4c5db02d56ecc4c5bf
-                    34007208d5b887185865
-                "),
-            },
-            Test {
-                // Test Case 2
-                ikm: &hex!("
-                    000102030405060708090a0b0c0d0e0f
-                    101112131415161718191a1b1c1d1e1f
-                    202122232425262728292a2b2c2d2e2f
-                    303132333435363738393a3b3c3d3e3f
-                    404142434445464748494a4b4c4d4e4f
-                "),
-                salt: &hex!("
-                    606162636465666768696a6b6c6d6e6f
-                    707172737475767778797a7b7c7d7e7f
-                    808182838485868788898a8b8c8d8e8f
-                    909192939495969798999a9b9c9d9e9f
-                    a0a1a2a3a4a5a6a7a8a9aaabacadaeaf
-                "),
-                info: &hex!("
-                    b0b1b2b3b4b5b6b7b8b9babbbcbdbebf
-                    c0c1c2c3c4c5c6c7c8c9cacbcccdcecf
-                    d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
-                    e0e1e2e3e4e5e6e7e8e9eaebecedeeef
-                    f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
-                "),
-                okm: &hex!("
-                    b11e398dc80327a1c8e7f78c596a4934
-                    4f012eda2d4efad8a050cc4c19afa97c
-                    59045a99cac7827271cb41c65e590e09
-                    da3275600c2f09b8367793a9aca3db71
-                    cc30c58179ec3e87c14c01d5c1f3434f
-                    1d87
-                "),
-            },
-            Test {
-                // Test Case 3
-                ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
-                salt: &hex!(""),
-                info: &hex!(""),
-                okm: &hex!("
-                    8da4e775a563c18f715f802a063c5a31
-                    b8a11f5c5ee1879ec3454e5f3c738d2d
-                    9d201395faa4b61a96c8
-                "),
-            },
-        ];
-        for Test { ikm, salt, info, okm } in tests.iter() {
-            let salt = if salt.is_empty() {
-                None
-            } else {
-                Some(&salt[..])
-            };
-            let hkdf = C::HkdfSha256::new(salt, ikm);
-            let mut okm2 = vec![0u8; okm.len()];
-            assert!(hkdf.expand(&info[..], &mut okm2).is_ok());
-            assert_eq!(okm2[..], okm[..]);
-        }
-    }
-
-    ///
-    pub fn test_lengths<C: CryptoProvider>(_: PhantomData<C>) {
-        let hkdf = C::HkdfSha256::new(None, &[]);
-        let mut longest = vec![0u8; MAX_SHA256_LENGTH];
-        assert!(hkdf.expand(&[], &mut longest).is_ok());
-        // Runtime is O(length), so exhaustively testing all legal lengths
-        // would take too long (at least without --release). Only test a
-        // subset: the first 500, the last 10, and every 100th in between.
-        // 0 is an invalid key length for openssl, so start at 1
-        let lengths = (1..MAX_SHA256_LENGTH + 1)
-            .filter(|&len| !(500..=MAX_SHA256_LENGTH - 10).contains(&len) || len % 100 == 0);
-
-        for length in lengths {
-            let mut okm = vec![0u8; length];
-
-            assert!(hkdf.expand(&[], &mut okm).is_ok());
-            assert_eq!(okm.len(), length);
-            assert_eq!(okm[..], longest[..length]);
-        }
-    }
-
-    ///
-    pub fn test_max_length<C: CryptoProvider>(_: PhantomData<C>) {
-        let hkdf = C::HkdfSha256::new(Some(&[]), &[]);
-        let mut okm = vec![0u8; MAX_SHA256_LENGTH];
-        assert!(hkdf.expand(&[], &mut okm).is_ok());
-    }
-
-    ///
-    pub fn test_max_length_exceeded<C: CryptoProvider>(_: PhantomData<C>) {
-        let hkdf = C::HkdfSha256::new(Some(&[]), &[]);
-        let mut okm = vec![0u8; MAX_SHA256_LENGTH + 1];
-        assert!(hkdf.expand(&[], &mut okm).is_err());
-    }
-
-    ///
-    pub fn test_unsupported_length<C: CryptoProvider>(_: PhantomData<C>) {
-        let hkdf = C::HkdfSha256::new(Some(&[]), &[]);
-        let mut okm = vec![0u8; 90000];
-        assert!(hkdf.expand(&[], &mut okm).is_err());
-    }
-
-    ///
-    pub fn test_expand_multi_info<C: CryptoProvider>(_: PhantomData<C>) {
-        let info_components = &[
-            &b"09090909090909090909090909090909090909090909"[..],
-            &b"8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a"[..],
-            &b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0"[..],
-            &b"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4"[..],
-            &b"1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d"[..],
-        ];
-
-        let hkdf = C::HkdfSha256::new(None, b"some ikm here");
-
-        // Compute HKDF-Expand on the concatenation of all the info components
-        let mut oneshot_res = [0u8; 16];
-        hkdf.expand(&info_components.concat(), &mut oneshot_res)
-            .unwrap();
-
-        // Now iteratively join the components of info_components until it's all 1 component. The value
-        // of HKDF-Expand should be the same throughout
-        let mut num_concatted = 0;
-        let mut info_head = Vec::new();
-
-        while num_concatted < info_components.len() {
-            info_head.extend(info_components[num_concatted]);
-
-            // Build the new input to be the info head followed by the remaining components
-            let input: Vec<&[u8]> = iter::once(info_head.as_slice())
-                .chain(info_components.iter().cloned().skip(num_concatted + 1))
-                .collect();
-
-            // Compute and compare to the one-shot answer
-            let mut multipart_res = [0u8; 16];
-            hkdf.expand_multi_info(&input, &mut multipart_res).unwrap();
-            assert_eq!(multipart_res, oneshot_res);
-            num_concatted += 1;
-        }
-    }
-
-    ///
-    pub fn run_hkdf_sha256_vectors<C: CryptoProvider>(_: PhantomData<C>) {
-        run_hkdf_test_vectors::<C::HkdfSha256>(HashAlg::Sha256)
-    }
-
-    ///
-    pub fn run_hkdf_sha512_vectors<C: CryptoProvider>(_: PhantomData<C>) {
-        run_hkdf_test_vectors::<C::HkdfSha512>(HashAlg::Sha512)
-    }
-
-    enum HashAlg {
-        Sha256,
-        Sha512,
-    }
-
-    ///
-    fn run_hkdf_test_vectors<K: Hkdf>(hash: HashAlg) {
-        let test_name = match hash {
-            HashAlg::Sha256 => wycheproof::hkdf::TestName::HkdfSha256,
-            HashAlg::Sha512 => wycheproof::hkdf::TestName::HkdfSha512,
-        };
-
-        let test_set =
-            wycheproof::hkdf::TestSet::load(test_name).expect("should be able to load test set");
-        for test_group in test_set.test_groups {
-            for test in test_group.tests {
-                let ikm = test.ikm;
-                let salt = test.salt;
-                let info = test.info;
-                let okm = test.okm;
-                let tc_id = test.tc_id;
-                if let Some(desc) = run_test::<K>(
-                    ikm.as_slice(),
-                    salt.as_slice(),
-                    info.as_slice(),
-                    okm.as_slice(),
-                ) {
-                    panic!(
-                        "\n\
-                         Failed test {tc_id}: {desc}\n\
-                         ikm:\t{ikm:?}\n\
-                         salt:\t{salt:?}\n\
-                         info:\t{info:?}\n\
-                         okm:\t{okm:?}\n"
-                    );
-                }
-            }
-        }
-    }
-
-    fn run_test<K: Hkdf>(ikm: &[u8], salt: &[u8], info: &[u8], okm: &[u8]) -> Option<&'static str> {
-        let prk = K::new(Some(salt), ikm);
-        let mut got_okm = vec![0; okm.len()];
-
-        if prk.expand(info, &mut got_okm).is_err() {
-            return Some("prk expand");
-        }
-        if got_okm != okm {
-            return Some("mismatch in okm");
-        }
-        None
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/hmac.rs b/nearby/crypto/crypto_provider/src/hmac.rs
index 40be189..c5a9ae1 100644
--- a/nearby/crypto/crypto_provider/src/hmac.rs
+++ b/nearby/crypto/crypto_provider/src/hmac.rs
@@ -44,125 +44,3 @@
 /// Error output when the provided key material length is invalid
 #[derive(Debug)]
 pub struct InvalidLength;
-
-/// Test cases exported for testing specific hmac implementations
-#[cfg(feature = "testing")]
-pub mod testing {
-    use crate::hmac::Hmac;
-    use crate::rstest_reuse::template;
-    pub use crate::testing::prelude::*;
-    use crate::CryptoProvider;
-    use core::cmp::min;
-    use core::marker::PhantomData;
-    use wycheproof::TestResult;
-
-    /// Generates the test cases to validate the hmac implementation.
-    /// For example, to test `MyCryptoProvider`:
-    ///
-    /// ```
-    /// mod tests {
-    ///     use std::marker::PhantomData;
-    ///     use crypto_provider::testing::CryptoProviderTestCase;
-    ///     #[apply(hmac_test_cases)]
-    ///     fn hmac_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>){
-    ///         testcase(PhantomData::<MyCryptoProvider>);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::hmac_sha256_test_vectors(hmac_sha256_test_vectors)]
-    #[case::hmac_sha512_test_vectors(hmac_sha512_test_vectors)]
-    fn hmac_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
-
-    /// Run wycheproof hmac sha256 test vectors on provided CryptoProvider
-    pub fn hmac_sha256_test_vectors<C: CryptoProvider>(_: PhantomData<C>) {
-        run_hmac_test_vectors::<32, C::HmacSha256>(HashAlg::Sha256)
-    }
-
-    /// Run wycheproof hmac sha512 test vectors on provided CryptoProvider
-    pub fn hmac_sha512_test_vectors<C: CryptoProvider>(_: PhantomData<C>) {
-        run_hmac_test_vectors::<64, C::HmacSha512>(HashAlg::Sha512)
-    }
-
-    enum HashAlg {
-        Sha256,
-        Sha512,
-    }
-
-    // Tests vectors from Project Wycheproof:
-    // https://github.com/google/wycheproof
-    fn run_hmac_test_vectors<const N: usize, H: Hmac<N>>(hash: HashAlg) {
-        let test_name = match hash {
-            HashAlg::Sha256 => wycheproof::mac::TestName::HmacSha256,
-            HashAlg::Sha512 => wycheproof::mac::TestName::HmacSha512,
-        };
-        let test_set =
-            wycheproof::mac::TestSet::load(test_name).expect("should be able to load test set");
-
-        for test_group in test_set.test_groups {
-            for test in test_group.tests {
-                let key = test.key;
-                let msg = test.msg;
-                let tag = test.tag;
-                let tc_id = test.tc_id;
-                let valid = match test.result {
-                    TestResult::Valid | TestResult::Acceptable => true,
-                    TestResult::Invalid => false,
-                };
-
-                if let Some(desc) =
-                    run_test::<N, H>(key.as_slice(), msg.as_slice(), tag.as_slice(), valid)
-                {
-                    panic!(
-                        "\n\
-                         Failed test {tc_id}: {desc}\n\
-                         key:\t{key:?}\n\
-                         msg:\t{msg:?}\n\
-                         tag:\t{tag:?}\n",
-                    );
-                }
-            }
-        }
-    }
-
-    fn run_test<const N: usize, H: Hmac<N>>(
-        key: &[u8],
-        input: &[u8],
-        tag: &[u8],
-        valid_data: bool,
-    ) -> Option<&'static str> {
-        let mut mac = H::new_from_slice(key).unwrap();
-        mac.update(input);
-        let result = mac.finalize();
-        let n = tag.len();
-        let result_bytes = &result[..n];
-
-        if valid_data {
-            if result_bytes != tag {
-                return Some("whole message");
-            }
-        } else {
-            return if result_bytes == tag {
-                Some("invalid should not match")
-            } else {
-                None
-            };
-        }
-
-        // test reading different chunk sizes
-        for chunk_size in 1..min(64, input.len()) {
-            let mut mac = H::new_from_slice(key).unwrap();
-            for chunk in input.chunks(chunk_size) {
-                mac.update(chunk);
-            }
-            let res = mac.verify_truncated_left(tag);
-            if res.is_err() {
-                return Some("chunked message");
-            }
-        }
-
-        None
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/lib.rs b/nearby/crypto/crypto_provider/src/lib.rs
index 4eb5563..3ebaf9b 100644
--- a/nearby/crypto/crypto_provider/src/lib.rs
+++ b/nearby/crypto/crypto_provider/src/lib.rs
@@ -19,9 +19,6 @@
 
 use core::fmt::Debug;
 
-#[cfg(feature = "testing")]
-pub use rstest_reuse;
-
 use crate::aes::{Aes128Key, Aes256Key};
 
 /// mod containing hmac trait
@@ -124,108 +121,3 @@
         unimplemented!()
     }
 }
-
-/// Utilities for testing implementations of this crate.
-#[cfg(feature = "testing")]
-pub mod testing {
-    extern crate alloc;
-
-    use alloc::{format, string::String};
-    use core::marker::PhantomData;
-
-    use hex_literal::hex;
-    use rand::{Rng, RngCore};
-    use rstest_reuse::template;
-
-    use crate::CryptoProvider;
-
-    /// Common items that needs to be imported to use these test cases
-    pub mod prelude {
-        pub use rstest::rstest;
-        pub use rstest_reuse;
-        pub use rstest_reuse::apply;
-
-        pub use super::CryptoProviderTestCase;
-    }
-
-    /// A test case for Crypto Provider. A test case is a function that panics if the test fails.
-    pub type CryptoProviderTestCase<T> = fn(PhantomData<T>);
-
-    #[derive(Debug)]
-    pub(crate) struct TestError(String);
-
-    impl TestError {
-        pub(crate) fn new<D: core::fmt::Debug>(value: D) -> Self {
-            Self(format!("{value:?}"))
-        }
-    }
-
-    /// Test for `constant_time_eq` when the two inputs are equal.
-    pub fn constant_time_eq_test_equal<C: CryptoProvider>(_marker: PhantomData<C>) {
-        assert!(C::constant_time_eq(
-            &hex!("00010203040506070809"),
-            &hex!("00010203040506070809")
-        ));
-    }
-
-    /// Test for `constant_time_eq` when the two inputs are not equal.
-    pub fn constant_time_eq_test_not_equal<C: CryptoProvider>(_marker: PhantomData<C>) {
-        assert!(!C::constant_time_eq(
-            &hex!("00010203040506070809"),
-            &hex!("00000000000000000000")
-        ));
-    }
-
-    /// Random tests for `constant_time_eq`.
-    pub fn constant_time_eq_random_test<C: CryptoProvider>(_marker: PhantomData<C>) {
-        let mut rng = rand::thread_rng();
-        for _ in 1..100 {
-            // Test using "oracle" of ==, with possibly different lengths for a and b
-            let mut a = alloc::vec![0; rng.gen_range(1..1000)];
-            rng.fill_bytes(&mut a);
-            let mut b = alloc::vec![0; rng.gen_range(1..1000)];
-            rng.fill_bytes(&mut b);
-            assert_eq!(C::constant_time_eq(&a, &b), a == b);
-        }
-
-        for _ in 1..10000 {
-            // Test using "oracle" of ==, with same lengths for a and b
-            let len = rng.gen_range(1..1000);
-            let mut a = alloc::vec![0; len];
-            rng.fill_bytes(&mut a);
-            let mut b = alloc::vec![0; len];
-            rng.fill_bytes(&mut b);
-            assert_eq!(C::constant_time_eq(&a, &b), a == b);
-        }
-
-        for _ in 1..10000 {
-            // Clones and the original should always be equal
-            let mut a = alloc::vec![0; rng.gen_range(1..1000)];
-            rng.fill_bytes(&mut a);
-            assert!(C::constant_time_eq(&a, &a.clone()));
-        }
-    }
-
-    /// Generates the test cases to validate the P256 implementation.
-    /// For example, to test `MyCryptoProvider`:
-    ///
-    /// ```
-    /// use crypto_provider::p256::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(constant_time_eq_test_cases)]
-    ///     fn constant_time_eq_tests(
-    ///             testcase: CryptoProviderTestCase<MyCryptoProvider>) {
-    ///         testcase(PhantomData);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::constant_time_eq_test_not_equal(constant_time_eq_test_not_equal)]
-    #[case::constant_time_eq_test_equal(constant_time_eq_test_equal)]
-    #[case::constant_time_eq_random_test(constant_time_eq_random_test)]
-    fn constant_time_eq_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {
-    }
-}
diff --git a/nearby/crypto/crypto_provider/src/p256.rs b/nearby/crypto/crypto_provider/src/p256.rs
index be14a39..90d3542 100644
--- a/nearby/crypto/crypto_provider/src/p256.rs
+++ b/nearby/crypto/crypto_provider/src/p256.rs
@@ -74,236 +74,3 @@
 {
     type PublicKey = E::PublicKey;
 }
-
-/// Utilities for testing. Implementations can use the test cases and functions provided to test
-/// their implementation.
-#[cfg(feature = "testing")]
-pub mod testing {
-    extern crate std;
-    use super::{P256PublicKey, P256};
-    pub use crate::testing::prelude::*;
-    use crate::{
-        elliptic_curve::{EcdhProvider, EphemeralSecret, EphemeralSecretForTesting, PublicKey},
-        testing::TestError,
-        CryptoRng,
-    };
-    use core::marker::PhantomData;
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// An ECDH provider that provides associated types for testing purposes. This can be mostly
-    /// considered "aliases" for the otherwise long fully-qualified associated types.
-    pub trait EcdhProviderForP256Test {
-        /// The ECDH Provider that is "wrapped" by this type.
-        type EcdhProvider: EcdhProvider<
-            P256,
-            PublicKey = <Self as EcdhProviderForP256Test>::PublicKey,
-            EphemeralSecret = <Self as EcdhProviderForP256Test>::EphemeralSecret,
-            SharedSecret = <Self as EcdhProviderForP256Test>::SharedSecret,
-        >;
-        /// The public key type.
-        type PublicKey: P256PublicKey;
-        /// The ephemeral secret type.
-        type EphemeralSecret: EphemeralSecretForTesting<P256, Impl = Self::EcdhProvider>;
-        /// The shared secret type.
-        type SharedSecret: Into<[u8; 32]>;
-    }
-
-    impl<E> EcdhProviderForP256Test for E
-    where
-        E: EcdhProvider<P256>,
-        E::PublicKey: P256PublicKey,
-        E::EphemeralSecret: EphemeralSecretForTesting<P256>,
-    {
-        type EcdhProvider = E;
-        type PublicKey = E::PublicKey;
-        type EphemeralSecret = E::EphemeralSecret;
-        type SharedSecret = E::SharedSecret;
-    }
-
-    /// Test for P256PublicKey::to_bytes
-    pub fn to_bytes_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        let sec1_bytes = hex!(
-            "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
-             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
-        );
-        let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
-        let sec1_bytes_compressed =
-            hex!("02756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
-        assert_eq!(sec1_bytes_compressed.to_vec(), key.to_bytes());
-    }
-
-    /// Random test for P256PublicKey::to_bytes
-    pub fn to_bytes_random_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        for _ in 1..100 {
-            let public_key_bytes =
-                E::EphemeralSecret::generate_random(&mut <E::EphemeralSecret as EphemeralSecret<
-                    P256,
-                >>::Rng::new())
-                .public_key_bytes();
-            let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
-            assert_eq!(
-                E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
-                public_key,
-                "from_bytes should return the same key for `{public_key_bytes:?}`",
-            );
-        }
-    }
-
-    /// Test for P256PublicKey::from_affine_coordinates
-    pub fn from_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        // https://www.secg.org/sec1-v2.pdf, section 2.3.3
-        let x = hex!("756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
-        let y = hex!("f0b6da270d2a58a060228bbe76c6dc1643088107636deff8aa79e8002a157b92");
-        let sec1 = hex!(
-            "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
-             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
-        );
-        let expected_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
-        assert!(
-            E::PublicKey::from_affine_coordinates(&x, &y).unwrap() == expected_key,
-            "Public key does not match"
-        );
-    }
-
-    /// Test for P256PublicKey::from_affine_coordinates
-    pub fn from_affine_coordinates_not_on_curve_test<E: EcdhProviderForP256Test>(
-        _: PhantomData<E>,
-    ) {
-        // (Invalid) coordinate from wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 193
-        let x = hex!("0000000000000000000000000000000000000000000000000000000000000000");
-        let y = hex!("0000000000000000000000000000000000000000000000000000000000000000");
-        let result = E::PublicKey::from_affine_coordinates(&x, &y);
-        assert!(
-            result.is_err(),
-            "Creating public key from invalid affine coordinate should fail"
-        );
-    }
-
-    /// Test for P256PublicKey::from_sec1_bytes
-    pub fn from_sec1_bytes_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        // (Invalid) sec1 encoding from wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 193
-        let sec1 = hex!(
-            "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-             00000000000000000000000000000000000000000000"
-        );
-        let result = E::PublicKey::from_sec1_bytes(&sec1);
-        assert!(
-            result.is_err(),
-            "Creating public key from point not on curve should fail"
-        );
-    }
-
-    /// Test for P256PublicKey::to_affine_coordinates
-    pub fn public_key_to_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        // https://www.secg.org/sec1-v2.pdf, section 2.3.3
-        let expected_x = hex!("756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
-        let expected_y = hex!("f0b6da270d2a58a060228bbe76c6dc1643088107636deff8aa79e8002a157b92");
-        let sec1 = hex!(
-            "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
-             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
-        );
-        let public_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
-        let (actual_x, actual_y) = public_key.to_affine_coordinates().unwrap();
-        assert_eq!(actual_x, expected_x);
-        assert_eq!(actual_y, expected_y);
-    }
-
-    /// Test for P256 Diffie-Hellman key exchange.
-    pub fn p256_ecdh_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        // From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1
-        // http://google3/third_party/wycheproof/testvectors/ecdh_secp256r1_ecpoint_test.json;l=22;rcl=375894991
-        // sec1 public key manually extracted from the ASN encoded test data
-        let public_key_sec1 = hex!(
-            "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f
-            26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf"
-        );
-        let private = hex!("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346");
-        let expected_shared_secret =
-            hex!("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285");
-        let actual_shared_secret = p256_ecdh_test_impl::<E>(&public_key_sec1, &private).unwrap();
-        assert_eq!(actual_shared_secret.into(), expected_shared_secret);
-    }
-
-    fn p256_ecdh_test_impl<E: EcdhProviderForP256Test>(
-        public_key_sec1: &[u8],
-        private: &[u8; 32],
-    ) -> Result<E::SharedSecret, TestError> {
-        let public_key = E::PublicKey::from_sec1_bytes(public_key_sec1).map_err(TestError::new)?;
-        let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
-            .map_err(TestError::new)?;
-        ephemeral_secret
-            .diffie_hellman(&public_key)
-            .map_err(TestError::new)
-    }
-
-    /// Wycheproof test for P256 Diffie-Hellman.
-    pub fn wycheproof_p256_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
-        // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/ecdh_secp256r1_ecpoint_test.json
-        let test_set =
-            wycheproof::ecdh::TestSet::load(wycheproof::ecdh::TestName::EcdhSecp256r1Ecpoint)
-                .unwrap();
-        for test_group in test_set.test_groups {
-            for test in test_group.tests {
-                if test.private_key.len() != 32 {
-                    // Some Wycheproof test cases have private key length that are not 32 bytes, but
-                    // the RustCrypto implementation doesn't support that (it always take 32 bytes
-                    // from the given RNG when generating a new key).
-                    continue;
-                };
-                std::println!("Testing {}", test.tc_id);
-                let result = p256_ecdh_test_impl::<E>(
-                    &test.public_key,
-                    &test
-                        .private_key
-                        .try_into()
-                        .expect("Private key should be 32 bytes long"),
-                );
-                match test.result {
-                    wycheproof::TestResult::Valid => {
-                        let shared_secret =
-                            result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
-                        assert_eq!(test.shared_secret, shared_secret.into());
-                    }
-                    wycheproof::TestResult::Invalid => {
-                        result
-                            .err()
-                            .unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
-                    }
-                    wycheproof::TestResult::Acceptable => {
-                        if let Ok(shared_secret) = result {
-                            assert_eq!(test.shared_secret, shared_secret.into());
-                        }
-                        // Test passes if `result` is an error because this test is "acceptable"
-                    }
-                }
-            }
-        }
-    }
-
-    /// Generates the test cases to validate the P256 implementation.
-    /// For example, to test `MyCryptoProvider`:
-    ///
-    /// ```
-    /// use crypto_provider::p256::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(p256_test_cases)]
-    ///     fn p256_tests(testcase: CryptoProviderTestCase<MyCryptoProvider> {
-    ///         testcase(PhantomData::<MyCryptoProvider>);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::to_bytes(to_bytes_test)]
-    #[case::to_bytes_random(to_bytes_random_test)]
-    #[case::from_sec1_bytes_not_on_curve(from_sec1_bytes_not_on_curve_test)]
-    #[case::from_affine_coordinates(from_affine_coordinates_test)]
-    #[case::from_affine_coordinates_not_on_curve(from_affine_coordinates_not_on_curve_test)]
-    #[case::public_key_to_affine_coordinates(public_key_to_affine_coordinates_test)]
-    #[case::p256_ecdh(p256_ecdh_test)]
-    #[case::wycheproof_p256(wycheproof_p256_test)]
-    fn p256_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
-}
diff --git a/nearby/crypto/crypto_provider/src/sha2.rs b/nearby/crypto/crypto_provider/src/sha2.rs
index b742303..7af7c96 100644
--- a/nearby/crypto/crypto_provider/src/sha2.rs
+++ b/nearby/crypto/crypto_provider/src/sha2.rs
@@ -23,115 +23,3 @@
     /// Computes the SHA2 512-bit hash.
     fn sha512(input: &[u8]) -> [u8; 64];
 }
-
-/// Utilities for testing. Implementations can use the test cases and functions provided to test
-/// their implementation.
-#[cfg(feature = "testing")]
-pub mod testing {
-
-    extern crate alloc;
-    extern crate std;
-    use super::{Sha256, Sha512};
-    pub use crate::testing::prelude::*;
-    use crate::CryptoProvider;
-    use alloc::vec::Vec;
-    use core::{marker::PhantomData, str::FromStr};
-    use hex::FromHex;
-    pub use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// Test vectors from SHA256ShortMsg.rsp in
-    /// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
-    pub fn sha256_cavp_short_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
-        sha256_cavp_vector_test::<C>(include_str!("testdata/SHA256ShortMsg.rsp"));
-    }
-
-    /// Test vectors from SHA256LongMsg.rsp in
-    /// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
-    pub fn sha256_cavp_long_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
-        sha256_cavp_vector_test::<C>(include_str!("testdata/SHA256LongMsg.rsp"));
-    }
-
-    /// Test vectors from SHA512ShortMsg.rsp in
-    /// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
-    pub fn sha512_cavp_short_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
-        sha512_cavp_vector_test::<C>(include_str!("testdata/SHA512ShortMsg.rsp"));
-    }
-
-    /// Test vectors from SHA512LongMsg.rsp in
-    /// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
-    pub fn sha512_cavp_long_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
-        sha512_cavp_vector_test::<C>(include_str!("testdata/SHA512LongMsg.rsp"));
-    }
-
-    /// Test vectors an rsp file in
-    /// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
-    fn sha256_cavp_vector_test<C: CryptoProvider>(cavp_file_contents: &str) {
-        sha_cavp_vector_test(<C::Sha256 as Sha256>::sha256, cavp_file_contents)
-    }
-
-    /// Test vectors an rsp file in
-    /// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
-    fn sha512_cavp_vector_test<C: CryptoProvider>(cavp_file_contents: &str) {
-        sha_cavp_vector_test(<C::Sha512 as Sha512>::sha512, cavp_file_contents)
-    }
-
-    fn sha_cavp_vector_test<const N: usize>(
-        hash_func: impl Fn(&[u8]) -> [u8; N],
-        cavp_file_contents: &str,
-    ) {
-        let test_cases = cavp_file_contents.split("\n\n").filter_map(|chunk| {
-            let mut len: Option<usize> = None;
-            let mut msg: Option<Vec<u8>> = None;
-            let mut md: Option<Vec<u8>> = None;
-            for line in chunk.split('\n') {
-                if line.starts_with('#') || line.is_empty() || line == std::format!("[L = {N}]") {
-                    continue;
-                } else if let Some(len_str) = line.strip_prefix("Len = ") {
-                    len = Some(FromStr::from_str(len_str).unwrap());
-                } else if let Some(hex_str) = line.strip_prefix("Msg = ") {
-                    msg = Some(Vec::<u8>::from_hex(hex_str).unwrap());
-                } else if let Some(hex_str) = line.strip_prefix("MD = ") {
-                    md = Some(Vec::<u8>::from_hex(hex_str).unwrap());
-                } else {
-                    panic!("Unexpected line in test file: `{}`", line);
-                }
-            }
-            if let (Some(len), Some(msg), Some(md)) = (len, msg, md) {
-                Some((len, msg, md))
-            } else {
-                None
-            }
-        });
-        for (len, mut msg, md) in test_cases {
-            if len == 0 {
-                // Truncate len = 0, since the test file has "Msg = 00" in there that should be
-                // ignored.
-                msg.truncate(0);
-            }
-            assert_eq!(msg.len(), len / 8);
-            let md_arr: [u8; N] = md.try_into().unwrap();
-            assert_eq!(hash_func(&msg), md_arr);
-        }
-    }
-
-    /// Generates the test cases to validate the SHA2 implementation.
-    /// For example, to test `MyCryptoProvider`:
-    ///
-    /// ```
-    /// use crypto_provider::sha2::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(sha2_test_cases)]
-    ///     fn sha2_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>) {
-    ///         testcase(PhantomData::<MyCryptoProvider>);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::sha256_cavp_short_vector(sha256_cavp_short_vector_test)]
-    #[case::sha256_cavp_long_vector(sha256_cavp_long_vector_test)]
-    fn sha2_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
-}
diff --git a/nearby/crypto/crypto_provider/src/x25519.rs b/nearby/crypto/crypto_provider/src/x25519.rs
index 0579159..f77a772 100644
--- a/nearby/crypto/crypto_provider/src/x25519.rs
+++ b/nearby/crypto/crypto_provider/src/x25519.rs
@@ -17,156 +17,3 @@
 /// Marker type for X25519 implementation. This is used by EcdhProvider as its type parameter.
 pub enum X25519 {}
 impl Curve for X25519 {}
-
-/// Utilities for testing. Implementations can use the test cases and functions provided to test
-/// their implementation.
-#[cfg(feature = "testing")]
-pub mod testing {
-    use super::X25519;
-    pub use crate::testing::prelude::*;
-    use crate::{
-        elliptic_curve::{EcdhProvider, EphemeralSecret, EphemeralSecretForTesting, PublicKey},
-        testing::TestError,
-        CryptoRng,
-    };
-    use core::marker::PhantomData;
-    use hex_literal::hex;
-    use rstest_reuse::template;
-
-    /// An ECDH provider that provides associated types for testing purposes. This can be mostly
-    /// considered "aliases" for the otherwise long fully-qualified associated types.
-    pub trait EcdhProviderForX25519Test {
-        /// The ECDH Provider that is "wrapped" by this type.
-        type EcdhProvider: EcdhProvider<
-            X25519,
-            PublicKey = <Self as EcdhProviderForX25519Test>::PublicKey,
-            EphemeralSecret = <Self as EcdhProviderForX25519Test>::EphemeralSecret,
-            SharedSecret = <Self as EcdhProviderForX25519Test>::SharedSecret,
-        >;
-        /// The public key type.
-        type PublicKey: PublicKey<X25519>;
-        /// The ephemeral secret type.
-        type EphemeralSecret: EphemeralSecretForTesting<X25519, Impl = Self::EcdhProvider>;
-        /// The shared secret type.
-        type SharedSecret: Into<[u8; 32]>;
-    }
-
-    impl<E> EcdhProviderForX25519Test for E
-    where
-        E: EcdhProvider<X25519>,
-        E::PublicKey: PublicKey<X25519>,
-        E::EphemeralSecret: EphemeralSecretForTesting<X25519>,
-    {
-        type EcdhProvider = E;
-        type PublicKey = E::PublicKey;
-        type EphemeralSecret = E::EphemeralSecret;
-        type SharedSecret = E::SharedSecret;
-    }
-
-    /// Test for `PublicKey<X25519>::to_bytes`
-    pub fn x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
-        let public_key_bytes =
-            hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
-        let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
-        assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes());
-    }
-
-    /// Random test for `PublicKey<X25519>::to_bytes`
-    pub fn x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
-        for _ in 1..100 {
-            let public_key_bytes =
-                E::EphemeralSecret::generate_random(&mut <E::EphemeralSecret as EphemeralSecret<
-                    X25519,
-                >>::Rng::new())
-                .public_key_bytes();
-            let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
-            assert_eq!(
-                E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
-                public_key,
-                "from_bytes should return the same key for `{public_key_bytes:?}`",
-            );
-        }
-    }
-
-    /// Test for X25519 Diffie-Hellman key exchange.
-    pub fn x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
-        // From wycheproof ecdh_secx25519r1_ecpoint_test.json, tcId 1
-        // http://google3/third_party/wycheproof/testvectors/ecdh_secx25519r1_ecpoint_test.json;l=22;rcl=375894991
-        // sec1 public key manually extracted from the ASN encoded test data
-        let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
-        let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475");
-        let expected_shared_secret =
-            hex!("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320");
-        let result = x25519_ecdh_test_impl::<E>(&public_key, &private).unwrap();
-        assert_eq!(expected_shared_secret, result.into());
-    }
-
-    fn x25519_ecdh_test_impl<E: EcdhProviderForX25519Test>(
-        public_key: &[u8],
-        private: &[u8; 32],
-    ) -> Result<E::SharedSecret, TestError> {
-        let public_key = E::PublicKey::from_bytes(public_key).map_err(TestError::new)?;
-        let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
-            .map_err(TestError::new)?;
-        ephemeral_secret
-            .diffie_hellman(&public_key)
-            .map_err(TestError::new)
-    }
-
-    /// Wycheproof test for X25519 Diffie-Hellman.
-    pub fn wycheproof_x25519_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
-        // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/x25519_test.json
-        let test_set = wycheproof::xdh::TestSet::load(wycheproof::xdh::TestName::X25519).unwrap();
-        for test_group in test_set.test_groups {
-            for test in test_group.tests {
-                let result = x25519_ecdh_test_impl::<E>(
-                    &test.public_key,
-                    &test
-                        .private_key
-                        .try_into()
-                        .expect("Private keys should be 32 bytes long"),
-                );
-                match test.result {
-                    wycheproof::TestResult::Valid => {
-                        let shared_secret =
-                            result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
-                        assert_eq!(&test.shared_secret, &shared_secret.into());
-                    }
-                    wycheproof::TestResult::Invalid => {
-                        result
-                            .err()
-                            .unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
-                    }
-                    wycheproof::TestResult::Acceptable => {
-                        if let Ok(shared_secret) = result {
-                            assert_eq!(test.shared_secret, shared_secret.into());
-                        }
-                        // Test passes if `result` is an error because this test is "acceptable"
-                    }
-                }
-            }
-        }
-    }
-
-    /// Generates the test cases to validate the x25519 implementation.
-    /// For example, to test `MyCryptoProvider`:
-    ///
-    /// ```
-    /// use crypto_provider::x25519::testing::*;
-    ///
-    /// mod tests {
-    ///     #[apply(x25519_test_cases)]
-    ///     fn x25519_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>) {
-    ///         testcase(PhantomData::<MyCryptoProvider>);
-    ///     }
-    /// }
-    /// ```
-    #[template]
-    #[export]
-    #[rstest]
-    #[case::x25519_to_bytes(x25519_to_bytes_test)]
-    #[case::x25519_to_bytes_random(x25519_to_bytes_random_test)]
-    #[case::x25519_ecdh(x25519_ecdh_test)]
-    #[case::wycheproof_x25519(wycheproof_x25519_test)]
-    fn x25519_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
-}
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.lock b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
index b1c65bd..7d097a6 100644
--- a/nearby/crypto/crypto_provider_boringssl/Cargo.lock
+++ b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
@@ -27,15 +27,6 @@
 [[package]]
 name = "crypto_provider"
 version = "0.1.0"
-dependencies = [
- "hex",
- "hex-literal",
- "rand",
- "rstest",
- "rstest_reuse",
- "test_helper",
- "wycheproof",
-]
 
 [[package]]
 name = "crypto_provider_boringssl"
@@ -44,6 +35,7 @@
  "bssl-crypto",
  "crypto_provider",
  "crypto_provider_stubs",
+ "crypto_provider_test",
 ]
 
 [[package]]
@@ -54,10 +46,25 @@
 ]
 
 [[package]]
+name = "crypto_provider_test"
+version = "0.1.0"
+dependencies = [
+ "crypto_provider",
+ "hex",
+ "hex-literal",
+ "rand",
+ "rand_ext",
+ "rstest",
+ "rstest_reuse",
+ "test_helper",
+ "wycheproof",
+]
+
+[[package]]
 name = "futures"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
+checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -70,9 +77,9 @@
 
 [[package]]
 name = "futures-channel"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -80,15 +87,15 @@
 
 [[package]]
 name = "futures-core"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83"
+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -97,32 +104,32 @@
 
 [[package]]
 name = "futures-io"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
+checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.15",
 ]
 
 [[package]]
 name = "futures-sink"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
 
 [[package]]
 name = "futures-task"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
 
 [[package]]
 name = "futures-timer"
@@ -132,9 +139,9 @@
 
 [[package]]
 name = "futures-util"
-version = "0.3.27"
+version = "0.3.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -150,9 +157,9 @@
 
 [[package]]
 name = "getrandom"
-version = "0.2.8"
+version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
 dependencies = [
  "cfg-if",
  "libc",
@@ -179,9 +186,18 @@
 
 [[package]]
 name = "libc"
-version = "0.2.140"
+version = "0.2.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
 
 [[package]]
 name = "memchr"
@@ -209,18 +225,18 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.53"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
 dependencies = [
  "proc-macro2",
 ]
@@ -256,6 +272,25 @@
 ]
 
 [[package]]
+name = "rand_ext"
+version = "0.1.0"
+dependencies = [
+ "crypto_provider",
+ "log",
+ "rand",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
 name = "rstest"
 version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -316,29 +351,29 @@
 
 [[package]]
 name = "serde"
-version = "1.0.158"
+version = "1.0.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
+checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.158"
+version = "1.0.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
+checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.6",
+ "syn 2.0.15",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.94"
+version = "1.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
 dependencies = [
  "itoa",
  "ryu",
@@ -367,9 +402,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.6"
+version = "2.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ece519cfaf36269ea69d16c363fa1d59ceba8296bbfbfc003c3176d01f2816ee"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.toml b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
index 6a62172..24ce66c 100644
--- a/nearby/crypto/crypto_provider_boringssl/Cargo.toml
+++ b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
@@ -12,4 +12,4 @@
 bssl-crypto = {path = "../bssl-crypto"}
 
 [dev-dependencies]
-crypto_provider = {path = "../crypto_provider", features = ["std", "alloc", "testing"]}
+crypto_provider_test = {path = "../crypto_provider_test"}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes.rs b/nearby/crypto/crypto_provider_boringssl/src/aes.rs
index a4f275e..fc97db2 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes.rs
@@ -113,7 +113,7 @@
 mod tests {
     use super::*;
     use core::marker::PhantomData;
-    use crypto_provider::aes::testing::*;
+    use crypto_provider_test::aes::*;
 
     #[apply(aes_128_encrypt_test_cases)]
     fn aes_128_encrypt_test(testcase: CryptoProviderTestCase<Aes128EncryptCipher>) {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/hkdf.rs b/nearby/crypto/crypto_provider_boringssl/src/hkdf.rs
index 4ab0ad4..3672bb1 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/hkdf.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/hkdf.rs
@@ -54,7 +54,7 @@
 mod tests {
     use crate::Boringssl;
     use core::marker::PhantomData;
-    use crypto_provider::hkdf::testing::*;
+    use crypto_provider_test::hkdf::*;
 
     #[apply(hkdf_test_cases)]
     fn hkdf_tests(testcase: CryptoProviderTestCase<Boringssl>) {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/hmac.rs b/nearby/crypto/crypto_provider_boringssl/src/hmac.rs
index 3459c3b..7a5e867 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/hmac.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/hmac.rs
@@ -84,7 +84,7 @@
 mod tests {
     use crate::Boringssl;
     use core::marker::PhantomData;
-    use crypto_provider::hmac::testing::*;
+    use crypto_provider_test::hmac::*;
 
     #[apply(hmac_test_cases)]
     fn hmac_tests(testcase: CryptoProviderTestCase<Boringssl>) {
diff --git a/nearby/crypto/crypto_provider_default/Cargo.toml b/nearby/crypto/crypto_provider_default/Cargo.toml
index 9eff7ea..f78ad8e 100644
--- a/nearby/crypto/crypto_provider_default/Cargo.toml
+++ b/nearby/crypto/crypto_provider_default/Cargo.toml
@@ -12,6 +12,7 @@
 cfg-if.workspace = true
 
 [features]
+std = ["crypto_provider_rustcrypto/std"]
 default = ["rustcrypto"]
 rustcrypto = ["crypto_provider_rustcrypto"]
 boringssl = ["crypto_provider_boringssl"]
diff --git a/nearby/crypto/crypto_provider_openssl/Cargo.toml b/nearby/crypto/crypto_provider_openssl/Cargo.toml
index 78c6cf3..8fa6faa 100644
--- a/nearby/crypto/crypto_provider_openssl/Cargo.toml
+++ b/nearby/crypto/crypto_provider_openssl/Cargo.toml
@@ -16,6 +16,6 @@
 boringssl = ["openssl/unstable_boringssl", "crypto_provider/gcm_siv"]
 
 [dev-dependencies]
-crypto_provider = { path = "../crypto_provider", default-features = false, features = ["testing"] }
-rstest = "0.16.0"
+crypto_provider_test.workspace = true
+rstest.workspace = true
 hex-literal.workspace = true
diff --git a/nearby/crypto/crypto_provider_openssl/src/aes.rs b/nearby/crypto/crypto_provider_openssl/src/aes.rs
index ded187f..55a3dd2 100644
--- a/nearby/crypto/crypto_provider_openssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/aes.rs
@@ -290,9 +290,9 @@
 mod tests {
     use core::marker::PhantomData;
 
-    use crypto_provider::aes::cbc::testing::*;
-    use crypto_provider::aes::ctr::testing::*;
-    use crypto_provider::aes::testing::*;
+    use crypto_provider_test::aes::cbc::*;
+    use crypto_provider_test::aes::ctr::*;
+    use crypto_provider_test::aes::*;
 
     use super::*;
 
diff --git a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
index cb37b6f..1384a06 100644
--- a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
@@ -126,7 +126,7 @@
 #[cfg(test)]
 mod tests {
     use crate::ed25519::Ed25519;
-    use crypto_provider::ed25519::testing::{run_rfc_test_vectors, run_wycheproof_test_vectors};
+    use crypto_provider_test::ed25519::{run_rfc_test_vectors, run_wycheproof_test_vectors};
 
     #[test]
     fn wycheproof_test_ed25519_openssl() {
diff --git a/nearby/crypto/crypto_provider_openssl/src/hkdf_boringssl.rs b/nearby/crypto/crypto_provider_openssl/src/hkdf_boringssl.rs
index f6c106d..eb23b3b 100644
--- a/nearby/crypto/crypto_provider_openssl/src/hkdf_boringssl.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/hkdf_boringssl.rs
@@ -67,7 +67,7 @@
 mod tests {
     use crate::Openssl;
     use core::marker::PhantomData;
-    use crypto_provider::hkdf::testing::*;
+    use crypto_provider_test::hkdf::*;
 
     #[apply(hkdf_test_cases)]
     fn hkdf_tests(testcase: CryptoProviderTestCase<Openssl>) {
diff --git a/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs b/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs
index 605cff1..c578ed7 100644
--- a/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs
@@ -65,7 +65,7 @@
 mod tests {
     use crate::Openssl;
     use core::marker::PhantomData;
-    use crypto_provider::hkdf::testing::*;
+    use crypto_provider_test::hkdf::*;
 
     #[apply(hkdf_test_cases)]
     fn hkdf_tests(testcase: CryptoProviderTestCase<Openssl>) {
diff --git a/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs b/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs
index af728b3..2202d7e 100644
--- a/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs
@@ -102,7 +102,7 @@
 mod tests {
     use crate::Openssl;
     use core::marker::PhantomData;
-    use crypto_provider::hmac::testing::*;
+    use crypto_provider_test::hmac::*;
 
     #[apply(hmac_test_cases)]
     fn hmac_tests(testcase: CryptoProviderTestCase<Openssl>) {
diff --git a/nearby/crypto/crypto_provider_openssl/src/hmac_openssl.rs b/nearby/crypto/crypto_provider_openssl/src/hmac_openssl.rs
index d99ee3e..3cff10f 100644
--- a/nearby/crypto/crypto_provider_openssl/src/hmac_openssl.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/hmac_openssl.rs
@@ -180,7 +180,7 @@
 mod tests {
     use crate::Openssl;
     use core::marker::PhantomData;
-    use crypto_provider::hmac::testing::*;
+    use crypto_provider_test::hmac::*;
 
     #[apply(hmac_test_cases)]
     fn hmac_tests(testcase: CryptoProviderTestCase<Openssl>) {
diff --git a/nearby/crypto/crypto_provider_openssl/src/lib.rs b/nearby/crypto/crypto_provider_openssl/src/lib.rs
index db926f0..41c4642 100644
--- a/nearby/crypto/crypto_provider_openssl/src/lib.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/lib.rs
@@ -112,8 +112,8 @@
 mod tests {
     use core::marker::PhantomData;
 
-    use crypto_provider::sha2::testing::*;
-    use crypto_provider::testing::*;
+    use crypto_provider_test::sha2::*;
+    use crypto_provider_test::*;
 
     use crate::Openssl;
 
diff --git a/nearby/crypto/crypto_provider_openssl/src/p256.rs b/nearby/crypto/crypto_provider_openssl/src/p256.rs
index a0c5072..5efd123 100644
--- a/nearby/crypto/crypto_provider_openssl/src/p256.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/p256.rs
@@ -132,7 +132,7 @@
 }
 
 #[cfg(test)]
-impl crypto_provider::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret {
+impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret {
     fn from_private_components(
         private_bytes: &[u8; 32],
         public_key: &P256PublicKey,
@@ -160,7 +160,7 @@
 mod tests {
     use super::P256Ecdh;
     use core::marker::PhantomData;
-    use crypto_provider::p256::testing::*;
+    use crypto_provider_test::p256::*;
 
     #[apply(p256_test_cases)]
     fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>) {
diff --git a/nearby/crypto/crypto_provider_openssl/src/x25519.rs b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
index 0745c0b..ff6f3b0 100644
--- a/nearby/crypto/crypto_provider_openssl/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
@@ -72,7 +72,7 @@
 }
 
 #[cfg(test)]
-impl crypto_provider::elliptic_curve::EphemeralSecretForTesting<X25519> for X25519PrivateKey {
+impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<X25519> for X25519PrivateKey {
     fn from_private_components(
         private_bytes: &[u8; 32],
         _public_key: &<Self::Impl as EcdhProvider<X25519>>::PublicKey,
@@ -93,7 +93,7 @@
 #[cfg(test)]
 mod tests {
     use core::marker::PhantomData;
-    use crypto_provider::x25519::testing::*;
+    use crypto_provider_test::x25519::*;
 
     use super::X25519Ecdh;
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
index 2ab5354..cd4cf4a 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
+++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -26,7 +26,7 @@
 
 [dev-dependencies]
 hex.workspace = true
-crypto_provider = { workspace = true, features = ["testing"] }
+crypto_provider_test.workspace = true
 crypto_provider_rustcrypto = { path = ".", features = ["std"] }
 
 [features]
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
index b43876c..06d7224 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
@@ -44,7 +44,7 @@
 mod tests {
     use super::AesCbcPkcs7Padded;
     use core::marker::PhantomData;
-    use crypto_provider::aes::cbc::testing::*;
+    use crypto_provider_test::aes::cbc::*;
 
     #[apply(aes_256_cbc_test_cases)]
     fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs
index 98eca08..c31e09a 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes/gcm_siv.rs
@@ -67,8 +67,8 @@
 mod tests {
     use core::marker::PhantomData;
 
-    use crypto_provider::aes::gcm_siv::testing::*;
-    use crypto_provider::aes::testing::*;
+    use crypto_provider_test::aes::gcm_siv::*;
+    use crypto_provider_test::aes::*;
 
     use super::*;
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
index a351b81..bd37458 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
@@ -154,8 +154,8 @@
 mod tests {
     use core::marker::PhantomData;
 
-    use crypto_provider::aes::ctr::testing::*;
-    use crypto_provider::aes::testing::*;
+    use crypto_provider_test::aes::ctr::*;
+    use crypto_provider_test::aes::*;
 
     use super::*;
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
index df5c5de..b971268 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
@@ -110,7 +110,7 @@
 
 #[cfg(test)]
 mod tests {
-    use crypto_provider::ed25519::testing::{run_rfc_test_vectors, run_wycheproof_test_vectors};
+    use crypto_provider_test::ed25519::{run_rfc_test_vectors, run_wycheproof_test_vectors};
 
     use crate::ed25519::Ed25519;
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
index 8ff5d7b..79379e2 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
@@ -76,7 +76,7 @@
 mod tests {
     use crate::RustCrypto;
     use core::marker::PhantomData;
-    use crypto_provider::hkdf::testing::*;
+    use crypto_provider_test::hkdf::*;
 
     #[apply(hkdf_test_cases)]
     fn hkdf_tests(testcase: CryptoProviderTestCase<RustCrypto>) {
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
index 95254a5..6eb678c 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
@@ -117,7 +117,7 @@
 mod tests {
     use crate::RustCrypto;
     use core::marker::PhantomData;
-    use crypto_provider::hmac::testing::*;
+    use crypto_provider_test::hmac::*;
 
     #[apply(hmac_test_cases)]
     fn hmac_tests(testcase: CryptoProviderTestCase<RustCrypto>) {
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
index e73ea11..7d4a3b4 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
@@ -126,7 +126,7 @@
 mod tests {
     use core::marker::PhantomData;
 
-    use crypto_provider::sha2::testing::*;
+    use crypto_provider_test::sha2::*;
 
     use crate::RustCrypto;
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
index a50bec8..3ad08f2 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
@@ -120,7 +120,8 @@
 
 #[cfg(test)]
 impl<R: CryptoRng + SeedableRng + RngCore + Send>
-    crypto_provider::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret<R>
+    crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<P256>
+    for P256EphemeralSecret<R>
 {
     fn from_private_components(
         private_bytes: &[u8; 32],
@@ -139,7 +140,7 @@
 mod tests {
     use super::P256Ecdh;
     use core::marker::PhantomData;
-    use crypto_provider::p256::testing::*;
+    use crypto_provider_test::p256::*;
     use rand::rngs::StdRng;
 
     #[apply(p256_test_cases)]
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
index 794d780..60d9bdb 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
@@ -68,7 +68,7 @@
 
 #[cfg(test)]
 impl<R: CryptoRng + RngCore + SeedableRng + Send>
-    crypto_provider::elliptic_curve::EphemeralSecretForTesting<X25519>
+    crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<X25519>
     for X25519EphemeralSecret<R>
 {
     fn from_private_components(
@@ -114,7 +114,7 @@
 mod tests {
     use super::X25519Ecdh;
     use core::marker::PhantomData;
-    use crypto_provider::x25519::testing::*;
+    use crypto_provider_test::x25519::*;
     use rand::rngs::StdRng;
 
     #[apply(x25519_test_cases)]
diff --git a/nearby/crypto/crypto_provider_test/Cargo.toml b/nearby/crypto/crypto_provider_test/Cargo.toml
new file mode 100644
index 0000000..1f58c84
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "crypto_provider_test"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[dependencies]
+crypto_provider.workspace = true
+rand_ext.workspace = true
+test_helper.workspace = true
+
+hex-literal.workspace = true
+rand.workspace = true
+rstest.workspace = true
+rstest_reuse.workspace = true
+wycheproof.workspace = true
+hex.workspace = true
\ No newline at end of file
diff --git a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
new file mode 100644
index 0000000..b22c828
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
@@ -0,0 +1,63 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+
+use crate::aes::Aes256Key;
+pub use crate::prelude::*;
+use core::marker::PhantomData;
+use crypto_provider::aes::cbc::{AesCbcIv, AesCbcPkcs7Padded};
+use hex_literal::hex;
+use rstest_reuse::template;
+
+/// Tests for AES-256-CBC encryption
+pub fn aes_256_cbc_test_encrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+    // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632
+    let key: Aes256Key =
+        hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
+    let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
+    let msg = hex!("3f56935def3f");
+    let expected_ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
+    assert_eq!(A::encrypt(&key, &iv, &msg), expected_ciphertext);
+}
+
+/// Tests for AES-256-CBC decryption
+pub fn aes_256_cbc_test_decrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+    // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632
+    let key: Aes256Key =
+        hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
+    let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
+    let ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
+    let expected_msg = hex!("3f56935def3f");
+    assert_eq!(A::decrypt(&key, &iv, &ciphertext).unwrap(), expected_msg);
+}
+
+/// Generates the test cases to validate the AES-256-CBC implementation.
+/// For example, to test `MyAesCbc256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::cbc::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_cbc_test_cases)]
+///     fn aes_256_cbc_tests(
+///             testcase: CryptoProviderTestCases<PhantomData<MyAesCbc256Impl>>) {
+///         testcase(PhantomData);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_256_cbc_test_encrypt)]
+#[case::decrypt(aes_256_cbc_test_decrypt)]
+fn aes_256_cbc_test_cases<A: AesCbcPkcs7Padded>(#[case] testcase: CryptoProviderTestCases<F>) {}
diff --git a/nearby/crypto/crypto_provider_test/src/aes/ctr.rs b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
new file mode 100644
index 0000000..401f65b
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
@@ -0,0 +1,183 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+use crate::aes::{Aes128Key, Aes256Key};
+pub use crate::prelude;
+use core::marker;
+use crypto_provider::aes::ctr::AesCtr;
+use hex_literal::hex;
+use rstest_reuse::template;
+
+/// Test AES-128-CTR encryption
+pub fn aes_128_ctr_test_encrypt<A: AesCtr<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.1
+    let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
+    let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+    let mut block: [u8; 16];
+    let mut cipher = A::new(&key, iv);
+
+    block = hex!("6bc1bee22e409f96e93d7e117393172a");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_1 = hex!("874d6191b620e3261bef6864990db6ce");
+    assert_eq!(expected_ciphertext_1, block);
+
+    block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_2 = hex!("9806f66b7970fdff8617187bb9fffdff");
+    assert_eq!(expected_ciphertext_2, block);
+
+    block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_3 = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
+    assert_eq!(expected_ciphertext_3, block);
+
+    block = hex!("f69f2445df4f9b17ad2b417be66c3710");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_3 = hex!("1e031dda2fbe03d1792170a0f3009cee");
+    assert_eq!(expected_ciphertext_3, block);
+}
+
+/// Test AES-128-CTR decryption
+pub fn aes_128_ctr_test_decrypt<A: AesCtr<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.2
+    let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
+    let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+    let mut block: [u8; 16];
+    let mut cipher = A::new(&key, iv);
+
+    block = hex!("874d6191b620e3261bef6864990db6ce");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
+    assert_eq!(expected_plaintext_1, block);
+
+    block = hex!("9806f66b7970fdff8617187bb9fffdff");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
+    assert_eq!(expected_plaintext_2, block);
+
+    block = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
+    assert_eq!(expected_plaintext_3, block);
+
+    block = hex!("1e031dda2fbe03d1792170a0f3009cee");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
+    assert_eq!(expected_plaintext_3, block);
+}
+
+/// Test AES-256-CTR encryption
+pub fn aes_256_ctr_test_encrypt<A: AesCtr<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.5
+    let key: Aes256Key =
+        hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
+    let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+    let mut block: [u8; 16];
+    let mut cipher = A::new(&key, iv);
+
+    block = hex!("6bc1bee22e409f96e93d7e117393172a");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_1 = hex!("601ec313775789a5b7a7f504bbf3d228");
+    assert_eq!(expected_ciphertext_1, block);
+
+    block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_2 = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
+    assert_eq!(expected_ciphertext_2, block);
+
+    block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_3 = hex!("2b0930daa23de94ce87017ba2d84988d");
+    assert_eq!(expected_ciphertext_3, block);
+
+    block = hex!("f69f2445df4f9b17ad2b417be66c3710");
+    cipher.encrypt(&mut block);
+    let expected_ciphertext_3 = hex!("dfc9c58db67aada613c2dd08457941a6");
+    assert_eq!(expected_ciphertext_3, block);
+}
+
+/// Test AES-256-CTR decryption
+pub fn aes_256_ctr_test_decrypt<A: AesCtr<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.5.6
+    let key: Aes256Key =
+        hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
+    let iv = hex!("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
+    let mut block: [u8; 16];
+    let mut cipher = A::new(&key, iv);
+
+    block = hex!("601ec313775789a5b7a7f504bbf3d228");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
+    assert_eq!(expected_plaintext_1, block);
+
+    block = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
+    assert_eq!(expected_plaintext_2, block);
+
+    block = hex!("2b0930daa23de94ce87017ba2d84988d");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
+    assert_eq!(expected_plaintext_3, block);
+
+    block = hex!("dfc9c58db67aada613c2dd08457941a6");
+    cipher.encrypt(&mut block);
+    let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
+    assert_eq!(expected_plaintext_3, block);
+}
+
+/// Generates the test cases to validate the AES-128-CTR implementation.
+/// For example, to test `MyAesCtr128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::ctr::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_ctr_test_cases)]
+///     fn aes_128_ctr_tests(testcase: CryptoProviderTestCase<MyAesCtr128Impl>) {
+///         testcase(MyAesCtr128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_128_ctr_test_encrypt)]
+#[case::decrypt(aes_128_ctr_test_decrypt)]
+fn aes_128_ctr_test_cases<F: AesCtrFactory<Key = Aes128Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256-CTR implementation.
+/// For example, to test `MyAesCtr256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::ctr::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_ctr_test_cases_impl)]
+///     fn aes_256_ctr_tests(testcase: CryptoProviderTestCase<MyAesCtr256Impl>) {
+///         testcase(MyAesCtr256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_256_ctr_test_encrypt)]
+#[case::decrypt(aes_256_ctr_test_decrypt)]
+fn aes_256_ctr_test_cases<F: AesCtrFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/aes/gcm_siv.rs b/nearby/crypto/crypto_provider_test/src/aes/gcm_siv.rs
new file mode 100644
index 0000000..b27e61c
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/aes/gcm_siv.rs
@@ -0,0 +1,122 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+use alloc::vec::Vec;
+use core::marker;
+
+use hex_literal::hex;
+use rstest_reuse::template;
+
+use crate::aes::{Aes128Key, Aes256Key};
+pub use crate::prelude;
+
+use crypto_provider::aes::gcm_siv::AesGcmSiv;
+
+/// Test AES-GCM-SIV-128 encryption/decryption
+pub fn aes_128_gcm_siv_test<A: AesGcmSiv<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+    // TC1
+    let test_key = hex!("01000000000000000000000000000000");
+    let nonce = hex!("030000000000000000000000");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("");
+    let mut buf = Vec::from(msg.as_slice());
+    let tag = hex!("dc20e2d83f25705bb49e439eca56de25");
+    assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..], &tag);
+    // TC2
+    let msg = hex!("0100000000000000");
+    let ct = hex!("b5d839330ac7b786");
+    let tag = hex!("578782fff6013b815b287c22493a364c");
+    let mut buf = Vec::from(msg.as_slice());
+    assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..8], &ct);
+    assert_eq!(&buf[8..], &tag);
+    assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..], &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+    // TC77
+    let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
+    let nonce = hex!("030000000000000000000000");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("0100000000000000");
+    let mut buf = Vec::new();
+    buf.extend_from_slice(&msg);
+    let ct = hex!("c2ef328e5c71c83b");
+    let tag = hex!("843122130f7364b761e0b97427e3df28");
+    assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..8], &ct);
+    assert_eq!(&buf[8..], &tag);
+    assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..], &msg);
+    // TC78
+    let msg = hex!("010000000000000000000000");
+    let ct = hex!("9aab2aeb3faa0a34aea8e2b1");
+    let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e");
+    let mut buf = Vec::from(msg.as_slice());
+    assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..12], &ct);
+    assert_eq!(&buf[12..], &tag);
+    assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
+    assert_eq!(&buf[..], &msg);
+}
+
+/// Generates the test cases to validate the AES-128-GCM-SIV implementation.
+/// For example, to test `MyAesGcmSiv128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::gcm_siv::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_gcm_siv_test_cases)]
+///     fn aes_128_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSivImpl>) {
+///         testcase(MyAesGcmSiv128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_128_gcm_siv_test)]
+#[case::decrypt(aes_128_gcm_siv_test)]
+fn aes_128_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes128Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256-GCM-SIV implementation.
+/// For example, to test `MyAesGcmSiv256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::gcm_siv::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_gcm_siv_test_cases)]
+///     fn aes_256_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSiv256Impl>) {
+///         testcase(MyAesGcmSiv256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_256_gcm_siv_test)]
+#[case::decrypt(aes_256_gcm_siv_test)]
+fn aes_256_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/aes/mod.rs b/nearby/crypto/crypto_provider_test/src/aes/mod.rs
new file mode 100644
index 0000000..e1489da
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/aes/mod.rs
@@ -0,0 +1,210 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+
+pub mod cbc;
+pub mod ctr;
+pub mod gcm_siv;
+
+pub use crate::prelude::*;
+
+use core::marker;
+use crypto_provider::aes::*;
+use hex_literal::hex;
+use rstest_reuse::template;
+
+/// Test encryption with AES-128
+pub fn aes_128_test_encrypt<A: AesEncryptCipher<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.1
+    let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
+    let mut block = [0_u8; 16];
+    let enc_cipher = A::new(&key);
+
+    block.copy_from_slice(&hex!("6bc1bee22e409f96e93d7e117393172a"));
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("3ad77bb40d7a3660a89ecaf32466ef97"), block);
+
+    block.copy_from_slice(&hex!("ae2d8a571e03ac9c9eb76fac45af8e51"));
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("f5d3d58503b9699de785895a96fdbaaf"), block);
+
+    block.copy_from_slice(&hex!("30c81c46a35ce411e5fbc1191a0a52ef"));
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("43b1cd7f598ece23881b00e3ed030688"), block);
+
+    block.copy_from_slice(&hex!("f69f2445df4f9b17ad2b417be66c3710"));
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("7b0c785e27e8ad3f8223207104725dd4"), block);
+}
+
+/// Test decryption with AES-128
+pub fn aes_128_test_decrypt<A: AesDecryptCipher<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.2
+    let key: Aes128Key = hex!("2b7e151628aed2a6abf7158809cf4f3c").into();
+    let mut block = [0_u8; 16];
+    let dec_cipher = A::new(&key);
+
+    block.copy_from_slice(&hex!("3ad77bb40d7a3660a89ecaf32466ef97"));
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("6bc1bee22e409f96e93d7e117393172a"), block);
+
+    block.copy_from_slice(&hex!("f5d3d58503b9699de785895a96fdbaaf"));
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("ae2d8a571e03ac9c9eb76fac45af8e51"), block);
+
+    block.copy_from_slice(&hex!("43b1cd7f598ece23881b00e3ed030688"));
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("30c81c46a35ce411e5fbc1191a0a52ef"), block);
+
+    block.copy_from_slice(&hex!("7b0c785e27e8ad3f8223207104725dd4"));
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("f69f2445df4f9b17ad2b417be66c3710"), block);
+}
+
+/// Test encryption with AES-256
+pub fn aes_256_test_encrypt<A: AesEncryptCipher<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.5
+    let key: Aes256Key =
+        hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
+    let mut block: [u8; 16];
+    let enc_cipher = A::new(&key);
+
+    block = hex!("6bc1bee22e409f96e93d7e117393172a");
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("f3eed1bdb5d2a03c064b5a7e3db181f8"), block);
+
+    block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("591ccb10d410ed26dc5ba74a31362870"), block);
+
+    block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("b6ed21b99ca6f4f9f153e7b1beafed1d"), block);
+
+    block = hex!("f69f2445df4f9b17ad2b417be66c3710");
+    enc_cipher.encrypt(&mut block);
+    assert_eq!(hex!("23304b7a39f9f3ff067d8d8f9e24ecc7"), block);
+}
+
+/// Test decryption with AES-256
+pub fn aes_256_test_decrypt<A: AesDecryptCipher<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+    // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf F.1.6
+    let key: Aes256Key =
+        hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").into();
+    let mut block: [u8; 16];
+    let dec_cipher = A::new(&key);
+
+    block = hex!("f3eed1bdb5d2a03c064b5a7e3db181f8");
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("6bc1bee22e409f96e93d7e117393172a"), block);
+
+    block = hex!("591ccb10d410ed26dc5ba74a31362870");
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("ae2d8a571e03ac9c9eb76fac45af8e51"), block);
+
+    block = hex!("b6ed21b99ca6f4f9f153e7b1beafed1d");
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("30c81c46a35ce411e5fbc1191a0a52ef"), block);
+
+    block = hex!("23304b7a39f9f3ff067d8d8f9e24ecc7");
+    dec_cipher.decrypt(&mut block);
+    assert_eq!(hex!("f69f2445df4f9b17ad2b417be66c3710"), block);
+}
+
+/// Generates the test cases to validate the AES-128 implementation.
+/// For example, to test `MyAes128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_encrypt_test_cases)]
+///     fn aes_128_tests(f: CryptoProviderTestCase<MyAes128Impl>) {
+///         f(MyAes128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_128_test_encrypt)]
+fn aes_128_encrypt_test_cases<A: AesFactory<Key = Aes128Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-128 implementation.
+/// For example, to test `MyAes128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_decrypt_test_cases)]
+///     fn aes_128_tests(f: CryptoProviderTestCase<MyAes128Impl>) {
+///         f(MyAes128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::decrypt(aes_128_test_decrypt)]
+fn aes_128_decrypt_test_cases<F: AesFactory<Key = Aes128Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256 implementation.
+/// For example, to test `MyAes256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_encrypt_test_cases)]
+///     fn aes_256_tests(f: CryptoProviderTestCase<MyAes256Impl>) {
+///         f(MyAes256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_256_test_encrypt)]
+fn aes_256_encrypt_test_cases<F: AesFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256 implementation.
+/// For example, to test `MyAes256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_decrypt_test_cases)]
+///     fn aes_256_tests(f: CryptoProviderTestCase<MyAes256Impl>) {
+///         f(MyAes256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::decrypt(aes_256_test_decrypt)]
+fn aes_256_decrypt_test_cases<F: AesFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/ed25519.rs b/nearby/crypto/crypto_provider_test/src/ed25519.rs
new file mode 100644
index 0000000..c2da3b3
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/ed25519.rs
@@ -0,0 +1,151 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+
+extern crate alloc;
+extern crate std;
+
+use alloc::borrow::ToOwned;
+use alloc::string::String;
+use alloc::vec::Vec;
+use crypto_provider::ed25519::{Ed25519Provider, KeyPair, PublicKey, Signature};
+use wycheproof::TestResult;
+
+// These are test vectors from the creators of Ed25519: https://ed25519.cr.yp.to/ which are referenced
+// as the SOT for the test vectors in the RFC: https://www.rfc-editor.org/rfc/rfc8032#section-7.1
+// The vectors have been formatted into a easily parsable/readable format by libgcrypt which is
+// also used for test cases in the above RFC:
+// https://dev.gnupg.org/source/libgcrypt/browse/master/tests/t-ed25519.inp
+const PATH_TO_RFC_VECTORS_FILE: &str =
+    "crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt";
+
+/// Runs set of Ed25519 wycheproof test vectors against a provided ed25519 implementation
+/// Tests vectors from Project Wycheproof: <https://github.com/google/wycheproof>
+pub fn run_wycheproof_test_vectors<E>()
+where
+    E: Ed25519Provider,
+{
+    let test_set = wycheproof::eddsa::TestSet::load(wycheproof::eddsa::TestName::Ed25519)
+        .expect("should be able to load test set");
+
+    for test_group in test_set.test_groups {
+        let key_pair = test_group.key;
+        let public_key = key_pair.pk;
+        let secret_key = key_pair.sk;
+
+        for test in test_group.tests {
+            let tc_id = test.tc_id;
+            let comment = test.comment;
+            let sig = test.sig;
+            let msg = test.msg;
+
+            let valid = match test.result {
+                TestResult::Invalid => false,
+                TestResult::Valid | TestResult::Acceptable => true,
+            };
+            let result = run_test::<E>(
+                public_key.clone(),
+                secret_key.clone(),
+                sig.clone(),
+                msg.clone(),
+            );
+            if valid {
+                if let Err(desc) = result {
+                    panic!(
+                        "\n\
+                         Failed test {}: {}\n\
+                         msg:\t{:?}\n\
+                         sig:\t{:?}\n\
+                         comment:\t{:?}\n",
+                        tc_id, desc, msg, sig, comment,
+                    );
+                }
+            } else {
+                assert!(result.is_err())
+            }
+        }
+    }
+}
+
+/// Runs the RFC specified test vectors against an Ed25519 implementation
+pub fn run_rfc_test_vectors<E>()
+where
+    E: Ed25519Provider,
+{
+    let file_contents =
+        std::fs::read_to_string(test_helper::get_data_file(PATH_TO_RFC_VECTORS_FILE))
+            .expect("should be able to read file");
+
+    let mut split_cases: Vec<&str> = file_contents.as_str().split("\n\n").collect();
+    // remove the comments
+    split_cases.remove(0);
+    for case in split_cases {
+        let test_case: Vec<&str> = case.split('\n').collect();
+
+        let tc_id = extract_string(test_case[0]);
+        let sk = extract_hex(test_case[1]);
+        let pk = extract_hex(test_case[2]);
+        let msg = extract_hex(test_case[3]);
+        let sig = extract_hex(test_case[4]);
+
+        let result = run_test::<E>(pk.clone(), sk.clone(), sig.clone(), msg.clone());
+        if let Err(desc) = result {
+            panic!(
+                "\n\
+                         Failed test {}: {}\n\
+                         msg:\t{:?}\n\
+                         sig:\t{:?}\n\"",
+                tc_id, desc, msg, sig,
+            );
+        }
+    }
+}
+
+fn extract_hex(line: &str) -> Vec<u8> {
+    test_helper::string_to_hex(extract_string(line).as_str())
+}
+
+fn extract_string(line: &str) -> String {
+    line.split(':').collect::<Vec<&str>>()[1].trim().to_owned()
+}
+
+fn run_test<E>(
+    pub_key: Vec<u8>,
+    secret_key: Vec<u8>,
+    sig: Vec<u8>,
+    msg: Vec<u8>,
+) -> Result<(), &'static str>
+where
+    E: Ed25519Provider,
+{
+    let kp_bytes: [u8; 64] = [secret_key.as_slice(), pub_key.as_slice()]
+        .concat()
+        .try_into()
+        .map_err(|_| "invalid length keypair")?;
+    let kp = E::KeyPair::from_bytes(kp_bytes)
+        .map_err(|_| "Should be able to create Keypair from bytes")?;
+
+    let sig_result = kp.sign(msg.as_slice());
+    (sig.as_slice() == sig_result.to_bytes())
+        .then_some(())
+        .ok_or("sig not matching expected")?;
+    let signature = E::Signature::from_bytes(sig.as_slice())
+        .map_err(|_| "unable to parse sign from test case")?;
+
+    let pub_key = kp.public();
+    pub_key
+        .verify_strict(msg.as_slice(), &signature)
+        .map_err(|_| "verify failed")?;
+
+    Ok(())
+}
diff --git a/nearby/crypto/crypto_provider_test/src/elliptic_curve.rs b/nearby/crypto/crypto_provider_test/src/elliptic_curve.rs
new file mode 100644
index 0000000..4807d25
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/elliptic_curve.rs
@@ -0,0 +1,26 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+
+use crypto_provider::elliptic_curve::*;
+
+/// Trait for an ephemeral secret for functions used in testing.
+pub trait EphemeralSecretForTesting<C: Curve>: EphemeralSecret<C> {
+    /// Creates an ephemeral secret from the given private and public components.
+    fn from_private_components(
+        _private_bytes: &[u8; 32],
+        _public_key: &<Self::Impl as EcdhProvider<C>>::PublicKey,
+    ) -> Result<Self, Self::Error>
+    where
+        Self: Sized;
+}
diff --git a/nearby/crypto/crypto_provider_test/src/hkdf.rs b/nearby/crypto/crypto_provider_test/src/hkdf.rs
new file mode 100644
index 0000000..5780263
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/hkdf.rs
@@ -0,0 +1,297 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+extern crate alloc;
+pub use crate::prelude::*;
+use crate::CryptoProvider;
+use alloc::vec;
+use alloc::vec::Vec;
+use core::iter;
+use core::marker::PhantomData;
+use crypto_provider::hkdf::Hkdf;
+use hex_literal::hex;
+use rstest_reuse::template;
+
+/// Generates the test cases to validate the hkdf implementation.
+/// For example, to test `MyCryptoProvider`:
+///
+/// ```
+/// mod tests {
+///     use std::marker::PhantomData;
+///     use crypto_provider::testing::CryptoProviderTestCase;
+///     #[apply(hkdf_test_cases)]
+///     fn hkdf_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>){
+///         testcase(PhantomData::<MyCryptoProvider>);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::basic_test_hkdf(basic_test_hkdf)]
+#[case::test_rfc5869_sha256(test_rfc5869_sha256)]
+#[case::test_lengths(test_lengths)]
+#[case::test_max_length(test_max_length)]
+#[case::test_max_length_exceeded(test_max_length_exceeded)]
+#[case::test_unsupported_length(test_unsupported_length)]
+#[case::test_expand_multi_info(test_expand_multi_info)]
+#[case::run_hkdf_sha256_vectors(run_hkdf_sha256_vectors)]
+#[case::run_hkdf_sha512_vectors(run_hkdf_sha512_vectors)]
+fn hkdf_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
+
+const MAX_SHA256_LENGTH: usize = 255 * (256 / 8); // =8160
+
+///
+pub struct Test<'a> {
+    ikm: &'a [u8],
+    salt: &'a [u8],
+    info: &'a [u8],
+    okm: &'a [u8],
+}
+
+/// data taken from sample code in Readme of crates.io page
+pub fn basic_test_hkdf<C: CryptoProvider>(_: PhantomData<C>) {
+    let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
+    let salt = hex!("000102030405060708090a0b0c");
+    let info = hex!("f0f1f2f3f4f5f6f7f8f9");
+
+    let hk = C::HkdfSha256::new(Some(&salt[..]), &ikm);
+    let mut okm = [0u8; 42];
+    hk.expand(&info, &mut okm)
+        .expect("42 is a valid length for Sha256 to output");
+
+    let expected = hex!(
+        "
+        3cb25f25faacd57a90434f64d0362f2a
+        2d2d0a90cf1a5a4c5db02d56ecc4c5bf
+        34007208d5b887185865
+        "
+    );
+    assert_eq!(okm, expected);
+}
+
+// Test Vectors from https://tools.ietf.org/html/rfc5869.
+#[rustfmt::skip]
+    ///
+    pub fn test_rfc5869_sha256<C: CryptoProvider>(_: PhantomData<C>) {
+        let tests = [
+            Test {
+                // Test Case 1
+                ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+                salt: &hex!("000102030405060708090a0b0c"),
+                info: &hex!("f0f1f2f3f4f5f6f7f8f9"),
+                okm: &hex!("
+                    3cb25f25faacd57a90434f64d0362f2a
+                    2d2d0a90cf1a5a4c5db02d56ecc4c5bf
+                    34007208d5b887185865
+                "),
+            },
+            Test {
+                // Test Case 2
+                ikm: &hex!("
+                    000102030405060708090a0b0c0d0e0f
+                    101112131415161718191a1b1c1d1e1f
+                    202122232425262728292a2b2c2d2e2f
+                    303132333435363738393a3b3c3d3e3f
+                    404142434445464748494a4b4c4d4e4f
+                "),
+                salt: &hex!("
+                    606162636465666768696a6b6c6d6e6f
+                    707172737475767778797a7b7c7d7e7f
+                    808182838485868788898a8b8c8d8e8f
+                    909192939495969798999a9b9c9d9e9f
+                    a0a1a2a3a4a5a6a7a8a9aaabacadaeaf
+                "),
+                info: &hex!("
+                    b0b1b2b3b4b5b6b7b8b9babbbcbdbebf
+                    c0c1c2c3c4c5c6c7c8c9cacbcccdcecf
+                    d0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+                    e0e1e2e3e4e5e6e7e8e9eaebecedeeef
+                    f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
+                "),
+                okm: &hex!("
+                    b11e398dc80327a1c8e7f78c596a4934
+                    4f012eda2d4efad8a050cc4c19afa97c
+                    59045a99cac7827271cb41c65e590e09
+                    da3275600c2f09b8367793a9aca3db71
+                    cc30c58179ec3e87c14c01d5c1f3434f
+                    1d87
+                "),
+            },
+            Test {
+                // Test Case 3
+                ikm: &hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+                salt: &hex!(""),
+                info: &hex!(""),
+                okm: &hex!("
+                    8da4e775a563c18f715f802a063c5a31
+                    b8a11f5c5ee1879ec3454e5f3c738d2d
+                    9d201395faa4b61a96c8
+                "),
+            },
+        ];
+        for Test { ikm, salt, info, okm } in tests.iter() {
+            let salt = if salt.is_empty() {
+                None
+            } else {
+                Some(&salt[..])
+            };
+            let hkdf = C::HkdfSha256::new(salt, ikm);
+            let mut okm2 = vec![0u8; okm.len()];
+            assert!(hkdf.expand(&info[..], &mut okm2).is_ok());
+            assert_eq!(okm2[..], okm[..]);
+        }
+    }
+
+///
+pub fn test_lengths<C: CryptoProvider>(_: PhantomData<C>) {
+    let hkdf = C::HkdfSha256::new(None, &[]);
+    let mut longest = vec![0u8; MAX_SHA256_LENGTH];
+    assert!(hkdf.expand(&[], &mut longest).is_ok());
+    // Runtime is O(length), so exhaustively testing all legal lengths
+    // would take too long (at least without --release). Only test a
+    // subset: the first 500, the last 10, and every 100th in between.
+    // 0 is an invalid key length for openssl, so start at 1
+    let lengths = (1..MAX_SHA256_LENGTH + 1)
+        .filter(|&len| !(500..=MAX_SHA256_LENGTH - 10).contains(&len) || len % 100 == 0);
+
+    for length in lengths {
+        let mut okm = vec![0u8; length];
+
+        assert!(hkdf.expand(&[], &mut okm).is_ok());
+        assert_eq!(okm.len(), length);
+        assert_eq!(okm[..], longest[..length]);
+    }
+}
+
+///
+pub fn test_max_length<C: CryptoProvider>(_: PhantomData<C>) {
+    let hkdf = C::HkdfSha256::new(Some(&[]), &[]);
+    let mut okm = vec![0u8; MAX_SHA256_LENGTH];
+    assert!(hkdf.expand(&[], &mut okm).is_ok());
+}
+
+///
+pub fn test_max_length_exceeded<C: CryptoProvider>(_: PhantomData<C>) {
+    let hkdf = C::HkdfSha256::new(Some(&[]), &[]);
+    let mut okm = vec![0u8; MAX_SHA256_LENGTH + 1];
+    assert!(hkdf.expand(&[], &mut okm).is_err());
+}
+
+///
+pub fn test_unsupported_length<C: CryptoProvider>(_: PhantomData<C>) {
+    let hkdf = C::HkdfSha256::new(Some(&[]), &[]);
+    let mut okm = vec![0u8; 90000];
+    assert!(hkdf.expand(&[], &mut okm).is_err());
+}
+
+///
+pub fn test_expand_multi_info<C: CryptoProvider>(_: PhantomData<C>) {
+    let info_components = &[
+        &b"09090909090909090909090909090909090909090909"[..],
+        &b"8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a"[..],
+        &b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0"[..],
+        &b"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4"[..],
+        &b"1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d"[..],
+    ];
+
+    let hkdf = C::HkdfSha256::new(None, b"some ikm here");
+
+    // Compute HKDF-Expand on the concatenation of all the info components
+    let mut oneshot_res = [0u8; 16];
+    hkdf.expand(&info_components.concat(), &mut oneshot_res)
+        .unwrap();
+
+    // Now iteratively join the components of info_components until it's all 1 component. The value
+    // of HKDF-Expand should be the same throughout
+    let mut num_concatted = 0;
+    let mut info_head = Vec::new();
+
+    while num_concatted < info_components.len() {
+        info_head.extend(info_components[num_concatted]);
+
+        // Build the new input to be the info head followed by the remaining components
+        let input: Vec<&[u8]> = iter::once(info_head.as_slice())
+            .chain(info_components.iter().cloned().skip(num_concatted + 1))
+            .collect();
+
+        // Compute and compare to the one-shot answer
+        let mut multipart_res = [0u8; 16];
+        hkdf.expand_multi_info(&input, &mut multipart_res).unwrap();
+        assert_eq!(multipart_res, oneshot_res);
+        num_concatted += 1;
+    }
+}
+
+///
+pub fn run_hkdf_sha256_vectors<C: CryptoProvider>(_: PhantomData<C>) {
+    run_hkdf_test_vectors::<C::HkdfSha256>(HashAlg::Sha256)
+}
+
+///
+pub fn run_hkdf_sha512_vectors<C: CryptoProvider>(_: PhantomData<C>) {
+    run_hkdf_test_vectors::<C::HkdfSha512>(HashAlg::Sha512)
+}
+
+enum HashAlg {
+    Sha256,
+    Sha512,
+}
+
+///
+fn run_hkdf_test_vectors<K: Hkdf>(hash: HashAlg) {
+    let test_name = match hash {
+        HashAlg::Sha256 => wycheproof::hkdf::TestName::HkdfSha256,
+        HashAlg::Sha512 => wycheproof::hkdf::TestName::HkdfSha512,
+    };
+
+    let test_set =
+        wycheproof::hkdf::TestSet::load(test_name).expect("should be able to load test set");
+    for test_group in test_set.test_groups {
+        for test in test_group.tests {
+            let ikm = test.ikm;
+            let salt = test.salt;
+            let info = test.info;
+            let okm = test.okm;
+            let tc_id = test.tc_id;
+            if let Some(desc) = run_test::<K>(
+                ikm.as_slice(),
+                salt.as_slice(),
+                info.as_slice(),
+                okm.as_slice(),
+            ) {
+                panic!(
+                    "\n\
+                         Failed test {tc_id}: {desc}\n\
+                         ikm:\t{ikm:?}\n\
+                         salt:\t{salt:?}\n\
+                         info:\t{info:?}\n\
+                         okm:\t{okm:?}\n"
+                );
+            }
+        }
+    }
+}
+
+fn run_test<K: Hkdf>(ikm: &[u8], salt: &[u8], info: &[u8], okm: &[u8]) -> Option<&'static str> {
+    let prk = K::new(Some(salt), ikm);
+    let mut got_okm = vec![0; okm.len()];
+
+    if prk.expand(info, &mut got_okm).is_err() {
+        return Some("prk expand");
+    }
+    if got_okm != okm {
+        return Some("mismatch in okm");
+    }
+    None
+}
diff --git a/nearby/crypto/crypto_provider_test/src/hmac.rs b/nearby/crypto/crypto_provider_test/src/hmac.rs
new file mode 100644
index 0000000..e3d1c0e
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/hmac.rs
@@ -0,0 +1,130 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+pub use crate::prelude::*;
+use crate::rstest_reuse::template;
+use crate::CryptoProvider;
+use core::cmp::min;
+use core::marker::PhantomData;
+use crypto_provider::hmac::Hmac;
+use wycheproof::TestResult;
+
+/// Generates the test cases to validate the hmac implementation.
+/// For example, to test `MyCryptoProvider`:
+///
+/// ```
+/// mod tests {
+///     use std::marker::PhantomData;
+///     use crypto_provider::testing::CryptoProviderTestCase;
+///     #[apply(hmac_test_cases)]
+///     fn hmac_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>){
+///         testcase(PhantomData::<MyCryptoProvider>);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::hmac_sha256_test_vectors(hmac_sha256_test_vectors)]
+#[case::hmac_sha512_test_vectors(hmac_sha512_test_vectors)]
+fn hmac_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
+
+/// Run wycheproof hmac sha256 test vectors on provided CryptoProvider
+pub fn hmac_sha256_test_vectors<C: CryptoProvider>(_: PhantomData<C>) {
+    run_hmac_test_vectors::<32, C::HmacSha256>(HashAlg::Sha256)
+}
+
+/// Run wycheproof hmac sha512 test vectors on provided CryptoProvider
+pub fn hmac_sha512_test_vectors<C: CryptoProvider>(_: PhantomData<C>) {
+    run_hmac_test_vectors::<64, C::HmacSha512>(HashAlg::Sha512)
+}
+
+enum HashAlg {
+    Sha256,
+    Sha512,
+}
+
+// Tests vectors from Project Wycheproof:
+// https://github.com/google/wycheproof
+fn run_hmac_test_vectors<const N: usize, H: Hmac<N>>(hash: HashAlg) {
+    let test_name = match hash {
+        HashAlg::Sha256 => wycheproof::mac::TestName::HmacSha256,
+        HashAlg::Sha512 => wycheproof::mac::TestName::HmacSha512,
+    };
+    let test_set =
+        wycheproof::mac::TestSet::load(test_name).expect("should be able to load test set");
+
+    for test_group in test_set.test_groups {
+        for test in test_group.tests {
+            let key = test.key;
+            let msg = test.msg;
+            let tag = test.tag;
+            let tc_id = test.tc_id;
+            let valid = match test.result {
+                TestResult::Valid | TestResult::Acceptable => true,
+                TestResult::Invalid => false,
+            };
+
+            if let Some(desc) =
+                run_test::<N, H>(key.as_slice(), msg.as_slice(), tag.as_slice(), valid)
+            {
+                panic!(
+                    "\n\
+                         Failed test {tc_id}: {desc}\n\
+                         key:\t{key:?}\n\
+                         msg:\t{msg:?}\n\
+                         tag:\t{tag:?}\n",
+                );
+            }
+        }
+    }
+}
+
+fn run_test<const N: usize, H: Hmac<N>>(
+    key: &[u8],
+    input: &[u8],
+    tag: &[u8],
+    valid_data: bool,
+) -> Option<&'static str> {
+    let mut mac = H::new_from_slice(key).unwrap();
+    mac.update(input);
+    let result = mac.finalize();
+    let n = tag.len();
+    let result_bytes = &result[..n];
+
+    if valid_data {
+        if result_bytes != tag {
+            return Some("whole message");
+        }
+    } else {
+        return if result_bytes == tag {
+            Some("invalid should not match")
+        } else {
+            None
+        };
+    }
+
+    // test reading different chunk sizes
+    for chunk_size in 1..min(64, input.len()) {
+        let mut mac = H::new_from_slice(key).unwrap();
+        for chunk in input.chunks(chunk_size) {
+            mac.update(chunk);
+        }
+        let res = mac.verify_truncated_left(tag);
+        if res.is_err() {
+            return Some("chunked message");
+        }
+    }
+
+    None
+}
diff --git a/nearby/crypto/crypto_provider_test/src/lib.rs b/nearby/crypto/crypto_provider_test/src/lib.rs
new file mode 100644
index 0000000..f7d6253
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/lib.rs
@@ -0,0 +1,121 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+extern crate alloc;
+
+use alloc::{format, string::String};
+use core::marker::PhantomData;
+
+use crypto_provider::CryptoProvider;
+use hex_literal::hex;
+use rand::{Rng, RngCore};
+use rstest_reuse::template;
+
+pub use rstest_reuse;
+
+pub mod aes;
+pub mod ed25519;
+pub mod elliptic_curve;
+pub mod hkdf;
+pub mod hmac;
+pub mod p256;
+pub mod sha2;
+pub mod x25519;
+
+/// Common items that needs to be imported to use these test cases
+pub mod prelude {
+    pub use super::CryptoProviderTestCase;
+    pub use rstest::rstest;
+    pub use rstest_reuse;
+    pub use rstest_reuse::apply;
+}
+
+/// A test case for Crypto Provider. A test case is a function that panics if the test fails.
+pub type CryptoProviderTestCase<T> = fn(PhantomData<T>);
+
+#[derive(Debug)]
+pub(crate) struct TestError(String);
+
+impl TestError {
+    pub(crate) fn new<D: core::fmt::Debug>(value: D) -> Self {
+        Self(format!("{value:?}"))
+    }
+}
+
+/// Test for `constant_time_eq` when the two inputs are equal.
+pub fn constant_time_eq_test_equal<C: CryptoProvider>(_marker: PhantomData<C>) {
+    assert!(C::constant_time_eq(
+        &hex!("00010203040506070809"),
+        &hex!("00010203040506070809")
+    ));
+}
+
+/// Test for `constant_time_eq` when the two inputs are not equal.
+pub fn constant_time_eq_test_not_equal<C: CryptoProvider>(_marker: PhantomData<C>) {
+    assert!(!C::constant_time_eq(
+        &hex!("00010203040506070809"),
+        &hex!("00000000000000000000")
+    ));
+}
+
+/// Random tests for `constant_time_eq`.
+pub fn constant_time_eq_random_test<C: CryptoProvider>(_marker: PhantomData<C>) {
+    let mut rng = rand::thread_rng();
+    for _ in 1..100 {
+        // Test using "oracle" of ==, with possibly different lengths for a and b
+        let mut a = alloc::vec![0; rng.gen_range(1..1000)];
+        rng.fill_bytes(&mut a);
+        let mut b = alloc::vec![0; rng.gen_range(1..1000)];
+        rng.fill_bytes(&mut b);
+        assert_eq!(C::constant_time_eq(&a, &b), a == b);
+    }
+
+    for _ in 1..10000 {
+        // Test using "oracle" of ==, with same lengths for a and b
+        let len = rng.gen_range(1..1000);
+        let mut a = alloc::vec![0; len];
+        rng.fill_bytes(&mut a);
+        let mut b = alloc::vec![0; len];
+        rng.fill_bytes(&mut b);
+        assert_eq!(C::constant_time_eq(&a, &b), a == b);
+    }
+
+    for _ in 1..10000 {
+        // Clones and the original should always be equal
+        let mut a = alloc::vec![0; rng.gen_range(1..1000)];
+        rng.fill_bytes(&mut a);
+        assert!(C::constant_time_eq(&a, &a.clone()));
+    }
+}
+
+/// Generates the test cases to validate the P256 implementation.
+/// For example, to test `MyCryptoProvider`:
+///
+/// ```
+/// use crypto_provider::p256::testing::*;
+///
+/// mod tests {
+///     #[apply(constant_time_eq_test_cases)]
+///     fn constant_time_eq_tests(
+///             testcase: CryptoProviderTestCase<MyCryptoProvider>) {
+///         testcase(PhantomData);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::constant_time_eq_test_not_equal(constant_time_eq_test_not_equal)]
+#[case::constant_time_eq_test_equal(constant_time_eq_test_equal)]
+#[case::constant_time_eq_random_test(constant_time_eq_random_test)]
+fn constant_time_eq_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
diff --git a/nearby/crypto/crypto_provider_test/src/p256.rs b/nearby/crypto/crypto_provider_test/src/p256.rs
new file mode 100644
index 0000000..6c5f395
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/p256.rs
@@ -0,0 +1,239 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+
+extern crate std;
+use crate::elliptic_curve::EphemeralSecretForTesting;
+pub use crate::prelude::*;
+use crate::TestError;
+use core::marker::PhantomData;
+use crypto_provider::p256::{P256PublicKey, P256};
+use crypto_provider::{
+    elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
+    CryptoRng,
+};
+use hex_literal::hex;
+use rstest_reuse::template;
+
+/// An ECDH provider that provides associated types for testing purposes. This can be mostly
+/// considered "aliases" for the otherwise long fully-qualified associated types.
+pub trait EcdhProviderForP256Test {
+    /// The ECDH Provider that is "wrapped" by this type.
+    type EcdhProvider: EcdhProvider<
+        P256,
+        PublicKey = <Self as EcdhProviderForP256Test>::PublicKey,
+        EphemeralSecret = <Self as EcdhProviderForP256Test>::EphemeralSecret,
+        SharedSecret = <Self as EcdhProviderForP256Test>::SharedSecret,
+    >;
+    /// The public key type.
+    type PublicKey: P256PublicKey;
+    /// The ephemeral secret type.
+    type EphemeralSecret: EphemeralSecretForTesting<P256, Impl = Self::EcdhProvider>;
+    /// The shared secret type.
+    type SharedSecret: Into<[u8; 32]>;
+}
+
+impl<E> EcdhProviderForP256Test for E
+where
+    E: EcdhProvider<P256>,
+    E::PublicKey: P256PublicKey,
+    E::EphemeralSecret: EphemeralSecretForTesting<P256>,
+{
+    type EcdhProvider = E;
+    type PublicKey = E::PublicKey;
+    type EphemeralSecret = E::EphemeralSecret;
+    type SharedSecret = E::SharedSecret;
+}
+
+/// Test for P256PublicKey::to_bytes
+pub fn to_bytes_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    let sec1_bytes = hex!(
+        "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+    );
+    let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+    let sec1_bytes_compressed =
+        hex!("02756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
+    assert_eq!(sec1_bytes_compressed.to_vec(), key.to_bytes());
+}
+
+/// Random test for P256PublicKey::to_bytes
+pub fn to_bytes_random_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    for _ in 1..100 {
+        let public_key_bytes =
+            E::EphemeralSecret::generate_random(&mut <E::EphemeralSecret as EphemeralSecret<
+                P256,
+            >>::Rng::new())
+            .public_key_bytes();
+        let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+        assert_eq!(
+            E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
+            public_key,
+            "from_bytes should return the same key for `{public_key_bytes:?}`",
+        );
+    }
+}
+
+/// Test for P256PublicKey::from_affine_coordinates
+pub fn from_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    // https://www.secg.org/sec1-v2.pdf, section 2.3.3
+    let x = hex!("756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
+    let y = hex!("f0b6da270d2a58a060228bbe76c6dc1643088107636deff8aa79e8002a157b92");
+    let sec1 = hex!(
+        "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+    );
+    let expected_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
+    assert!(
+        E::PublicKey::from_affine_coordinates(&x, &y).unwrap() == expected_key,
+        "Public key does not match"
+    );
+}
+
+/// Test for P256PublicKey::from_affine_coordinates
+pub fn from_affine_coordinates_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    // (Invalid) coordinate from wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 193
+    let x = hex!("0000000000000000000000000000000000000000000000000000000000000000");
+    let y = hex!("0000000000000000000000000000000000000000000000000000000000000000");
+    let result = E::PublicKey::from_affine_coordinates(&x, &y);
+    assert!(
+        result.is_err(),
+        "Creating public key from invalid affine coordinate should fail"
+    );
+}
+
+/// Test for P256PublicKey::from_sec1_bytes
+pub fn from_sec1_bytes_not_on_curve_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    // (Invalid) sec1 encoding from wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 193
+    let sec1 = hex!(
+        "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+             00000000000000000000000000000000000000000000"
+    );
+    let result = E::PublicKey::from_sec1_bytes(&sec1);
+    assert!(
+        result.is_err(),
+        "Creating public key from point not on curve should fail"
+    );
+}
+
+/// Test for P256PublicKey::to_affine_coordinates
+pub fn public_key_to_affine_coordinates_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    // https://www.secg.org/sec1-v2.pdf, section 2.3.3
+    let expected_x = hex!("756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
+    let expected_y = hex!("f0b6da270d2a58a060228bbe76c6dc1643088107636deff8aa79e8002a157b92");
+    let sec1 = hex!(
+        "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+    );
+    let public_key = E::PublicKey::from_sec1_bytes(&sec1).unwrap();
+    let (actual_x, actual_y) = public_key.to_affine_coordinates().unwrap();
+    assert_eq!(actual_x, expected_x);
+    assert_eq!(actual_y, expected_y);
+}
+
+/// Test for P256 Diffie-Hellman key exchange.
+pub fn p256_ecdh_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    // From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1
+    // http://google3/third_party/wycheproof/testvectors/ecdh_secp256r1_ecpoint_test.json;l=22;rcl=375894991
+    // sec1 public key manually extracted from the ASN encoded test data
+    let public_key_sec1 = hex!(
+        "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f
+            26ac333a93a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf"
+    );
+    let private = hex!("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346");
+    let expected_shared_secret =
+        hex!("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285");
+    let actual_shared_secret = p256_ecdh_test_impl::<E>(&public_key_sec1, &private).unwrap();
+    assert_eq!(actual_shared_secret.into(), expected_shared_secret);
+}
+
+fn p256_ecdh_test_impl<E: EcdhProviderForP256Test>(
+    public_key_sec1: &[u8],
+    private: &[u8; 32],
+) -> Result<E::SharedSecret, TestError> {
+    let public_key = E::PublicKey::from_sec1_bytes(public_key_sec1).map_err(TestError::new)?;
+    let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
+        .map_err(TestError::new)?;
+    ephemeral_secret
+        .diffie_hellman(&public_key)
+        .map_err(TestError::new)
+}
+
+/// Wycheproof test for P256 Diffie-Hellman.
+pub fn wycheproof_p256_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/ecdh_secp256r1_ecpoint_test.json
+    let test_set =
+        wycheproof::ecdh::TestSet::load(wycheproof::ecdh::TestName::EcdhSecp256r1Ecpoint).unwrap();
+    for test_group in test_set.test_groups {
+        for test in test_group.tests {
+            if test.private_key.len() != 32 {
+                // Some Wycheproof test cases have private key length that are not 32 bytes, but
+                // the RustCrypto implementation doesn't support that (it always take 32 bytes
+                // from the given RNG when generating a new key).
+                continue;
+            };
+            std::println!("Testing {}", test.tc_id);
+            let result = p256_ecdh_test_impl::<E>(
+                &test.public_key,
+                &test
+                    .private_key
+                    .try_into()
+                    .expect("Private key should be 32 bytes long"),
+            );
+            match test.result {
+                wycheproof::TestResult::Valid => {
+                    let shared_secret =
+                        result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
+                    assert_eq!(test.shared_secret, shared_secret.into());
+                }
+                wycheproof::TestResult::Invalid => {
+                    result
+                        .err()
+                        .unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
+                }
+                wycheproof::TestResult::Acceptable => {
+                    if let Ok(shared_secret) = result {
+                        assert_eq!(test.shared_secret, shared_secret.into());
+                    }
+                    // Test passes if `result` is an error because this test is "acceptable"
+                }
+            }
+        }
+    }
+}
+
+/// Generates the test cases to validate the P256 implementation.
+/// For example, to test `MyCryptoProvider`:
+///
+/// ```
+/// use crypto_provider::p256::testing::*;
+///
+/// mod tests {
+///     #[apply(p256_test_cases)]
+///     fn p256_tests(testcase: CryptoProviderTestCase<MyCryptoProvider> {
+///         testcase(PhantomData::<MyCryptoProvider>);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::to_bytes(to_bytes_test)]
+#[case::to_bytes_random(to_bytes_random_test)]
+#[case::from_sec1_bytes_not_on_curve(from_sec1_bytes_not_on_curve_test)]
+#[case::from_affine_coordinates(from_affine_coordinates_test)]
+#[case::from_affine_coordinates_not_on_curve(from_affine_coordinates_not_on_curve_test)]
+#[case::public_key_to_affine_coordinates(public_key_to_affine_coordinates_test)]
+#[case::p256_ecdh(p256_ecdh_test)]
+#[case::wycheproof_p256(wycheproof_p256_test)]
+fn p256_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
diff --git a/nearby/crypto/crypto_provider_test/src/sha2.rs b/nearby/crypto/crypto_provider_test/src/sha2.rs
new file mode 100644
index 0000000..330f284
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/sha2.rs
@@ -0,0 +1,119 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+
+extern crate alloc;
+extern crate std;
+pub use crate::prelude::*;
+use crate::CryptoProvider;
+use alloc::vec::Vec;
+use core::{marker::PhantomData, str::FromStr};
+use crypto_provider::sha2::{Sha256, Sha512};
+use hex::FromHex;
+pub use hex_literal::hex;
+use rstest_reuse::template;
+
+/// Test vectors from SHA256ShortMsg.rsp in
+/// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
+pub fn sha256_cavp_short_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
+    sha256_cavp_vector_test::<C>(include_str!("testdata/SHA256ShortMsg.rsp"));
+}
+
+/// Test vectors from SHA256LongMsg.rsp in
+/// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
+pub fn sha256_cavp_long_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
+    sha256_cavp_vector_test::<C>(include_str!("testdata/SHA256LongMsg.rsp"));
+}
+
+/// Test vectors from SHA512ShortMsg.rsp in
+/// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
+pub fn sha512_cavp_short_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
+    sha512_cavp_vector_test::<C>(include_str!("testdata/SHA512ShortMsg.rsp"));
+}
+
+/// Test vectors from SHA512LongMsg.rsp in
+/// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
+pub fn sha512_cavp_long_vector_test<C: CryptoProvider>(_marker: PhantomData<C>) {
+    sha512_cavp_vector_test::<C>(include_str!("testdata/SHA512LongMsg.rsp"));
+}
+
+/// Test vectors an rsp file in
+/// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
+fn sha256_cavp_vector_test<C: CryptoProvider>(cavp_file_contents: &str) {
+    sha_cavp_vector_test(<C::Sha256 as Sha256>::sha256, cavp_file_contents)
+}
+
+/// Test vectors an rsp file in
+/// <https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing#shavs>
+fn sha512_cavp_vector_test<C: CryptoProvider>(cavp_file_contents: &str) {
+    sha_cavp_vector_test(<C::Sha512 as Sha512>::sha512, cavp_file_contents)
+}
+
+fn sha_cavp_vector_test<const N: usize>(
+    hash_func: impl Fn(&[u8]) -> [u8; N],
+    cavp_file_contents: &str,
+) {
+    let test_cases = cavp_file_contents.split("\n\n").filter_map(|chunk| {
+        let mut len: Option<usize> = None;
+        let mut msg: Option<Vec<u8>> = None;
+        let mut md: Option<Vec<u8>> = None;
+        for line in chunk.split('\n') {
+            if line.starts_with('#') || line.is_empty() || line == std::format!("[L = {N}]") {
+                continue;
+            } else if let Some(len_str) = line.strip_prefix("Len = ") {
+                len = Some(FromStr::from_str(len_str).unwrap());
+            } else if let Some(hex_str) = line.strip_prefix("Msg = ") {
+                msg = Some(Vec::<u8>::from_hex(hex_str).unwrap());
+            } else if let Some(hex_str) = line.strip_prefix("MD = ") {
+                md = Some(Vec::<u8>::from_hex(hex_str).unwrap());
+            } else {
+                panic!("Unexpected line in test file: `{}`", line);
+            }
+        }
+        if let (Some(len), Some(msg), Some(md)) = (len, msg, md) {
+            Some((len, msg, md))
+        } else {
+            None
+        }
+    });
+    for (len, mut msg, md) in test_cases {
+        if len == 0 {
+            // Truncate len = 0, since the test file has "Msg = 00" in there that should be
+            // ignored.
+            msg.truncate(0);
+        }
+        assert_eq!(msg.len(), len / 8);
+        let md_arr: [u8; N] = md.try_into().unwrap();
+        assert_eq!(hash_func(&msg), md_arr);
+    }
+}
+
+/// Generates the test cases to validate the SHA2 implementation.
+/// For example, to test `MyCryptoProvider`:
+///
+/// ```
+/// use crypto_provider::sha2::testing::*;
+///
+/// mod tests {
+///     #[apply(sha2_test_cases)]
+///     fn sha2_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>) {
+///         testcase(PhantomData::<MyCryptoProvider>);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::sha256_cavp_short_vector(sha256_cavp_short_vector_test)]
+#[case::sha256_cavp_long_vector(sha256_cavp_long_vector_test)]
+fn sha2_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
diff --git a/nearby/crypto/crypto_provider/src/testdata/README.md b/nearby/crypto/crypto_provider_test/src/testdata/README.md
similarity index 100%
rename from nearby/crypto/crypto_provider/src/testdata/README.md
rename to nearby/crypto/crypto_provider_test/src/testdata/README.md
diff --git a/nearby/crypto/crypto_provider/src/testdata/SHA256LongMsg.rsp b/nearby/crypto/crypto_provider_test/src/testdata/SHA256LongMsg.rsp
similarity index 100%
rename from nearby/crypto/crypto_provider/src/testdata/SHA256LongMsg.rsp
rename to nearby/crypto/crypto_provider_test/src/testdata/SHA256LongMsg.rsp
diff --git a/nearby/crypto/crypto_provider/src/testdata/SHA256ShortMsg.rsp b/nearby/crypto/crypto_provider_test/src/testdata/SHA256ShortMsg.rsp
similarity index 100%
rename from nearby/crypto/crypto_provider/src/testdata/SHA256ShortMsg.rsp
rename to nearby/crypto/crypto_provider_test/src/testdata/SHA256ShortMsg.rsp
diff --git a/nearby/crypto/crypto_provider/src/testdata/SHA512LongMsg.rsp b/nearby/crypto/crypto_provider_test/src/testdata/SHA512LongMsg.rsp
similarity index 100%
rename from nearby/crypto/crypto_provider/src/testdata/SHA512LongMsg.rsp
rename to nearby/crypto/crypto_provider_test/src/testdata/SHA512LongMsg.rsp
diff --git a/nearby/crypto/crypto_provider/src/testdata/SHA512ShortMsg.rsp b/nearby/crypto/crypto_provider_test/src/testdata/SHA512ShortMsg.rsp
similarity index 100%
rename from nearby/crypto/crypto_provider/src/testdata/SHA512ShortMsg.rsp
rename to nearby/crypto/crypto_provider_test/src/testdata/SHA512ShortMsg.rsp
diff --git a/nearby/crypto/crypto_provider/src/testdata/ecdsa/rfc_test_vectors.txt b/nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt
similarity index 100%
rename from nearby/crypto/crypto_provider/src/testdata/ecdsa/rfc_test_vectors.txt
rename to nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt
diff --git a/nearby/crypto/crypto_provider_test/src/x25519.rs b/nearby/crypto/crypto_provider_test/src/x25519.rs
new file mode 100644
index 0000000..23f8f9b
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/x25519.rs
@@ -0,0 +1,161 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+
+use crate::elliptic_curve::EphemeralSecretForTesting;
+pub use crate::prelude::*;
+use crate::TestError;
+use core::marker::PhantomData;
+use crypto_provider::x25519::X25519;
+use crypto_provider::{
+    elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
+    CryptoRng,
+};
+use hex_literal::hex;
+use rstest_reuse::template;
+
+/// An ECDH provider that provides associated types for testing purposes. This can be mostly
+/// considered "aliases" for the otherwise long fully-qualified associated types.
+pub trait EcdhProviderForX25519Test {
+    /// The ECDH Provider that is "wrapped" by this type.
+    type EcdhProvider: EcdhProvider<
+        X25519,
+        PublicKey = <Self as EcdhProviderForX25519Test>::PublicKey,
+        EphemeralSecret = <Self as EcdhProviderForX25519Test>::EphemeralSecret,
+        SharedSecret = <Self as EcdhProviderForX25519Test>::SharedSecret,
+    >;
+    /// The public key type.
+    type PublicKey: PublicKey<X25519>;
+    /// The ephemeral secret type.
+    type EphemeralSecret: EphemeralSecretForTesting<X25519, Impl = Self::EcdhProvider>;
+    /// The shared secret type.
+    type SharedSecret: Into<[u8; 32]>;
+}
+
+impl<E> EcdhProviderForX25519Test for E
+where
+    E: EcdhProvider<X25519>,
+    E::PublicKey: PublicKey<X25519>,
+    E::EphemeralSecret: EphemeralSecretForTesting<X25519>,
+{
+    type EcdhProvider = E;
+    type PublicKey = E::PublicKey;
+    type EphemeralSecret = E::EphemeralSecret;
+    type SharedSecret = E::SharedSecret;
+}
+
+/// Test for `PublicKey<X25519>::to_bytes`
+pub fn x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+    let public_key_bytes = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
+    let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+    assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes());
+}
+
+/// Random test for `PublicKey<X25519>::to_bytes`
+pub fn x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+    for _ in 1..100 {
+        let public_key_bytes =
+            E::EphemeralSecret::generate_random(&mut <E::EphemeralSecret as EphemeralSecret<
+                X25519,
+            >>::Rng::new())
+            .public_key_bytes();
+        let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+        assert_eq!(
+            E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
+            public_key,
+            "from_bytes should return the same key for `{public_key_bytes:?}`",
+        );
+    }
+}
+
+/// Test for X25519 Diffie-Hellman key exchange.
+pub fn x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+    // From wycheproof ecdh_secx25519r1_ecpoint_test.json, tcId 1
+    // http://google3/third_party/wycheproof/testvectors/ecdh_secx25519r1_ecpoint_test.json;l=22;rcl=375894991
+    // sec1 public key manually extracted from the ASN encoded test data
+    let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
+    let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475");
+    let expected_shared_secret =
+        hex!("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320");
+    let result = x25519_ecdh_test_impl::<E>(&public_key, &private).unwrap();
+    assert_eq!(expected_shared_secret, result.into());
+}
+
+fn x25519_ecdh_test_impl<E: EcdhProviderForX25519Test>(
+    public_key: &[u8],
+    private: &[u8; 32],
+) -> Result<E::SharedSecret, TestError> {
+    let public_key = E::PublicKey::from_bytes(public_key).map_err(TestError::new)?;
+    let ephemeral_secret = E::EphemeralSecret::from_private_components(private, &public_key)
+        .map_err(TestError::new)?;
+    ephemeral_secret
+        .diffie_hellman(&public_key)
+        .map_err(TestError::new)
+}
+
+/// Wycheproof test for X25519 Diffie-Hellman.
+pub fn wycheproof_x25519_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
+    // Test cases from https://github.com/randombit/wycheproof-rs/blob/master/src/data/x25519_test.json
+    let test_set = wycheproof::xdh::TestSet::load(wycheproof::xdh::TestName::X25519).unwrap();
+    for test_group in test_set.test_groups {
+        for test in test_group.tests {
+            let result = x25519_ecdh_test_impl::<E>(
+                &test.public_key,
+                &test
+                    .private_key
+                    .try_into()
+                    .expect("Private keys should be 32 bytes long"),
+            );
+            match test.result {
+                wycheproof::TestResult::Valid => {
+                    let shared_secret =
+                        result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
+                    assert_eq!(&test.shared_secret, &shared_secret.into());
+                }
+                wycheproof::TestResult::Invalid => {
+                    result
+                        .err()
+                        .unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
+                }
+                wycheproof::TestResult::Acceptable => {
+                    if let Ok(shared_secret) = result {
+                        assert_eq!(test.shared_secret, shared_secret.into());
+                    }
+                    // Test passes if `result` is an error because this test is "acceptable"
+                }
+            }
+        }
+    }
+}
+
+/// Generates the test cases to validate the x25519 implementation.
+/// For example, to test `MyCryptoProvider`:
+///
+/// ```
+/// use crypto_provider::x25519::testing::*;
+///
+/// mod tests {
+///     #[apply(x25519_test_cases)]
+///     fn x25519_tests(testcase: CryptoProviderTestCase<MyCryptoProvider>) {
+///         testcase(PhantomData::<MyCryptoProvider>);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::x25519_to_bytes(x25519_to_bytes_test)]
+#[case::x25519_to_bytes_random(x25519_to_bytes_random_test)]
+#[case::x25519_ecdh(x25519_ecdh_test)]
+#[case::wycheproof_x25519(wycheproof_x25519_test)]
+fn x25519_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
diff --git a/nearby/presence/array_view/src/lib.rs b/nearby/presence/array_view/src/lib.rs
index 7a579a6..24a5001 100644
--- a/nearby/presence/array_view/src/lib.rs
+++ b/nearby/presence/array_view/src/lib.rs
@@ -42,6 +42,16 @@
 }
 
 impl<T, const N: usize> ArrayView<T, N> {
+    /// A version of [`ArrayView#try_from_array`] which panics if `len > buffer.len()`,
+    /// suitable for usage in `const` contexts.
+    pub const fn const_from_array(array: [T; N], len: usize) -> ArrayView<T, N> {
+        if N < len {
+            panic!("Invalid const ArrayView");
+        } else {
+            ArrayView { array, len }
+        }
+    }
+
     /// Create an [ArrayView] of the first `len` elements of `buffer`.
     ///
     /// Returns `None` if `len > buffer.len()`.
diff --git a/nearby/presence/ldt/fuzz/Cargo.lock b/nearby/presence/ldt/fuzz/Cargo.lock
index e0b529d..285bbca 100644
--- a/nearby/presence/ldt/fuzz/Cargo.lock
+++ b/nearby/presence/ldt/fuzz/Cargo.lock
@@ -207,9 +207,9 @@
 
 [[package]]
 name = "der"
-version = "0.7.4"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11"
+checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -260,9 +260,9 @@
 
 [[package]]
 name = "elliptic-curve"
-version = "0.13.4"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7"
+checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d"
 dependencies = [
  "base16ct",
  "crypto-bigint",
@@ -295,9 +295,9 @@
 
 [[package]]
 name = "generic-array"
-version = "0.14.7"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
 dependencies = [
  "typenum",
  "version_check",
@@ -426,9 +426,9 @@
 
 [[package]]
 name = "p256"
-version = "0.13.2"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+checksum = "7270da3e5caa82afd3deb054cc237905853813aea3859544bc082c3fe55b8d47"
 dependencies = [
  "elliptic-curve",
  "primeorder",
@@ -470,9 +470,9 @@
 
 [[package]]
 name = "primeorder"
-version = "0.13.1"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5"
+checksum = "7613fdcc0831c10060fa69833ea8fa2caa94b6456f51e25356a885b530a2e3d0"
 dependencies = [
  "elliptic-curve",
 ]
@@ -525,9 +525,9 @@
 
 [[package]]
 name = "sec1"
-version = "0.7.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e"
 dependencies = [
  "base16ct",
  "der",
diff --git a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
index 31409c8..92f2deb 100644
--- a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
+++ b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
@@ -211,9 +211,9 @@
 
 [[package]]
 name = "der"
-version = "0.7.4"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11"
+checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -264,9 +264,9 @@
 
 [[package]]
 name = "elliptic-curve"
-version = "0.13.4"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7"
+checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d"
 dependencies = [
  "base16ct",
  "crypto-bigint",
@@ -299,9 +299,9 @@
 
 [[package]]
 name = "generic-array"
-version = "0.14.7"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
 dependencies = [
  "typenum",
  "version_check",
@@ -453,9 +453,9 @@
 
 [[package]]
 name = "p256"
-version = "0.13.2"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+checksum = "7270da3e5caa82afd3deb054cc237905853813aea3859544bc082c3fe55b8d47"
 dependencies = [
  "elliptic-curve",
  "primeorder",
@@ -497,9 +497,9 @@
 
 [[package]]
 name = "primeorder"
-version = "0.13.1"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5"
+checksum = "7613fdcc0831c10060fa69833ea8fa2caa94b6456f51e25356a885b530a2e3d0"
 dependencies = [
  "elliptic-curve",
 ]
@@ -552,9 +552,9 @@
 
 [[package]]
 name = "sec1"
-version = "0.7.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e"
 dependencies = [
  "base16ct",
  "der",
diff --git a/nearby/presence/ldt_np_adv_ffi/Cargo.lock b/nearby/presence/ldt_np_adv_ffi/Cargo.lock
index 12f0615..0ad9a64 100644
--- a/nearby/presence/ldt_np_adv_ffi/Cargo.lock
+++ b/nearby/presence/ldt_np_adv_ffi/Cargo.lock
@@ -305,9 +305,9 @@
 
 [[package]]
 name = "elliptic-curve"
-version = "0.13.4"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7"
+checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d"
 dependencies = [
  "base16ct",
  "crypto-bigint",
@@ -581,9 +581,9 @@
 
 [[package]]
 name = "p256"
-version = "0.13.2"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+checksum = "7270da3e5caa82afd3deb054cc237905853813aea3859544bc082c3fe55b8d47"
 dependencies = [
  "elliptic-curve",
  "primeorder",
@@ -647,9 +647,9 @@
 
 [[package]]
 name = "primeorder"
-version = "0.13.1"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5"
+checksum = "7613fdcc0831c10060fa69833ea8fa2caa94b6456f51e25356a885b530a2e3d0"
 dependencies = [
  "elliptic-curve",
 ]
@@ -734,9 +734,9 @@
 
 [[package]]
 name = "sec1"
-version = "0.7.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e"
 dependencies = [
  "base16ct",
  "der",
diff --git a/nearby/presence/ldt_np_adv_ffi/src/lib.rs b/nearby/presence/ldt_np_adv_ffi/src/lib.rs
index 7b440e4..b4dd932 100644
--- a/nearby/presence/ldt_np_adv_ffi/src/lib.rs
+++ b/nearby/presence/ldt_np_adv_ffi/src/lib.rs
@@ -22,15 +22,11 @@
 )]
 // These features are needed to support no_std + alloc
 #![feature(lang_items)]
-#![feature(alloc_error_handler)]
 
 //! Rust ffi wrapper of ldt_np_adv, can be called from C/C++ Clients
 
-mod handle_map;
-
 extern crate alloc;
 
-use crate::handle_map::get_dec_handle_map;
 use alloc::boxed::Box;
 use core::slice;
 use handle_map::get_enc_handle_map;
@@ -40,6 +36,10 @@
 };
 use np_hkdf::NpKeySeedHkdf;
 
+use crate::handle_map::get_dec_handle_map;
+
+mod handle_map;
+
 // Pull in the needed deps for std vs no_std
 cfg_if::cfg_if! {
     // Test pulls in std which causes duplicate errors
diff --git a/nearby/presence/ldt_np_adv_ffi/src/no_std.rs b/nearby/presence/ldt_np_adv_ffi/src/no_std.rs
index 34a16ef..524bc3d 100644
--- a/nearby/presence/ldt_np_adv_ffi/src/no_std.rs
+++ b/nearby/presence/ldt_np_adv_ffi/src/no_std.rs
@@ -14,18 +14,12 @@
 //
 // mod to handle all of the impls needed for no_std
 
-use libc_alloc::LibcAlloc;
-
 extern crate panic_abort;
 
+use libc_alloc::LibcAlloc;
+
 #[global_allocator]
 static ALLOCATOR: LibcAlloc = LibcAlloc;
 
 #[lang = "eh_personality"]
 extern "C" fn eh_personality() {}
-
-#[alloc_error_handler]
-#[allow(clippy::panic)]
-fn default_handler(layout: core::alloc::Layout) -> ! {
-    panic!("memory allocation of {} bytes failed", layout.size())
-}
diff --git a/nearby/presence/ldt_np_c_sample/tests/np_ffi_tests.cc b/nearby/presence/ldt_np_c_sample/tests/np_ffi_tests.cc
index 1cc2ae2..6359e7e 100644
--- a/nearby/presence/ldt_np_c_sample/tests/np_ffi_tests.cc
+++ b/nearby/presence/ldt_np_c_sample/tests/np_ffi_tests.cc
@@ -14,13 +14,13 @@
 
 #include <gtest/gtest.h>
 #include <json/json.h>
-#include <fstream>
 
 extern "C" {
 #include "np_ldt.h"
 }
 
 #include <algorithm>
+#include <fstream>
 
 // TODO: get multi threaded tests working on windows
 #ifndef _WIN32
diff --git a/nearby/presence/xts_aes/fuzz/Cargo.lock b/nearby/presence/xts_aes/fuzz/Cargo.lock
index 06b5f86..2bdaef3 100644
--- a/nearby/presence/xts_aes/fuzz/Cargo.lock
+++ b/nearby/presence/xts_aes/fuzz/Cargo.lock
@@ -207,9 +207,9 @@
 
 [[package]]
 name = "der"
-version = "0.7.4"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b14af2045fa69ed2b7a48934bebb842d0f33e73e96e78766ecb14bb5347a11"
+checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0"
 dependencies = [
  "const-oid",
  "zeroize",
@@ -260,9 +260,9 @@
 
 [[package]]
 name = "elliptic-curve"
-version = "0.13.4"
+version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7"
+checksum = "6ea5a92946e8614bb585254898bb7dd1ddad241ace60c52149e3765e34cc039d"
 dependencies = [
  "base16ct",
  "crypto-bigint",
@@ -295,9 +295,9 @@
 
 [[package]]
 name = "generic-array"
-version = "0.14.7"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
 dependencies = [
  "typenum",
  "version_check",
@@ -407,9 +407,9 @@
 
 [[package]]
 name = "p256"
-version = "0.13.2"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+checksum = "7270da3e5caa82afd3deb054cc237905853813aea3859544bc082c3fe55b8d47"
 dependencies = [
  "elliptic-curve",
  "primeorder",
@@ -451,9 +451,9 @@
 
 [[package]]
 name = "primeorder"
-version = "0.13.1"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5"
+checksum = "7613fdcc0831c10060fa69833ea8fa2caa94b6456f51e25356a885b530a2e3d0"
 dependencies = [
  "elliptic-curve",
 ]
@@ -506,9 +506,9 @@
 
 [[package]]
 name = "sec1"
-version = "0.7.2"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e"
 dependencies = [
  "base16ct",
  "der",
diff --git a/nearby/scripts/build-script.sh b/nearby/scripts/build-script.sh
index 3643482..5fc999e 100755
--- a/nearby/scripts/build-script.sh
+++ b/nearby/scripts/build-script.sh
@@ -22,7 +22,9 @@
 # Use to generate headers for new source code files
 gen_headers() {
   set -e
-  $HOME/go/bin/addlicense -c "Google LLC" -l apache -ignore=**/android/build/** -ignore=target/** -ignore=**/target/** -ignore=".idea/*" -ignore=**/cmake-build/** -ignore="**/java/build/**" .
+  $HOME/go/bin/addlicense -c "Google LLC" -l apache -ignore=**/android/build/** -ignore=target/** \
+      -ignore=**/target/** -ignore=".idea/*" -ignore=**/cmake-build/** -ignore="**/java/build/**" \
+      -ignore="**/ukey2_c_ffi/cpp/build/**" .
 }
 
 # Checks the workspace 3rd party crates and makes sure they have a valid license
@@ -40,6 +42,7 @@
   check_workspace
   check_boringssl
   check_ldt_ffi
+  check_ukey2_ffi
   build_fuzzers
 }
 
@@ -88,6 +91,7 @@
       -ignore="**/cmake-build/**" \
       -ignore="**/java/build/**" \
       -ignore="**/java/*/build/**" \
+      -ignore="**/ukey2_c_ffi/cpp/build/**" \
       .; then
     echo "License header check succeeded!"
   else
@@ -169,6 +173,30 @@
   cd ../
 }
 
+# Builds and runs tests for the UKEY2 FFI
+check_ukey2_ffi() {
+  set -e
+  cd $SCRIPT_DIR/..
+  cd connections/ukey2/ukey2_c_ffi
+  # Default build, RustCrypto
+  cargo build --release --lib
+  # Try to build with OpenSSL
+  cargo build --no-default-features --features=openssl
+  cargo doc --no-deps
+  cargo clippy --release
+  cargo clippy --no-default-features --features=openssl
+  cargo deny check
+
+  # build C/C++ samples, tests, and benches
+  cd cpp
+  mkdir -p build && cd build
+  cmake ..
+  make all
+  ctest
+
+  cd $SCRIPT_DIR/..
+}
+
 # Clones boringssl and uses bindgen to generate the rust crate, applies AOSP
 # specific patches to the 3p `openssl` crate so that it can use a bssl backend
 prepare_boringssl() {
@@ -181,11 +209,12 @@
     git clone https://boringssl.googlesource.com/boringssl
   fi
   # Snap to the AOSP commit of boringssl
-  boringssl_rev=$(curl https://android.googlesource.com/platform/external/boringssl/+/master/BORINGSSL_REVISION?format=text | base64 -d)
+   boringssl_rev=$(curl https://android.googlesource.com/platform/external/boringssl/+/master/BORINGSSL_REVISION?format=text | base64 -d)
   cd boringssl && git checkout $boringssl_rev && mkdir -p build && cd build
   target=$(rustc -vV | awk '/host/ { print $2 }')
   cmake -G Ninja .. -DRUST_BINDINGS="$target" && ninja
-  # A valid Rust crate is built under `boringssl-build/boringssl/build/rust/bssl-sys`
+  # The Rust crate is in `boringssl-build/boringssl/build/rust/bssl-sys`, which depends on a
+  # cmake-generated file as part of its source.
 
   cd $projectroot/boringssl-build
   rm -Rf rust-openssl
diff --git a/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch b/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch
index aca9408..be6ca09 100644
--- a/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch
+++ b/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch
@@ -1,35 +1,72 @@
-From b89f5e640f0d4d9cb87412d897797ce89e12d15b Mon Sep 17 00:00:00 2001
+From 66e1daa9b4a345617bf9a090d56fa09ccd9b1d35 Mon Sep 17 00:00:00 2001
 From: Maurice Lam <yukl@google.com>
-Date: Thu, 2 Feb 2023 19:16:31 +0000
-Subject: [PATCH 1/3] Apply Android changes
+Date: Wed, 26 Apr 2023 02:21:36 +0000
+Subject: [PATCH] Apply Android patches
 
 ---
- .cargo/config.toml           |    2 +
+ openssl/src/bio.rs           |    6 +-
+ openssl/src/bn.rs            |    2 +-
  openssl/src/cipher.rs        |    4 +
+ openssl/src/dh.rs            |    2 +-
  openssl/src/ec.rs            |   20 +
  openssl/src/encrypt.rs       |    4 +-
  openssl/src/hkdf.rs          |   89 ++
- openssl/src/hmac.rs          |   68 ++
- openssl/src/lib.rs           |    7 +
+ openssl/src/hmac.rs          |  217 ++++
+ openssl/src/lib.rs           |   12 +
  openssl/src/pkey.rs          |   20 +-
  openssl/src/sign.rs          |   10 +-
  openssl/src/symm.rs          |    7 +-
- openssl/src/x509/mod.rs      |   36 +
- openssl/src/x509/mod.rs.orig | 1843 ++++++++++++++++++++++++++++++++++
- 12 files changed, 2091 insertions(+), 19 deletions(-)
- create mode 100644 .cargo/config.toml
+ openssl/src/x509/mod.rs      |   52 +-
+ openssl/src/x509/mod.rs.orig | 1879 ++++++++++++++++++++++++++++++++++
+ 14 files changed, 2292 insertions(+), 32 deletions(-)
  create mode 100644 openssl/src/hkdf.rs
  create mode 100644 openssl/src/hmac.rs
  create mode 100644 openssl/src/x509/mod.rs.orig
 
-diff --git a/.cargo/config.toml b/.cargo/config.toml
-new file mode 100644
-index 00000000..f92f06a0
---- /dev/null
-+++ b/.cargo/config.toml
-@@ -0,0 +1,2 @@
-+[patch.crates-io]
-+bssl-sys = { path = "../boringssl/build/rust" }
+diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs
+index 6a72552a..03242188 100644
+--- a/openssl/src/bio.rs
++++ b/openssl/src/bio.rs
+@@ -4,7 +4,7 @@ use std::marker::PhantomData;
+ use std::ptr;
+ use std::slice;
+ 
+-use crate::cvt_p;
++use crate::{cvt_p, SignedLenType};
+ use crate::error::ErrorStack;
+ 
+ pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>);
+@@ -25,7 +25,7 @@ impl<'a> MemBioSlice<'a> {
+         let bio = unsafe {
+             cvt_p(BIO_new_mem_buf(
+                 buf.as_ptr() as *const _,
+-                buf.len() as c_int,
++                buf.len() as SignedLenType,
+             ))?
+         };
+ 
+@@ -78,7 +78,7 @@ cfg_if! {
+         use ffi::BIO_new_mem_buf;
+     } else {
+         #[allow(bad_style)]
+-        unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: ::libc::c_int) -> *mut ffi::BIO {
++        unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: SignedLenType) -> *mut ffi::BIO {
+             ffi::BIO_new_mem_buf(buf as *mut _, len)
+         }
+     }
+diff --git a/openssl/src/bn.rs b/openssl/src/bn.rs
+index 0328730a..fe820a2c 100644
+--- a/openssl/src/bn.rs
++++ b/openssl/src/bn.rs
+@@ -814,7 +814,7 @@ impl BigNumRef {
+     /// assert_eq!(&bn_vec, &[0, 0, 0x45, 0x43]);
+     /// ```
+     #[corresponds(BN_bn2binpad)]
+-    #[cfg(ossl110)]
++    #[cfg(any(boringssl, ossl110))]
+     pub fn to_vec_padded(&self, pad_to: i32) -> Result<Vec<u8>, ErrorStack> {
+         let mut v = Vec::with_capacity(pad_to as usize);
+         unsafe {
 diff --git a/openssl/src/cipher.rs b/openssl/src/cipher.rs
 index aeedf459..570794ca 100644
 --- a/openssl/src/cipher.rs
@@ -64,6 +101,19 @@
      pub fn bf_ecb() -> &'static CipherRef {
          unsafe { CipherRef::from_ptr(ffi::EVP_bf_ecb() as *mut _) }
      }
+diff --git a/openssl/src/dh.rs b/openssl/src/dh.rs
+index 12170b99..e781543e 100644
+--- a/openssl/src/dh.rs
++++ b/openssl/src/dh.rs
+@@ -239,7 +239,7 @@ where
+ }
+ 
+ cfg_if! {
+-    if #[cfg(any(ossl110, libressl270))] {
++    if #[cfg(any(ossl110, libressl270, boringssl))] {
+         use ffi::{DH_set0_pqg, DH_get0_pqg, DH_get0_key, DH_set0_key};
+     } else {
+         #[allow(bad_style)]
 diff --git a/openssl/src/ec.rs b/openssl/src/ec.rs
 index 248ced3e..c56f5da7 100644
 --- a/openssl/src/ec.rs
@@ -214,17 +264,19 @@
 +}
 diff --git a/openssl/src/hmac.rs b/openssl/src/hmac.rs
 new file mode 100644
-index 00000000..601ae01b
+index 00000000..465781e2
 --- /dev/null
 +++ b/openssl/src/hmac.rs
-@@ -0,0 +1,68 @@
-+use crate::cvt_p;
+@@ -0,0 +1,217 @@
 +use crate::error::ErrorStack;
 +use crate::md::MdRef;
++use crate::{cvt, cvt_p};
++use ffi::HMAC_CTX;
 +use foreign_types::ForeignTypeRef;
++use libc::{c_uint, c_void};
 +use openssl_macros::corresponds;
-+use libc::{c_void, c_uint};
 +use std::convert::TryFrom;
++use std::ptr;
 +
 +/// Computes the HMAC as a one-shot operation.
 +///
@@ -240,8 +292,9 @@
 +    md: &MdRef,
 +    key: &[u8],
 +    data: &[u8],
-+    out: &'a mut [u8]
++    out: &'a mut [u8],
 +) -> Result<&'a [u8], ErrorStack> {
++    assert!(out.len() >= md.size());
 +    let mut out_len = c_uint::try_from(out.len()).unwrap();
 +    unsafe {
 +        cvt_p(ffi::HMAC(
@@ -251,43 +304,189 @@
 +            data.as_ptr(),
 +            data.len(),
 +            out.as_mut_ptr(),
-+            &mut out_len
-+            ))?;
++            &mut out_len,
++        ))?;
 +    }
 +    Ok(&out[..out_len as usize])
 +}
 +
++/// A context object used to perform HMAC operations.
++///
++/// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message
++/// authentication, which is based on a hash function.
++///
++/// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead.
++#[cfg(boringssl)]
++pub struct HmacCtx {
++    ctx: *mut HMAC_CTX,
++    output_size: usize,
++}
++
++#[cfg(boringssl)]
++impl HmacCtx {
++    /// Creates a new [HmacCtx] to use the hash function `md` and key `key`.
++    #[corresponds(HMAC_Init_ex)]
++    pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> {
++        unsafe {
++            // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into
++            // ErrorStack in the returned result by `cvt_p`.
++            let ctx = cvt_p(ffi::HMAC_CTX_new())?;
++            // Safety:
++            // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new,
++            //   which is the line above.
++            // - HMAC_Init_ex may return an error if key is null but the md is different from
++            //   before. This is avoided here since key is guaranteed to be non-null.
++            cvt(ffi::HMAC_Init_ex(
++                ctx,
++                key.as_ptr() as *const c_void,
++                key.len(),
++                md.as_ptr(),
++                ptr::null_mut(),
++            ))?;
++            Ok(Self {
++                ctx,
++                output_size: md.size(),
++            })
++        }
++    }
++
++    /// `update` can be called repeatedly with chunks of the message `data` to be authenticated.
++    #[corresponds(HMAC_Update)]
++    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
++        unsafe {
++            // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the
++            // returned result by `cvt`.
++            cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ())
++        }
++    }
++
++    /// Finishes the HMAC process, and places the message authentication code in `output`.
++    /// The number of bytes written to `output` is returned.
++    ///
++    /// # Panics
++    ///
++    /// Panics if the `output` is smaller than the required size. The output size is indicated by
++    /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will
++    /// always be large enough.
++    #[corresponds(HMAC_Final)]
++    pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
++        assert!(output.len() >= self.output_size);
++        unsafe {
++            // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer
++            // than the length of `output`.
++            let mut size: c_uint = 0;
++            cvt(ffi::HMAC_Final(
++                self.ctx,
++                output.as_mut_ptr(),
++                &mut size as *mut c_uint,
++            ))
++            .map(|_| size as usize)
++        }
++    }
++}
++
++impl Drop for HmacCtx {
++    #[corresponds(HMAC_CTX_free)]
++    fn drop(&mut self) {
++        unsafe {
++            ffi::HMAC_CTX_free(self.ctx);
++        }
++    }
++}
++
 +#[cfg(test)]
 +mod tests {
 +    use super::*;
 +    use crate::md::Md;
-+    use crate::memcmp;
 +
-+    const SHA_256_DIGEST_SIZE:usize = 32;
++    const SHA_256_DIGEST_SIZE: usize = 32;
 +
 +    #[test]
 +    fn hmac_sha256_test() {
-+        let expected_hmac = [0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7];
++        let expected_hmac = [
++            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++            0x2e, 0x32, 0xcf, 0xf7,
++        ];
 +        let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
-+        let key:[u8; 20] = [0x0b; 20];
++        let key: [u8; 20] = [0x0b; 20];
 +        let data = b"Hi There";
-+        let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+        expect!(memcmp::eq(&hmac_result, &expected_hmac));
++        let hmac_result =
++            hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
++        assert_eq!(&hmac_result, &expected_hmac);
++    }
++
++    #[test]
++    #[should_panic]
++    fn hmac_sha256_output_too_short() {
++        let mut out = vec![0_u8; 1];
++        let key: [u8; 20] = [0x0b; 20];
++        let data = b"Hi There";
++        hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
 +    }
 +
 +    #[test]
 +    fn hmac_sha256_test_big_buffer() {
-+        let expected_hmac = [0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7];
++        let expected_hmac = [
++            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++            0x2e, 0x32, 0xcf, 0xf7,
++        ];
 +        let mut out: [u8; 100] = [0; 100];
-+        let key:[u8;20] = [0x0b; 20];
++        let key: [u8; 20] = [0x0b; 20];
 +        let data = b"Hi There";
-+        let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+        expect_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
-+        expect!(memcmp::eq(&hmac_result, &expected_hmac));
++        let hmac_result =
++            hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
++        assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
++        assert_eq!(&hmac_result, &expected_hmac);
++    }
++
++    #[test]
++    fn hmac_sha256_update_test() {
++        let expected_hmac = [
++            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++            0x2e, 0x32, 0xcf, 0xf7,
++        ];
++        let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
++        let key: [u8; 20] = [0x0b; 20];
++        let data = b"Hi There";
++        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
++        hmac_ctx.update(data).unwrap();
++        let size = hmac_ctx.finalize(&mut out).unwrap();
++        assert_eq!(&out, &expected_hmac);
++        assert_eq!(size, SHA_256_DIGEST_SIZE);
++    }
++
++    #[test]
++    fn hmac_sha256_update_chunks_test() {
++        let expected_hmac = [
++            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++            0x2e, 0x32, 0xcf, 0xf7,
++        ];
++        let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
++        let key: [u8; 20] = [0x0b; 20];
++        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
++        hmac_ctx.update(b"Hi").unwrap();
++        hmac_ctx.update(b" There").unwrap();
++        let size = hmac_ctx.finalize(&mut out).unwrap();
++        assert_eq!(&out, &expected_hmac);
++        assert_eq!(size, SHA_256_DIGEST_SIZE);
++    }
++
++    #[test]
++    #[should_panic]
++    fn hmac_sha256_update_output_too_short() {
++        let mut out = vec![0_u8; 1];
++        let key: [u8; 20] = [0x0b; 20];
++        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
++        hmac_ctx.update(b"Hi There").unwrap();
++        hmac_ctx.finalize(&mut out).unwrap();
 +    }
 +}
 diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs
-index 035c90c6..fec22cd9 100644
+index 035c90c6..02a51dbb 100644
 --- a/openssl/src/lib.rs
 +++ b/openssl/src/lib.rs
 @@ -120,6 +120,9 @@
@@ -311,6 +510,18 @@
  #[cfg(ossl300)]
  pub mod lib_ctx;
  pub mod md;
+@@ -189,6 +196,11 @@ type LenType = libc::size_t;
+ #[cfg(not(boringssl))]
+ type LenType = libc::c_int;
+ 
++#[cfg(boringssl)]
++type SignedLenType = libc::ssize_t;
++#[cfg(not(boringssl))]
++type SignedLenType = libc::c_int;
++
+ #[inline]
+ fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
+     if r.is_null() {
 diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs
 index 780bd637..92daa882 100644
 --- a/openssl/src/pkey.rs
@@ -494,7 +705,7 @@
          unsafe { Cipher(ffi::EVP_bf_ecb()) }
      }
 diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs
-index 940c8c9c..34b86c10 100644
+index 940c8c9c..f9477e4a 100644
 --- a/openssl/src/x509/mod.rs
 +++ b/openssl/src/x509/mod.rs
 @@ -356,6 +356,19 @@ impl X509Builder {
@@ -517,6 +728,70 @@
      /// Consumes the builder, returning the certificate.
      pub fn build(self) -> X509 {
          self.0
+@@ -898,13 +911,13 @@ impl X509NameBuilder {
+     pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> {
+         unsafe {
+             let field = CString::new(field).unwrap();
+-            assert!(value.len() <= c_int::max_value() as usize);
++            assert!(value.len() <= isize::max_value() as usize);
+             cvt(ffi::X509_NAME_add_entry_by_txt(
+                 self.0.as_ptr(),
+                 field.as_ptr() as *mut _,
+                 ffi::MBSTRING_UTF8,
+                 value.as_ptr(),
+-                value.len() as c_int,
++                value.len() as isize,
+                 -1,
+                 0,
+             ))
+@@ -925,13 +938,13 @@ impl X509NameBuilder {
+     ) -> Result<(), ErrorStack> {
+         unsafe {
+             let field = CString::new(field).unwrap();
+-            assert!(value.len() <= c_int::max_value() as usize);
++            assert!(value.len() <= isize::max_value() as usize);
+             cvt(ffi::X509_NAME_add_entry_by_txt(
+                 self.0.as_ptr(),
+                 field.as_ptr() as *mut _,
+                 ty.as_raw(),
+                 value.as_ptr(),
+-                value.len() as c_int,
++                value.len() as isize,
+                 -1,
+                 0,
+             ))
+@@ -946,13 +959,13 @@ impl X509NameBuilder {
+     /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html
+     pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> {
+         unsafe {
+-            assert!(value.len() <= c_int::max_value() as usize);
++            assert!(value.len() <= isize::max_value() as usize);
+             cvt(ffi::X509_NAME_add_entry_by_NID(
+                 self.0.as_ptr(),
+                 field.as_raw(),
+                 ffi::MBSTRING_UTF8,
+                 value.as_ptr() as *mut _,
+-                value.len() as c_int,
++                value.len() as isize,
+                 -1,
+                 0,
+             ))
+@@ -972,13 +985,13 @@ impl X509NameBuilder {
+         ty: Asn1Type,
+     ) -> Result<(), ErrorStack> {
+         unsafe {
+-            assert!(value.len() <= c_int::max_value() as usize);
++            assert!(value.len() <= isize::max_value() as usize);
+             cvt(ffi::X509_NAME_add_entry_by_NID(
+                 self.0.as_ptr(),
+                 field.as_raw(),
+                 ty.as_raw(),
+                 value.as_ptr() as *mut _,
+-                value.len() as c_int,
++                value.len() as isize,
+                 -1,
+                 0,
+             ))
 @@ -1286,6 +1299,29 @@ impl X509ReqBuilder {
          }
      }
@@ -549,10 +824,10 @@
          self.0
 diff --git a/openssl/src/x509/mod.rs.orig b/openssl/src/x509/mod.rs.orig
 new file mode 100644
-index 00000000..940c8c9c
+index 00000000..34b86c10
 --- /dev/null
 +++ b/openssl/src/x509/mod.rs.orig
-@@ -0,0 +1,1843 @@
+@@ -0,0 +1,1879 @@
 +//! The standard defining the format of public key certificates.
 +//!
 +//! An `X509` certificate binds an identity to a public key, and is either
@@ -911,6 +1186,19 @@
 +        unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) }
 +    }
 +
++    /// Signs the certificate with a private key but without a digest.
++    ///
++    /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
++    /// message digest.
++    #[cfg(boringssl)]
++    #[corresponds(X509_sign)]
++    pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
++    where
++        T: HasPrivate,
++    {
++        unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) }
++    }
++
 +    /// Consumes the builder, returning the certificate.
 +    pub fn build(self) -> X509 {
 +        self.0
@@ -1841,6 +2129,29 @@
 +        }
 +    }
 +
++    /// Sign the request using a private key without a digest.
++    ///
++    /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
++    /// message digest.
++    ///
++    /// This corresponds to [`X509_REQ_sign`].
++    ///
++    /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html
++    #[cfg(boringssl)]
++    pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
++    where
++        T: HasPrivate,
++    {
++        unsafe {
++            cvt(ffi::X509_REQ_sign(
++                self.0.as_ptr(),
++                key.as_ptr(),
++                ptr::null(),
++            ))
++            .map(|_| ())
++        }
++    }
++
 +    /// Returns the `X509Req`.
 +    pub fn build(self) -> X509Req {
 +        self.0
@@ -2397,5 +2708,5 @@
 +    }
 +}
 -- 
-2.39.1.519.gcb327c4b5f-goog
+2.40.1.495.gc816e09b53d-goog
 
diff --git a/nearby/scripts/openssl-patches/0002-Make-openssl-buildable.patch b/nearby/scripts/openssl-patches/0002-Make-openssl-buildable.patch
deleted file mode 100644
index 46f8a4b..0000000
--- a/nearby/scripts/openssl-patches/0002-Make-openssl-buildable.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-From 4a7246ed78e8845dd2ac5477fb4abd490a83f6eb Mon Sep 17 00:00:00 2001
-From: Maurice Lam <yukl@google.com>
-Date: Thu, 2 Feb 2023 19:21:46 +0000
-Subject: [PATCH 2/3] Make openssl buildable
-
----
- openssl/src/bio.rs | 9 +++++++--
- openssl/src/dh.rs  | 2 +-
- 2 files changed, 8 insertions(+), 3 deletions(-)
-
-diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs
-index 6a72552a..5007d677 100644
---- a/openssl/src/bio.rs
-+++ b/openssl/src/bio.rs
-@@ -7,6 +7,11 @@ use std::slice;
- use crate::cvt_p;
- use crate::error::ErrorStack;
- 
-+#[cfg(boringssl)]
-+type SignedLenType = libc::ssize_t;
-+#[cfg(not(boringssl))]
-+type SignedLenType = libc::c_int;
-+
- pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>);
- 
- impl<'a> Drop for MemBioSlice<'a> {
-@@ -25,7 +30,7 @@ impl<'a> MemBioSlice<'a> {
-         let bio = unsafe {
-             cvt_p(BIO_new_mem_buf(
-                 buf.as_ptr() as *const _,
--                buf.len() as c_int,
-+                buf.len() as SignedLenType,
-             ))?
-         };
- 
-@@ -78,7 +83,7 @@ cfg_if! {
-         use ffi::BIO_new_mem_buf;
-     } else {
-         #[allow(bad_style)]
--        unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: ::libc::c_int) -> *mut ffi::BIO {
-+        unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: SignedLenType) -> *mut ffi::BIO {
-             ffi::BIO_new_mem_buf(buf as *mut _, len)
-         }
-     }
-diff --git a/openssl/src/dh.rs b/openssl/src/dh.rs
-index 12170b99..e781543e 100644
---- a/openssl/src/dh.rs
-+++ b/openssl/src/dh.rs
-@@ -239,7 +239,7 @@ where
- }
- 
- cfg_if! {
--    if #[cfg(any(ossl110, libressl270))] {
-+    if #[cfg(any(ossl110, libressl270, boringssl))] {
-         use ffi::{DH_set0_pqg, DH_get0_pqg, DH_get0_key, DH_set0_key};
-     } else {
-         #[allow(bad_style)]
--- 
-2.39.1.519.gcb327c4b5f-goog
-
diff --git a/nearby/scripts/openssl-patches/0003-Add-HmacCtx-wrapper-for-BoringSSL.patch b/nearby/scripts/openssl-patches/0003-Add-HmacCtx-wrapper-for-BoringSSL.patch
deleted file mode 100644
index 9c3f3a6..0000000
--- a/nearby/scripts/openssl-patches/0003-Add-HmacCtx-wrapper-for-BoringSSL.patch
+++ /dev/null
@@ -1,183 +0,0 @@
-From 1ff6b4d427e85f290b68e3b9f547ca536360f32b Mon Sep 17 00:00:00 2001
-From: Maurice Lam <yukl@google.com>
-Date: Thu, 2 Feb 2023 19:45:12 +0000
-Subject: [PATCH 3/3] Add HmacCtx wrapper for BoringSSL
-
----
- openssl-sys/src/handwritten/hmac.rs |   2 +-
- openssl/src/hmac.rs                 | 111 ++++++++++++++++++++++++----
- 2 files changed, 96 insertions(+), 17 deletions(-)
-
-diff --git a/openssl-sys/src/handwritten/hmac.rs b/openssl-sys/src/handwritten/hmac.rs
-index 7cbb7cc9..7c0e0b5a 100644
---- a/openssl-sys/src/handwritten/hmac.rs
-+++ b/openssl-sys/src/handwritten/hmac.rs
-@@ -3,7 +3,7 @@ use libc::*;
- use *;
- 
- cfg_if! {
--    if #[cfg(any(ossl110, libressl350))] {
-+    if #[cfg(any(ossl110, libressl350, boringssl))] {
-         extern "C" {
-             pub fn HMAC_CTX_new() -> *mut HMAC_CTX;
-             pub fn HMAC_CTX_free(ctx: *mut HMAC_CTX);
-diff --git a/openssl/src/hmac.rs b/openssl/src/hmac.rs
-index 601ae01b..90fd2175 100644
---- a/openssl/src/hmac.rs
-+++ b/openssl/src/hmac.rs
-@@ -1,10 +1,12 @@
--use crate::cvt_p;
-+use crate::{cvt, cvt_p};
- use crate::error::ErrorStack;
- use crate::md::MdRef;
-+use ffi::HMAC_CTX;
- use foreign_types::ForeignTypeRef;
-+use libc::{c_uint, c_void};
- use openssl_macros::corresponds;
--use libc::{c_void, c_uint};
- use std::convert::TryFrom;
-+use std::ptr;
- 
- /// Computes the HMAC as a one-shot operation.
- ///
-@@ -20,7 +22,7 @@ pub fn hmac<'a>(
-     md: &MdRef,
-     key: &[u8],
-     data: &[u8],
--    out: &'a mut [u8]
-+    out: &'a mut [u8],
- ) -> Result<&'a [u8], ErrorStack> {
-     let mut out_len = c_uint::try_from(out.len()).unwrap();
-     unsafe {
-@@ -31,38 +33,115 @@ pub fn hmac<'a>(
-             data.as_ptr(),
-             data.len(),
-             out.as_mut_ptr(),
--            &mut out_len
--            ))?;
-+            &mut out_len,
-+        ))?;
-     }
-     Ok(&out[..out_len as usize])
- }
- 
-+/// Only available in boringssl. For openssl, use `PKey::hmac` instead.
-+#[cfg(boringssl)]
-+pub struct HmacCtx {
-+    ctx: *mut HMAC_CTX,
-+}
-+
-+#[cfg(boringssl)]
-+impl HmacCtx {
-+    #[corresponds(HMAC_CTX_new)]
-+    pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> {
-+        unsafe {
-+            let ctx = cvt_p(ffi::HMAC_CTX_new())?;
-+            cvt(ffi::HMAC_Init_ex(
-+                ctx,
-+                key.as_ptr() as *const c_void,
-+                key.len(),
-+                md.as_ptr(),
-+                ptr::null_mut(),
-+            ))?;
-+            Ok(Self { ctx })
-+        }
-+    }
-+
-+    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
-+        unsafe { cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ()) }
-+    }
-+
-+    /// Finishes the HMAC process, writing any remaining data to `output`.
-+    /// The number of bytes written to `output` is returned.
-+    /// `update` should not be called after this method.
-+    pub fn finalize(&mut self, md: &mut [u8]) -> Result<usize, ErrorStack> {
-+        unsafe {
-+            let mut size: c_uint = 0;
-+            cvt(ffi::HMAC_Final(
-+                self.ctx,
-+                md.as_mut_ptr(),
-+                &mut size as *mut c_uint,
-+            ))
-+            .map(|_| size as usize)
-+        }
-+    }
-+}
-+
-+impl Drop for HmacCtx {
-+    #[corresponds(HMAC_CTX_free)]
-+    fn drop(&mut self) {
-+        unsafe {
-+            ffi::HMAC_CTX_free(self.ctx);
-+        }
-+    }
-+}
-+
- #[cfg(test)]
- mod tests {
-     use super::*;
-     use crate::md::Md;
--    use crate::memcmp;
- 
--    const SHA_256_DIGEST_SIZE:usize = 32;
-+    const SHA_256_DIGEST_SIZE: usize = 32;
- 
-     #[test]
-     fn hmac_sha256_test() {
--        let expected_hmac = [0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7];
-+        let expected_hmac = [
-+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+            0x2e, 0x32, 0xcf, 0xf7,
-+        ];
-         let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
--        let key:[u8; 20] = [0x0b; 20];
-+        let key: [u8; 20] = [0x0b; 20];
-         let data = b"Hi There";
--        let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
--        expect!(memcmp::eq(&hmac_result, &expected_hmac));
-+        let hmac_result =
-+            hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+        assert_eq!(&hmac_result, &expected_hmac);
-     }
- 
-     #[test]
-     fn hmac_sha256_test_big_buffer() {
--        let expected_hmac = [0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7];
-+        let expected_hmac = [
-+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+            0x2e, 0x32, 0xcf, 0xf7,
-+        ];
-         let mut out: [u8; 100] = [0; 100];
--        let key:[u8;20] = [0x0b; 20];
-+        let key: [u8; 20] = [0x0b; 20];
-+        let data = b"Hi There";
-+        let hmac_result =
-+            hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+        assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
-+        assert_eq!(&hmac_result, &expected_hmac);
-+    }
-+
-+    #[test]
-+    fn hmac_sha256_update_test() {
-+        let expected_hmac = [
-+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+            0x2e, 0x32, 0xcf, 0xf7,
-+        ];
-+        let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
-+        let key: [u8; 20] = [0x0b; 20];
-         let data = b"Hi There";
--        let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
--        expect_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
--        expect!(memcmp::eq(&hmac_result, &expected_hmac));
-+        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
-+        hmac_ctx.update(data).unwrap();
-+        hmac_ctx.finalize(&mut out).unwrap();
-+        assert_eq!(&out, &expected_hmac);
-     }
- }
--- 
-2.39.1.519.gcb327c4b5f-goog
-
diff --git a/nearby/scripts/openssl-patches/README.md b/nearby/scripts/openssl-patches/README.md
index da6f7d0..8483cad 100644
--- a/nearby/scripts/openssl-patches/README.md
+++ b/nearby/scripts/openssl-patches/README.md
@@ -1,10 +1,38 @@
 This directory contains patch files for `rust-openssl` for it to build successfully with
 `--features=unstable_boringssl`.
 
-After running `prepare-boringssl.sh`, the `rust-openssl` git repo is cloned to
+After running `prepare_boringssl`, the `rust-openssl` git repo is cloned to
 `beto-rust/boringssl-build/rust-openssl/openssl`, and the patches in this directory will be applied.
 
-If you make further changes, or update the "base commit" in `prepare-boringssl.sh`, you can
-regenerate the patch files by checking out to the desired state of the tree, with all changes
-committed, and run `git format-patch BASE_COMMIT`. (Note: `BASE_COMMIT` is set by
-`prepare-boringssl.sh`)
+If you make further changes, or update the "base commit" in `prepare_boringssl`, you can
+regenerate the patch files by following these steps:
+
+1. Run `(source nearby/scripts/build-script.sh && prepare_boringssl)`
+2. `cd boringssl-build/rust-openssl/` and make the necessary changes
+3. Commit the changes
+4. `git format-patch BASE_COMMIT`. (Note: `BASE_COMMIT` is set by `prepare_boringssl`)
+5. The patch files will be generated in the current working directory. Move them here in
+   `nearby/scripts/openssl-patches`.
+
+### Regenerate patches based on AOSP changes
+
+In the "make the necessary changes" part in Step 2 above, follow these steps:
+
+1. Download the patch files in https://googleplex-android.googlesource.com/platform/external/rust/crates/openssl/+/master/patches
+2. `cd` into the openssl directory since the AOSP project starts at that root:
+   ```sh
+   $ cd openssl
+   ```
+3. Reset your branch to `BASE_COMMIT` to ensure the AOSP patches apply cleanly.
+   ```sh
+   $ git co BASE_COMMIT
+   $ git co -b create-patch
+   ```
+4. Apply the patches from AOSP
+   ```sh
+   for i in /path/to/android/external/rust/crates/openssl/patches/*; do patch -p1 < $i; done
+   ```
+5. Remove unneeded files (like `.orig`). Commit the changes.
+6. Patches locally in `scripts/openssl-patches` but not in AOSP are lost in this process. Reapply
+   the appropriate ones at this point, using `git apply` or `git am`.
+7. Continue with `git format-patch` described in step 4 in the previous section.