Project import generated by Copybara.

GitOrigin-RevId: 06b0c815d958bef0a60978d0766d65af839e37b6
Change-Id: Ifac0c45ae509ca8a8ed793fac5c981d4e23d3bf9
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/nearby/crypto/bssl-crypto/Cargo.toml
deleted file mode 100644
index bfe3964..0000000
--- a/nearby/crypto/bssl-crypto/Cargo.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[package]
-name = "bssl-crypto"
-version.workspace = true
-edition.workspace = true
-publish.workspace = true
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml
index 60f8626..e7dd8ba 100644
--- a/nearby/crypto/crypto_provider/Cargo.toml
+++ b/nearby/crypto/crypto_provider/Cargo.toml
@@ -4,11 +4,14 @@
 edition.workspace = true
 publish.workspace = true
 
+[dependencies]
+tinyvec.workspace = true
+
 [dev-dependencies]
 criterion.workspace = true
 hex-literal.workspace = true
 crypto_provider_openssl.workspace = true
-crypto_provider_rustcrypto.workspace = true
+crypto_provider_rustcrypto = { workspace = true, features = ["std"] }
 rand_ext.workspace = true
 rand.workspace = true
 
@@ -17,6 +20,7 @@
 std = []
 alloc = []
 test_vectors = []
+raw_private_key_permit = []
 
 [[bench]]
 name = "hmac_bench"
diff --git a/nearby/crypto/crypto_provider/src/aead.rs b/nearby/crypto/crypto_provider/src/aead.rs
new file mode 100644
index 0000000..165272e
--- /dev/null
+++ b/nearby/crypto/crypto_provider/src/aead.rs
@@ -0,0 +1,86 @@
+// 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.
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+/// An implementation of AES-GCM-SIV.
+///
+/// An AesGcmSiv impl may be used for encryption and decryption.
+pub trait AesGcmSiv: Aead<Nonce = [u8; 12]> {}
+
+/// An implementation of AES-GCM.
+///
+/// An AesGcm impl may be used for encryption and decryption.
+pub trait AesGcm: Aead<Nonce = [u8; 12]> {}
+
+/// Error returned on unsuccessful AEAD operation.
+#[derive(Debug)]
+pub struct AeadError;
+
+/// Initializes an AEAD
+pub trait AeadInit<K: crate::aes::AesKey> {
+    /// Instantiates a new instance of the AEAD from key material.
+    fn new(key: &K) -> Self;
+}
+
+/// Authenticated Encryption with Associated Data (AEAD) algorithm, where `N` is the size of the
+/// Nonce. Encrypts and decrypts buffers in-place.
+pub trait Aead {
+    /// The size of the authentication tag, this is appended to the message on the encrypt operation
+    /// and truncated from the plaintext after decrypting.
+    const TAG_SIZE: usize;
+
+    /// The cryptographic nonce used by the AEAD. The nonce must be unique for all messages with
+    /// the same key. This is critically important - nonce reuse may completely undermine the
+    /// security of the AEAD. Nonces may be predictable and public, so long as they are unique.
+    type Nonce: AsRef<[u8]>;
+
+    /// The type of the tag, which should always be [u8; Self::TAG_SIZE].
+    type Tag: AsRef<[u8]>;
+
+    /// Encrypt the given buffer containing a plaintext message. On success returns the encrypted
+    /// `msg` and appended auth tag, which will result in a Vec which is  `Self::TAG_SIZE` bytes
+    /// greater than the initial message.
+    #[cfg(feature = "alloc")]
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError>;
+
+    /// Encrypt the given buffer containing a plaintext message in-place, and returns the tag in the
+    /// result value.
+    fn encrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError>;
+
+    /// Decrypt the message, returning the decrypted plaintext or an error in the event the
+    /// provided authentication tag does not match the given ciphertext. On success the returned
+    /// Vec will only contain the plaintext and so will be `Self::TAG_SIZE` bytes less than the
+    /// initial message.
+    #[cfg(feature = "alloc")]
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError>;
+
+    /// Decrypt the message in-place, returning an error and leaving the input `msg` unchanged in
+    /// the event the provided authentication tag does not match the given ciphertext.
+    fn decrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+        tag: &Self::Tag,
+    ) -> Result<(), AeadError>;
+}
diff --git a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs
deleted file mode 100644
index 3be7db3..0000000
--- a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs
+++ /dev/null
@@ -1,23 +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.
-
-//! Traits for AES-GCM-SIV.
-
-extern crate alloc;
-use crate::aead::Aead;
-
-/// An implementation of AES-GCM-SIV.
-///
-/// An AesGcmSiv impl may be used for encryption and decryption.
-pub trait AesGcmSiv: Aead<Nonce = [u8; 12]> {}
diff --git a/nearby/crypto/crypto_provider/src/aead/mod.rs b/nearby/crypto/crypto_provider/src/aead/mod.rs
deleted file mode 100644
index 27284a9..0000000
--- a/nearby/crypto/crypto_provider/src/aead/mod.rs
+++ /dev/null
@@ -1,50 +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.
-
-extern crate alloc;
-use alloc::vec::Vec;
-
-/// Contains traits for the AES-GCM-SIV AEAD algorithm.
-pub mod aes_gcm_siv;
-
-/// Error returned on unsuccessful AEAD operation.
-pub struct AeadError;
-
-/// Authenticated Encryption with Associated Data (AEAD) algorithm, where `N` is the size of the
-/// Nonce. Encrypts and decrypts buffers in-place.
-pub trait Aead {
-    /// The size of the authentication tag, this is appended to the message on the encrypt operation
-    /// and truncated from the plaintext after decrypting.
-    const TAG_SIZE: usize;
-
-    /// The cryptographic nonce used by the AEAD. The nonce must be unique for all messages with
-    /// the same key. This is critically important - nonce reuse may completely undermine the
-    /// security of the AEAD. Nonces may be predictable and public, so long as they are unique.
-    type Nonce;
-
-    /// The key material used to initialize the AEAD.
-    type Key;
-
-    /// Instantiates a new instance of the AEAD from key material.
-    fn new(key: &Self::Key) -> Self;
-
-    /// Encrypt the given buffer containing a plaintext message in-place. On success increases the
-    /// buffer by `Self::TAG_SIZE` bytes and appends the auth tag to the end of `msg`.
-    fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &Self::Nonce) -> Result<(), AeadError>;
-
-    /// Decrypt the message in-place, returning an error in the event the provided authentication
-    /// tag does not match the given ciphertext. The buffer will be truncated to the length of the
-    /// original plaintext message upon success.
-    fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &Self::Nonce) -> Result<(), AeadError>;
-}
diff --git a/nearby/crypto/crypto_provider/src/aes/cbc.rs b/nearby/crypto/crypto_provider/src/aes/cbc.rs
index e32c588..59ce52d 100644
--- a/nearby/crypto/crypto_provider/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider/src/aes/cbc.rs
@@ -14,7 +14,10 @@
 
 //! Traits for AES-CBC 256 with PKCS7 padding.
 
+#[cfg(feature = "alloc")]
 extern crate alloc;
+use crate::tinyvec::SliceVec;
+#[cfg(feature = "alloc")]
 use alloc::vec::Vec;
 
 use super::Aes256Key;
@@ -24,15 +27,53 @@
 
 /// Trait for implementing AES-CBC with PKCS7 padding.
 pub trait AesCbcPkcs7Padded {
+    /// Calculate the padded output length (e.g. output of `encrypt`) from the unpadded length (e.g.
+    /// input message of `encrypt`).
+    fn padded_output_len(unpadded_len: usize) -> usize {
+        (unpadded_len - (unpadded_len % 16))
+            .checked_add(16)
+            .expect("Padded output length is larger than usize::MAX")
+    }
+
     /// Encrypt message using `key` and `iv`, returning a ciphertext.
+    #[cfg(feature = "alloc")]
     fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8>;
+
+    /// Encrypt message using `key` and `iv` in-place in the given `message` vec. The given slice
+    /// vec should have enough capacity to contain both the ciphertext and the padding (which can be
+    /// calculated from `padded_output_len()`). If it doesn't have enough capacity, an error will be
+    /// returned. The contents of the input `message` buffer is undefined in that case.
+    fn encrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        message: &mut SliceVec<u8>,
+    ) -> Result<(), EncryptionError>;
+
     /// Decrypt ciphertext using `key` and `iv`, returning the original message if `Ok()` otherwise
     /// a `DecryptionError` indicating the type of error that occurred while decrypting.
+    #[cfg(feature = "alloc")]
     fn decrypt(
         key: &Aes256Key,
         iv: &AesCbcIv,
         ciphertext: &[u8],
     ) -> Result<Vec<u8>, DecryptionError>;
+
+    /// Decrypt ciphertext using `key` and `iv` and unpad it in-place. Returning the original
+    /// message if `Ok()` otherwise a `DecryptionError` indicating the type of error that occurred
+    /// while decrypting. In that case, the contents of the `ciphertext` buffer is undefined.
+    fn decrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &mut SliceVec<u8>,
+    ) -> Result<(), DecryptionError>;
+}
+
+/// Error type for describing what went wrong encrypting a message.
+#[derive(Debug, PartialEq, Eq)]
+pub enum EncryptionError {
+    /// Failed to add PKCS7 padding to the output when encrypting a message. This typically happens
+    /// when the given output buffer does not have enough capacity to append the padding.
+    PaddingFailed,
 }
 
 /// Error type for describing what went wrong decrypting a ciphertext.
@@ -43,3 +84,65 @@
     /// correctly. Exposing padding errors can cause a padding oracle vulnerability.
     BadPadding,
 }
+
+#[cfg(test)]
+mod test {
+    #[cfg(feature = "alloc")]
+    extern crate alloc;
+    #[cfg(feature = "alloc")]
+    use alloc::vec::Vec;
+
+    use crate::aes::Aes256Key;
+    use crate::tinyvec::SliceVec;
+
+    use super::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError, EncryptionError};
+
+    #[test]
+    fn test_padded_output_len() {
+        assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(0), 16);
+        assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(15), 16);
+        assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(16), 32);
+        assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(30), 32);
+        assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(32), 48);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_padded_output_len_overflow() {
+        AesCbcPkcs7PaddedStub::padded_output_len(usize::MAX);
+    }
+
+    struct AesCbcPkcs7PaddedStub;
+
+    impl AesCbcPkcs7Padded for AesCbcPkcs7PaddedStub {
+        #[cfg(feature = "alloc")]
+        fn encrypt(_key: &Aes256Key, _iv: &AesCbcIv, _message: &[u8]) -> Vec<u8> {
+            unimplemented!()
+        }
+
+        fn encrypt_in_place(
+            _key: &Aes256Key,
+            _iv: &AesCbcIv,
+            _message: &mut SliceVec<u8>,
+        ) -> Result<(), EncryptionError> {
+            unimplemented!()
+        }
+
+        #[cfg(feature = "alloc")]
+        fn decrypt(
+            _key: &Aes256Key,
+            _iv: &AesCbcIv,
+            _ciphertext: &[u8],
+        ) -> Result<Vec<u8>, DecryptionError> {
+            unimplemented!()
+        }
+
+        fn decrypt_in_place(
+            _key: &Aes256Key,
+            _iv: &AesCbcIv,
+            _ciphertext: &mut SliceVec<u8>,
+        ) -> Result<(), DecryptionError> {
+            unimplemented!()
+        }
+    }
+}
diff --git a/nearby/crypto/crypto_provider/src/aes/ctr.rs b/nearby/crypto/crypto_provider/src/aes/ctr.rs
index 73d4f55..6779fe8 100644
--- a/nearby/crypto/crypto_provider/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider/src/aes/ctr.rs
@@ -36,10 +36,8 @@
     /// Build a `Self` from key material.
     fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self;
 
-    /// Encrypt the data in place, advancing the counter state appropriately.
-    fn encrypt(&mut self, data: &mut [u8]);
-    /// Decrypt the data in place, advancing the counter state appropriately.
-    fn decrypt(&mut self, data: &mut [u8]);
+    /// Applies the key stream to the data in place, advancing the counter state appropriately.
+    fn apply_keystream(&mut self, data: &mut [u8]);
 }
 
 /// The combined nonce and counter that CTR increments and encrypts to form the keystream.
diff --git a/nearby/crypto/crypto_provider/src/aes/mod.rs b/nearby/crypto/crypto_provider/src/aes/mod.rs
index 83e48de..e359bad 100644
--- a/nearby/crypto/crypto_provider/src/aes/mod.rs
+++ b/nearby/crypto/crypto_provider/src/aes/mod.rs
@@ -20,7 +20,6 @@
 
 pub mod ctr;
 
-#[cfg(feature = "alloc")]
 pub mod cbc;
 
 /// Block size in bytes for AES (and XTS-AES)
diff --git a/nearby/crypto/crypto_provider/src/ed25519.rs b/nearby/crypto/crypto_provider/src/ed25519.rs
index 48c2c50..b435ca2 100644
--- a/nearby/crypto/crypto_provider/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider/src/ed25519.rs
@@ -43,6 +43,59 @@
 /// A byte buffer the size of a ed25519 `PrivateKey`.
 pub type RawPrivateKey = [u8; PRIVATE_KEY_LENGTH];
 
+/// A permission token which may be supplied to methods which allow
+/// converting private keys to/from raw bytes.
+///
+/// In general, operations of this kind should only be done in
+/// development-tools, tests, or in credential storage layers
+/// to prevent accidental exposure of the private key.
+pub struct RawPrivateKeyPermit {
+    _marker: (),
+}
+
+impl RawPrivateKeyPermit {
+    pub(crate) fn new() -> Self {
+        Self { _marker: () }
+    }
+}
+
+#[cfg(feature = "raw_private_key_permit")]
+impl core::default::Default for RawPrivateKeyPermit {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// A crypto-provider-independent representation of the private
+/// key of an ed25519 key-pair, kept in such a way that
+/// it does not permit de-structuring it into raw bytes,
+/// nor constructing one from raw bytes.
+///
+/// Useful for when you want a data-structure to be
+/// crypto-provider independent and contain a private key.
+#[derive(Clone)]
+pub struct PrivateKey {
+    wrapped: RawPrivateKey,
+}
+
+impl PrivateKey {
+    /// Derives the public key corresponding to this private key.
+    pub fn derive_public_key<E: Ed25519Provider>(&self) -> E::PublicKey {
+        let key_pair = E::KeyPair::from_private_key(self);
+        key_pair.public()
+    }
+    /// Returns the raw bytes of this private key.
+    /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+    pub fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
+        self.wrapped
+    }
+    /// Constructs a private key from the raw bytes of the key.
+    /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+    pub fn from_raw_private_key(wrapped: RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self {
+        Self { wrapped }
+    }
+}
+
 /// The keypair which includes both public and secret halves of an asymmetric key.
 pub trait KeyPair: Sized {
     /// The ed25519 public key, used when verifying a message
@@ -52,15 +105,37 @@
     type Signature: Signature;
 
     /// Returns the private key bytes of the `KeyPair`.
-    /// This method should only ever be called by code which securely stores private credentials.
-    fn private_key(&self) -> RawPrivateKey;
+    /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+    fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey;
 
     /// Builds a key-pair from a `RawPrivateKey` array of bytes.
-    /// This should only ever be called by code which securely stores private credentials.
-    fn from_private_key(bytes: &RawPrivateKey) -> Self
+    /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+    fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self
     where
         Self: Sized;
 
+    /// Returns the private key of the `KeyPair` in an opaque form.
+    fn private_key(&self) -> PrivateKey {
+        // We're okay to reach in and grab the bytes of the private key,
+        // since the way that we're exposing it would require a valid
+        // [`RawPrivateKeyPermit`] to extract them again.
+        let wrapped = self.raw_private_key(&RawPrivateKeyPermit::new());
+        PrivateKey { wrapped }
+    }
+
+    /// Builds a key-pair from a [`PrivateKey`], given in an opaque form.
+    fn from_private_key(private_key: &PrivateKey) -> Self
+    where
+        Self: Sized,
+    {
+        // We're okay to reach in and construct an instance from
+        // the bytes of the private key, since the way that they
+        // were originally expressed would still require a valid
+        // [`RawPrivateKeyPermit`] to access them.
+        let raw_private_key = &private_key.wrapped;
+        Self::from_raw_private_key(raw_private_key, &RawPrivateKeyPermit::new())
+    }
+
     /// Sign the given message and return a digital signature
     fn sign(&self, msg: &[u8]) -> Self::Signature;
 
diff --git a/nearby/crypto/crypto_provider/src/elliptic_curve.rs b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
index d06a21d..d176769 100644
--- a/nearby/crypto/crypto_provider/src/elliptic_curve.rs
+++ b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
@@ -12,12 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-extern crate alloc;
-
 use core::fmt::Debug;
 
-use alloc::vec::Vec;
-
 /// Marker trait for an elliptic curve used for diffie-hellman.
 pub trait Curve {}
 
@@ -42,12 +38,16 @@
     /// The random number generator to be used for generating a secret
     type Rng: crate::CryptoRng;
 
+    /// The for encoded public key bytes, for example a `[u8; N]` array if the size is fixed, or
+    /// `ArrayVec<[u8; N]>` if the size is bounded but not fixed.
+    type EncodedPublicKey: AsRef<[u8]> + Debug;
+
     /// Generates a new random ephemeral secret.
     fn generate_random(rng: &mut Self::Rng) -> Self;
 
     /// Returns the bytes of the public key for this ephemeral secret that is suitable for sending
     /// over the wire for key exchange.
-    fn public_key_bytes(&self) -> Vec<u8>;
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey;
 
     /// Performs diffie-hellman key exchange using this ephemeral secret with the given public key
     /// `other_pub`.
@@ -59,6 +59,8 @@
 
 /// Trait for a public key used for elliptic curve diffie hellman.
 pub trait PublicKey<E: Curve>: Sized + PartialEq + Debug {
+    /// The type for an encoded public key.
+    type EncodedPublicKey: AsRef<[u8]> + Debug;
     /// The error type associated with Public Key.
     type Error: Debug;
 
@@ -71,5 +73,5 @@
     /// the sec1 encoding, may return equivalent but different byte-representations due to point
     /// compression, so it is not necessarily true that `from_bytes(bytes)?.to_bytes() == bytes`
     /// (but it is always true that `from_bytes(key.to_bytes())? == key`).
-    fn to_bytes(&self) -> Vec<u8>;
+    fn to_bytes(&self) -> Self::EncodedPublicKey;
 }
diff --git a/nearby/crypto/crypto_provider/src/lib.rs b/nearby/crypto/crypto_provider/src/lib.rs
index 624072a..0031ebb 100644
--- a/nearby/crypto/crypto_provider/src/lib.rs
+++ b/nearby/crypto/crypto_provider/src/lib.rs
@@ -48,6 +48,8 @@
 /// mod containing traits for ed25519 key generation, signing, and verification
 pub mod ed25519;
 
+pub use tinyvec;
+
 /// Uber crypto trait which defines the traits for all crypto primitives as associated types
 pub trait CryptoProvider: Clone + Debug + PartialEq + Eq + Send {
     /// The Hkdf type which implements the hkdf trait
@@ -80,10 +82,13 @@
     /// using SHA-512 (SHA-2) and Curve25519
     type Ed25519: ed25519::Ed25519Provider;
     /// The trait defining AES-128-GCM-SIV, a nonce-misuse resistant AEAD with a key size of 16 bytes.
-    type Aes128GcmSiv: aead::aes_gcm_siv::AesGcmSiv<Key = Aes128Key>;
+    type Aes128GcmSiv: aead::AesGcmSiv + aead::AeadInit<Aes128Key>;
     /// The trait defining AES-256-GCM-SIV, a nonce-misuse resistant AEAD with a key size of 32 bytes.
-    type Aes256GcmSiv: aead::aes_gcm_siv::AesGcmSiv<Key = Aes256Key>;
-
+    type Aes256GcmSiv: aead::AesGcmSiv + aead::AeadInit<Aes256Key>;
+    /// The trait defining AES-128-GCM, an AEAD with a key size of 16 bytes.
+    type Aes128Gcm: aead::AesGcm + aead::AeadInit<Aes128Key>;
+    /// The trait defining AES-256-GCM, an AEAD with a key size of 32 bytes.
+    type Aes256Gcm: aead::AesGcm + aead::AeadInit<Aes256Key>;
     /// The cryptographically secure random number generator
     type CryptoRng: CryptoRng;
 
diff --git a/nearby/crypto/crypto_provider/src/p256.rs b/nearby/crypto/crypto_provider/src/p256.rs
index 90d3542..fd7b531 100644
--- a/nearby/crypto/crypto_provider/src/p256.rs
+++ b/nearby/crypto/crypto_provider/src/p256.rs
@@ -12,10 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-extern crate alloc;
+use tinyvec::ArrayVec;
 
 use crate::elliptic_curve::{Curve, EcdhProvider, PublicKey};
-use alloc::vec::Vec;
 use core::fmt::Debug;
 
 /// Marker type for P256 implementation. This is used by EcdhProvider as its type parameter.
@@ -23,6 +22,24 @@
 pub enum P256 {}
 impl Curve for P256 {}
 
+/// Longest length for a sec-1 encoded P256 public key, which is the uncompressed format
+/// `04 || X || Y` as defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography")
+/// standard.
+const P256_PUBLIC_KEY_MAX_LENGTH: usize = 65;
+
+/// Whether an elliptic curve point should be compressed or not.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PointCompression {
+    /// The elliptic curve point should be compressed (`02 || X` or `03 || X`),
+    /// as defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve
+    /// Cryptography").
+    Compressed,
+    /// The elliptic curve point should be uncompressed (`04 || X || Y`), as
+    /// defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve
+    /// Cryptography").
+    Uncompressed,
+}
+
 /// Trait for a NIST-P256 public key.
 pub trait P256PublicKey: Sized + PartialEq + Debug {
     /// The error type associated with this implementation.
@@ -36,7 +53,10 @@
     /// ("Elliptic Curve Cryptography") standard. Note that it is not necessarily true that
     /// `from_sec1_bytes(bytes)?.to_sec1_bytes() == bytes` because of point compression. (But it is
     /// always true that `from_sec1_bytes(key.to_sec1_bytes())? == key`).
-    fn to_sec1_bytes(&self) -> Vec<u8>;
+    fn to_sec1_bytes(
+        &self,
+        point_compression: PointCompression,
+    ) -> ArrayVec<[u8; P256_PUBLIC_KEY_MAX_LENGTH]>;
 
     /// Converts this public key's x and y coordinates on the elliptic curve to big endian octet
     /// strings.
@@ -48,13 +68,14 @@
 
 impl<P: P256PublicKey> PublicKey<P256> for P {
     type Error = <Self as P256PublicKey>::Error;
+    type EncodedPublicKey = ArrayVec<[u8; P256_PUBLIC_KEY_MAX_LENGTH]>;
 
     fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
         Self::from_sec1_bytes(bytes)
     }
 
-    fn to_bytes(&self) -> Vec<u8> {
-        Self::to_sec1_bytes(self)
+    fn to_bytes(&self) -> Self::EncodedPublicKey {
+        Self::to_sec1_bytes(self, PointCompression::Uncompressed)
     }
 }
 
diff --git a/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml b/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml
deleted file mode 100644
index f5ab7fa..0000000
--- a/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-paths = [
-    "../../../boringssl-build/boringssl/rust/bssl-crypto",
-]
\ No newline at end of file
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.lock b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
index 14402c1..7773e56 100644
--- a/nearby/crypto/crypto_provider_boringssl/Cargo.lock
+++ b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
@@ -4,13 +4,20 @@
 
 [[package]]
 name = "base64"
-version = "0.13.1"
+version = "0.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
 
 [[package]]
 name = "bssl-crypto"
 version = "0.1.0"
+dependencies = [
+ "bssl-sys",
+]
+
+[[package]]
+name = "bssl-sys"
+version = "0.1.0"
 
 [[package]]
 name = "cfg-if"
@@ -21,6 +28,9 @@
 [[package]]
 name = "crypto_provider"
 version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
 
 [[package]]
 name = "crypto_provider_boringssl"
@@ -28,18 +38,10 @@
 dependencies = [
  "bssl-crypto",
  "crypto_provider",
- "crypto_provider_stubs",
  "crypto_provider_test",
 ]
 
 [[package]]
-name = "crypto_provider_stubs"
-version = "0.1.0"
-dependencies = [
- "crypto_provider",
-]
-
-[[package]]
 name = "crypto_provider_test"
 version = "0.1.0"
 dependencies = [
@@ -56,9 +58,9 @@
 
 [[package]]
 name = "getrandom"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
  "cfg-if",
  "libc",
@@ -79,24 +81,21 @@
 
 [[package]]
 name = "itoa"
-version = "1.0.6"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "libc"
-version = "0.2.144"
+version = "0.2.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
 
 [[package]]
 name = "log"
-version = "0.4.17"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
 name = "ppv-lite86"
@@ -106,18 +105,18 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.27"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
  "proc-macro2",
 ]
@@ -197,14 +196,14 @@
 
 [[package]]
 name = "rstest_reuse"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073"
+checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4"
 dependencies = [
  "quote",
  "rand",
  "rustc_version",
- "syn 1.0.109",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -218,41 +217,41 @@
 
 [[package]]
 name = "ryu"
-version = "1.0.13"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "semver"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
 
 [[package]]
 name = "serde"
-version = "1.0.162"
+version = "1.0.187"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
+checksum = "30a7fe14252655bd1e578af19f5fa00fe02fd0013b100ca6b49fde31c41bae4c"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.162"
+version = "1.0.187"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
+checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn 2.0.29",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.96"
+version = "1.0.105"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
 dependencies = [
  "itoa",
  "ryu",
@@ -272,9 +271,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.15"
+version = "2.0.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -290,10 +289,16 @@
 ]
 
 [[package]]
-name = "unicode-ident"
-version = "1.0.8"
+name = "tinyvec"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
 
 [[package]]
 name = "wasi"
@@ -303,9 +308,9 @@
 
 [[package]]
 name = "wycheproof"
-version = "0.4.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "183c789620c674b79dac33cd3aadb6c8006b66cba6a680402235aaebc743e3df"
+checksum = "e639f57253b80c6584b378011aec0fed61c4c21d7a4b97c4d9d7eaf35ca77d12"
 dependencies = [
  "base64",
  "hex",
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.toml b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
index 24ce66c..fb3bfdf 100644
--- a/nearby/crypto/crypto_provider_boringssl/Cargo.toml
+++ b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
@@ -6,10 +6,9 @@
 
 [dependencies]
 crypto_provider = { path = "../crypto_provider", features = ["alloc", "std"] }
-crypto_provider_stubs = { path = "../crypto_provider_stubs" }
 
-# Note: before this crate will work you need to run `scripts/prepare-boringssl.sh`
-bssl-crypto = {path = "../bssl-crypto"}
+# Note: before this crate will work you need to run `cargo run -p build_scripts -- build-boringssl`
+bssl-crypto = {path = "../../../third_party/boringssl/rust/bssl-crypto"}
 
 [dev-dependencies]
 crypto_provider_test = {path = "../crypto_provider_test"}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs
new file mode 100644
index 0000000..03bb087
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs
@@ -0,0 +1,89 @@
+// 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::vec::Vec;
+use bssl_crypto::aead::Aead as _;
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
+use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey};
+
+pub struct AesGcm(bssl_crypto::aead::AesGcm);
+
+impl AeadInit<Aes128Key> for AesGcm {
+    fn new(key: &Aes128Key) -> Self {
+        Self(bssl_crypto::aead::new_aes_128_gcm(key.as_array()))
+    }
+}
+
+impl AeadInit<Aes256Key> for AesGcm {
+    fn new(key: &Aes256Key) -> Self {
+        Self(bssl_crypto::aead::new_aes_256_gcm(key.as_array()))
+    }
+}
+
+impl crypto_provider::aead::AesGcm for AesGcm {}
+
+impl Aead for AesGcm {
+    const TAG_SIZE: usize = 16;
+    type Nonce = [u8; 12];
+    type Tag = [u8; 16];
+
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0.encrypt(msg, aad, nonce).map_err(|_| AeadError)
+    }
+
+    fn encrypt_detached(
+        &self,
+        _msg: &mut [u8],
+        _aad: &[u8],
+        _nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError> {
+        unimplemented!("Not yet supported by boringssl")
+    }
+
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0.decrypt(msg, aad, nonce).map_err(|_| AeadError)
+    }
+
+    fn decrypt_detached(
+        &self,
+        _msg: &mut [u8],
+        _aad: &[u8],
+        _nonce: &Self::Nonce,
+        _tag: &Self::Tag,
+    ) -> Result<(), AeadError> {
+        unimplemented!("Not yet supported by boringssl")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use core::marker::PhantomData;
+
+    use crypto_provider_test::aead::aes_gcm::*;
+    use crypto_provider_test::aes::*;
+
+    use super::*;
+
+    #[apply(aes_128_gcm_test_cases)]
+    fn aes_gcm_128_test(testcase: CryptoProviderTestCase<AesGcm>) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_256_gcm_test_cases)]
+    fn aes_gcm_256_test(testcase: CryptoProviderTestCase<AesGcm>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs
new file mode 100644
index 0000000..d1fb0e7
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs
@@ -0,0 +1,88 @@
+// 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::vec::Vec;
+use bssl_crypto::aead::Aead as _;
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
+use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey};
+
+pub struct AesGcmSiv(bssl_crypto::aead::AesGcmSiv);
+
+impl AeadInit<Aes128Key> for AesGcmSiv {
+    fn new(key: &Aes128Key) -> Self {
+        Self(bssl_crypto::aead::new_aes_128_gcm_siv(key.as_array()))
+    }
+}
+
+impl AeadInit<Aes256Key> for AesGcmSiv {
+    fn new(key: &Aes256Key) -> Self {
+        Self(bssl_crypto::aead::new_aes_256_gcm_siv(key.as_array()))
+    }
+}
+
+impl crypto_provider::aead::AesGcmSiv for AesGcmSiv {}
+
+impl Aead for AesGcmSiv {
+    const TAG_SIZE: usize = 16;
+    type Nonce = [u8; 12];
+    type Tag = [u8; 16];
+
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0.encrypt(msg, aad, nonce).map_err(|_| AeadError)
+    }
+
+    fn encrypt_detached(
+        &self,
+        _msg: &mut [u8],
+        _aad: &[u8],
+        _nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError> {
+        unimplemented!("Not yet supported by boringssl")
+    }
+
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0.decrypt(msg, aad, nonce).map_err(|_| AeadError)
+    }
+
+    fn decrypt_detached(
+        &self,
+        _msg: &mut [u8],
+        _aad: &[u8],
+        _nonce: &Self::Nonce,
+        _tag: &Self::Tag,
+    ) -> Result<(), AeadError> {
+        unimplemented!("Not yet supported by boringssl")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use core::marker::PhantomData;
+
+    use crypto_provider_test::aead::aes_gcm_siv::*;
+    use crypto_provider_test::aes::*;
+
+    use super::*;
+
+    #[apply(aes_128_gcm_siv_test_cases)]
+    fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv>) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_256_gcm_siv_test_cases)]
+    fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
similarity index 73%
rename from nearby/crypto/bssl-crypto/src/lib.rs
rename to nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
index 89e6968..424f16e 100644
--- a/nearby/crypto/bssl-crypto/src/lib.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
@@ -4,7 +4,7 @@
 // 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
+//      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,
@@ -12,5 +12,5 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the
-//! `build-boringssl` subcommand of the top level crate.
+pub(crate) mod aes_gcm;
+pub(crate) mod aes_gcm_siv;
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs
new file mode 100644
index 0000000..4d3fd3f
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs
@@ -0,0 +1,81 @@
+// 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::vec::Vec;
+use bssl_crypto::cipher::BlockCipher;
+use crypto_provider::{
+    aes::{
+        cbc::{AesCbcIv, DecryptionError, EncryptionError},
+        Aes256Key, AesKey,
+    },
+    tinyvec::SliceVec,
+};
+
+/// BoringSSL implementation of AES-CBC with PKCS7 padding
+pub enum AesCbcPkcs7Padded {}
+impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded {
+    #[allow(clippy::expect_used)]
+    fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
+        let encryptor = bssl_crypto::cipher::aes_cbc::Aes256Cbc::new_encrypt(key.as_array(), iv);
+        encryptor.encrypt_padded(message).expect("Encrypting AES-CBC should be infallible")
+    }
+
+    fn encrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        message: &mut SliceVec<u8>,
+    ) -> Result<(), EncryptionError> {
+        let result = Self::encrypt(key, iv, message);
+        if message.capacity() < result.len() {
+            return Err(EncryptionError::PaddingFailed);
+        }
+        message.clear();
+        message.extend_from_slice(&result);
+        Ok(())
+    }
+
+    fn decrypt(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &[u8],
+    ) -> Result<Vec<u8>, DecryptionError> {
+        let decryptor = bssl_crypto::cipher::aes_cbc::Aes256Cbc::new_decrypt(key.as_array(), iv);
+        decryptor.decrypt_padded(ciphertext).map_err(|_| DecryptionError::BadPadding)
+    }
+
+    fn decrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &mut SliceVec<u8>,
+    ) -> Result<(), DecryptionError> {
+        Self::decrypt(key, iv, ciphertext).map(|result| {
+            ciphertext.clear();
+            ciphertext.extend_from_slice(&result);
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::AesCbcPkcs7Padded;
+    use core::marker::PhantomData;
+    use crypto_provider_test::aes::cbc::*;
+
+    #[apply(aes_256_cbc_test_cases)]
+    fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs
new file mode 100644
index 0000000..c13ab31
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs
@@ -0,0 +1,74 @@
+// 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 bssl_crypto::cipher::StreamCipher;
+use crypto_provider::aes::{ctr::NonceAndCounter, Aes128Key, Aes256Key, AesKey};
+
+/// BoringSSL implementation of AES-CTR 128.
+pub struct AesCtr128(bssl_crypto::cipher::aes_ctr::Aes128Ctr);
+
+impl crypto_provider::aes::ctr::AesCtr for AesCtr128 {
+    type Key = Aes128Key;
+
+    fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self {
+        Self(bssl_crypto::cipher::aes_ctr::Aes128Ctr::new(
+            key.as_array(),
+            &nonce_and_counter.as_block_array(),
+        ))
+    }
+
+    #[allow(clippy::expect_used)]
+    fn apply_keystream(&mut self, data: &mut [u8]) {
+        assert!(data.len() < i32::MAX as usize);
+        self.0.apply_keystream(data).expect("Data length should fit inside of a i32")
+    }
+}
+
+/// BoringSSL implementation of AES-CTR 256.
+pub struct AesCtr256(bssl_crypto::cipher::aes_ctr::Aes256Ctr);
+
+impl crypto_provider::aes::ctr::AesCtr for AesCtr256 {
+    type Key = Aes256Key;
+
+    fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self {
+        Self(bssl_crypto::cipher::aes_ctr::Aes256Ctr::new(
+            key.as_array(),
+            &nonce_and_counter.as_block_array(),
+        ))
+    }
+
+    #[allow(clippy::expect_used)]
+    fn apply_keystream(&mut self, data: &mut [u8]) {
+        assert!(data.len() < i32::MAX as usize);
+        self.0.apply_keystream(data).expect("Data length should fit inside of a i32")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use core::marker::PhantomData;
+    use crypto_provider_test::aes::ctr::*;
+    use crypto_provider_test::aes::*;
+
+    #[apply(aes_128_ctr_test_cases)]
+    fn aes_128_ctr_test(testcase: CryptoProviderTestCase<AesCtr128>) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_256_ctr_test_cases)]
+    fn aes_256_ctr_test(testcase: CryptoProviderTestCase<AesCtr256>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
similarity index 97%
rename from nearby/crypto/crypto_provider_boringssl/src/aes.rs
rename to nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
index 88bfeac..d5cbe58 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
@@ -17,6 +17,12 @@
     Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey,
 };
 
+/// AES_CTR implementations.
+pub mod ctr;
+
+/// AES_CBC implementations.
+pub mod cbc;
+
 /// BoringSSL AES-128 operations
 pub struct Aes128;
 impl Aes for Aes128 {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs b/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
index 7c671ae..b5c374a 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
@@ -13,7 +13,8 @@
 // limitations under the License.
 
 use crypto_provider::ed25519::{
-    InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError,
+    InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _,
+    SignatureError,
 };
 
 pub struct Ed25519;
@@ -30,11 +31,11 @@
     type PublicKey = PublicKey;
     type Signature = Signature;
 
-    fn private_key(&self) -> RawPrivateKey {
+    fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
         self.0.to_seed()
     }
 
-    fn from_private_key(bytes: &RawPrivateKey) -> Self
+    fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self
     where
         Self: Sized,
     {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/lib.rs b/nearby/crypto/crypto_provider_boringssl/src/lib.rs
index 3b78898..75758b6 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/lib.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/lib.rs
@@ -21,14 +21,12 @@
     clippy::expect_used
 )]
 
-//! Crate which provides impls for CryptoProvider backed by BoringSSL.
-
-use bssl_crypto::digest::{Sha256, Sha512};
+//! Crate which provides impls for CryptoProvider backed by BoringSSL
+//!
 use bssl_crypto::rand::rand_bytes;
 use crypto_provider::{CryptoProvider, CryptoRng};
-use crypto_provider_stubs::*;
 
-/// Implementation of `crypto_provider::aes` types using BoringSSL.
+/// Implementation of `crypto_provider::aes` types using BoringSSL
 pub mod aes;
 
 /// Implementations of crypto_provider::hkdf traits backed by BoringSSL
@@ -40,31 +38,45 @@
 /// Implementations of crypto_provider::ed25519 traits backed by BoringSSL
 mod ed25519;
 
+/// Implementations of crypto_provider::aead traits backed by BoringSSL
+mod aead;
+
+/// Implementations of crypto_provider::p256 traits backed by BoringSSL
+mod p256;
+
+/// Implementations of crypto_provider::x25519 traits backed by BoringSSL
+mod x25519;
+
+/// Implementations of crypto_provider::sha2 traits backed by BoringSSL
+mod sha2;
+
 /// The BoringSSL backed struct which implements CryptoProvider
 #[derive(Default, Clone, Debug, PartialEq, Eq)]
 pub struct Boringssl;
 
 impl CryptoProvider for Boringssl {
-    type HkdfSha256 = hkdf::Hkdf<Sha256>;
+    type HkdfSha256 = hkdf::Hkdf<bssl_crypto::digest::Sha256>;
     type HmacSha256 = hmac::HmacSha256;
-    type HkdfSha512 = hkdf::Hkdf<Sha512>;
+    type HkdfSha512 = hkdf::Hkdf<bssl_crypto::digest::Sha512>;
     type HmacSha512 = hmac::HmacSha512;
-    type AesCbcPkcs7Padded = AesCbcPkcs7PaddedStubs;
-    type X25519 = X25519Stubs;
-    type P256 = P256Stubs;
-    type Sha256 = Sha2Stubs;
-    type Sha512 = Sha2Stubs;
+    type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded;
+    type X25519 = x25519::X25519Ecdh;
+    type P256 = p256::P256Ecdh;
+    type Sha256 = sha2::Sha256;
+    type Sha512 = sha2::Sha512;
     type Aes128 = aes::Aes128;
     type Aes256 = aes::Aes256;
-    type AesCtr128 = Aes128Stubs;
-    type AesCtr256 = Aes256Stubs;
+    type AesCtr128 = aes::ctr::AesCtr128;
+    type AesCtr256 = aes::ctr::AesCtr256;
     type Ed25519 = ed25519::Ed25519;
-    type Aes128GcmSiv = Aes128Stubs;
-    type Aes256GcmSiv = Aes256Stubs;
+    type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv;
+    type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv;
+    type Aes128Gcm = aead::aes_gcm::AesGcm;
+    type Aes256Gcm = aead::aes_gcm::AesGcm;
     type CryptoRng = BoringSslRng;
 
-    fn constant_time_eq(_a: &[u8], _b: &[u8]) -> bool {
-        unimplemented!()
+    fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
+        bssl_crypto::mem::crypto_memcmp(a, b)
     }
 }
 
@@ -86,3 +98,17 @@
         rand_bytes(dest)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use core::marker::PhantomData;
+    use crypto_provider_test::prelude::*;
+    use crypto_provider_test::sha2::*;
+
+    use crate::Boringssl;
+
+    #[apply(sha2_test_cases)]
+    fn sha2_tests(testcase: CryptoProviderTestCase<Boringssl>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/p256.rs b/nearby/crypto/crypto_provider_boringssl/src/p256.rs
new file mode 100644
index 0000000..7fefecd
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/p256.rs
@@ -0,0 +1,117 @@
+// 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 bssl_crypto::ecdh::{PrivateKey, PublicKey};
+use crypto_provider::{
+    elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey as _},
+    p256::{PointCompression, P256},
+    tinyvec::ArrayVec,
+};
+
+/// Implementation of NIST-P256 using bssl-crypto crate.
+pub struct P256Ecdh;
+
+impl EcdhProvider<P256> for P256Ecdh {
+    type PublicKey = P256PublicKey;
+    type EphemeralSecret = P256EphemeralSecret;
+    type SharedSecret = [u8; 32];
+}
+
+/// A NIST-P256 public key.
+#[derive(Debug, PartialEq, Eq)]
+pub struct P256PublicKey(PublicKey<bssl_crypto::ecdh::P256>);
+impl crypto_provider::p256::P256PublicKey for P256PublicKey {
+    type Error = bssl_crypto::ecdh::Error;
+
+    fn from_sec1_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
+        PublicKey::try_from(bytes).map(Self)
+    }
+
+    fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
+        match point_compression {
+            PointCompression::Compressed => unimplemented!(),
+            PointCompression::Uncompressed => {
+                let mut bytes = ArrayVec::<[u8; 65]>::new();
+                bytes.extend_from_slice(&self.0.to_vec());
+                bytes
+            }
+        }
+    }
+
+    #[allow(clippy::expect_used)]
+    fn to_affine_coordinates(&self) -> Result<([u8; 32], [u8; 32]), Self::Error> {
+        Ok(self.0.to_affine_coordinates())
+    }
+    fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> {
+        PublicKey::<bssl_crypto::ecdh::P256>::from_affine_coordinates(x, y).map(Self)
+    }
+}
+
+/// Ephemeral secrect for use in a P256 Diffie-Hellman
+pub struct P256EphemeralSecret {
+    secret: PrivateKey<bssl_crypto::ecdh::P256>,
+}
+
+impl EphemeralSecret<P256> for P256EphemeralSecret {
+    type Impl = P256Ecdh;
+    type Error = bssl_crypto::ecdh::Error;
+    type Rng = ();
+    type EncodedPublicKey =
+        <P256PublicKey as crypto_provider::elliptic_curve::PublicKey<P256>>::EncodedPublicKey;
+
+    fn generate_random(_rng: &mut Self::Rng) -> Self {
+        Self { secret: PrivateKey::generate() }
+    }
+
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+        P256PublicKey((&self.secret).into()).to_bytes()
+    }
+
+    fn diffie_hellman(
+        self,
+        other_pub: &P256PublicKey,
+    ) -> Result<<Self::Impl as EcdhProvider<P256>>::SharedSecret, Self::Error> {
+        let shared_secret = self.secret.diffie_hellman(&other_pub.0)?;
+        let bytes: <Self::Impl as EcdhProvider<P256>>::SharedSecret = shared_secret.to_bytes();
+        Ok(bytes)
+    }
+}
+
+#[cfg(test)]
+impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret {
+    #[allow(clippy::unwrap_used)]
+    fn from_private_components(
+        private_bytes: &[u8; 32],
+        _public_key: &P256PublicKey,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self { secret: PrivateKey::from_private_bytes(private_bytes).unwrap() })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::P256Ecdh;
+    use core::marker::PhantomData;
+    use crypto_provider_test::p256::*;
+
+    #[apply(p256_test_cases)]
+    fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>, name: &str) {
+        if name == "to_bytes_compressed" {
+            return; // EC point compression not supported by bssl-crypto yet
+        }
+        testcase(PhantomData::<P256Ecdh>)
+    }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/sha2.rs b/nearby/crypto/crypto_provider_boringssl/src/sha2.rs
new file mode 100644
index 0000000..d95294d
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/sha2.rs
@@ -0,0 +1,35 @@
+// 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.
+
+/// BoringSSL implementation for SHA256
+pub struct Sha256;
+
+impl crypto_provider::sha2::Sha256 for Sha256 {
+    fn sha256(input: &[u8]) -> [u8; 32] {
+        let mut digest = bssl_crypto::digest::Sha256::new_digest();
+        digest.update(input);
+        digest.finalize()
+    }
+}
+
+/// BoringSSL implementation for SHA512
+pub struct Sha512;
+
+impl crypto_provider::sha2::Sha512 for Sha512 {
+    fn sha512(input: &[u8]) -> [u8; 64] {
+        let mut digest = bssl_crypto::digest::Sha512::new_digest();
+        digest.update(input);
+        digest.finalize()
+    }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/x25519.rs b/nearby/crypto/crypto_provider_boringssl/src/x25519.rs
new file mode 100644
index 0000000..58d040b
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/x25519.rs
@@ -0,0 +1,102 @@
+// 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::{EcdhProvider, EphemeralSecret, PublicKey};
+use crypto_provider::x25519::X25519;
+
+/// The BoringSSL implementation of X25519 ECDH.
+pub struct X25519Ecdh;
+
+impl EcdhProvider<X25519> for X25519Ecdh {
+    type PublicKey = X25519PublicKey;
+    type EphemeralSecret = X25519EphemeralSecret;
+    type SharedSecret = [u8; 32];
+}
+
+/// A X25519 ephemeral secret used for Diffie-Hellman.
+pub struct X25519EphemeralSecret {
+    secret: bssl_crypto::x25519::PrivateKey,
+}
+
+impl EphemeralSecret<X25519> for X25519EphemeralSecret {
+    type Impl = X25519Ecdh;
+    type Error = bssl_crypto::x25519::DiffieHellmanError;
+    type Rng = ();
+    type EncodedPublicKey = [u8; 32];
+
+    fn generate_random(_rng: &mut Self::Rng) -> Self {
+        Self { secret: bssl_crypto::x25519::PrivateKey::generate() }
+    }
+
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+        let pubkey: bssl_crypto::x25519::PublicKey = (&self.secret).into();
+        pubkey.to_bytes()
+    }
+
+    fn diffie_hellman(
+        self,
+        other_pub: &<X25519Ecdh as EcdhProvider<X25519>>::PublicKey,
+    ) -> Result<<X25519Ecdh as EcdhProvider<X25519>>::SharedSecret, Self::Error> {
+        self.secret.diffie_hellman(&other_pub.0).map(|secret| secret.to_bytes())
+    }
+}
+
+#[cfg(test)]
+impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<X25519>
+    for X25519EphemeralSecret
+{
+    fn from_private_components(
+        private_bytes: &[u8; 32],
+        _public_key: &X25519PublicKey,
+    ) -> Result<Self, Self::Error> {
+        Ok(Self { secret: bssl_crypto::x25519::PrivateKey::from_private_bytes(private_bytes) })
+    }
+}
+
+/// A X25519 public key.
+#[derive(Debug, PartialEq, Eq)]
+pub struct X25519PublicKey(bssl_crypto::x25519::PublicKey);
+
+impl PublicKey<X25519> for X25519PublicKey {
+    type Error = Error;
+    type EncodedPublicKey = [u8; 32];
+
+    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
+        let byte_arr: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?;
+        Ok(Self(bssl_crypto::x25519::PublicKey::from(&byte_arr)))
+    }
+
+    fn to_bytes(&self) -> Self::EncodedPublicKey {
+        self.0.to_bytes()
+    }
+}
+
+/// Error type for the BoringSSL implementation of x25519.
+#[derive(Debug)]
+pub enum Error {
+    /// Unexpected size for the given input.
+    WrongSize,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::X25519Ecdh;
+    use core::marker::PhantomData;
+    use crypto_provider_test::x25519::*;
+
+    #[apply(x25519_test_cases)]
+    fn x25519_tests(testcase: CryptoProviderTestCase<X25519Ecdh>) {
+        testcase(PhantomData::<X25519Ecdh>)
+    }
+}
diff --git a/nearby/crypto/crypto_provider_default/src/lib.rs b/nearby/crypto/crypto_provider_default/src/lib.rs
index 4b1b01e..b880ba8 100644
--- a/nearby/crypto/crypto_provider_default/src/lib.rs
+++ b/nearby/crypto/crypto_provider_default/src/lib.rs
@@ -15,6 +15,10 @@
 //! Provides multiple implementations of CryptoProvider through the same struct, configurable by
 //! feature flag.
 
+#![no_std]
+#![forbid(unsafe_code)]
+#![deny(missing_docs)]
+
 cfg_if::cfg_if! {
     if #[cfg(feature = "rustcrypto")] {
         pub use crypto_provider_rustcrypto::RustCrypto as CryptoProviderImpl;
diff --git a/nearby/crypto/crypto_provider_openssl/src/aes.rs b/nearby/crypto/crypto_provider_openssl/src/aes.rs
index f5d5b00..c2ac5b1 100644
--- a/nearby/crypto/crypto_provider_openssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/aes.rs
@@ -26,10 +26,13 @@
 
 use openssl::symm::{Cipher, Crypter, Mode};
 
-use crypto_provider::aes::{
-    cbc::{AesCbcIv, DecryptionError},
-    ctr::NonceAndCounter,
-    Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey,
+use crypto_provider::{
+    aes::{
+        cbc::{AesCbcIv, DecryptionError, EncryptionError},
+        ctr::NonceAndCounter,
+        Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey,
+    },
+    tinyvec::SliceVec,
 };
 
 /// Uber struct which contains impls for AES-128 fns
@@ -126,7 +129,23 @@
 impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for OpenSslAesCbcPkcs7 {
     fn encrypt(key: &crypto_provider::aes::Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
         openssl::symm::encrypt(Cipher::aes_256_cbc(), key.as_slice(), Some(iv.as_slice()), message)
-            .unwrap()
+            // The output buffer is allocated by the openssl crate and guarantees to have enough
+            // space to hold the output value and does not overlap with the input slice.
+            .expect("encrypt should always succeed")
+    }
+
+    fn encrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        message: &mut SliceVec<u8>,
+    ) -> Result<(), EncryptionError> {
+        let encrypted = Self::encrypt(key, iv, message);
+        if encrypted.len() > message.capacity() {
+            return Err(EncryptionError::PaddingFailed);
+        }
+        message.clear();
+        message.extend_from_slice(&encrypted);
+        Ok(())
     }
 
     fn decrypt(
@@ -142,12 +161,22 @@
         )
         .map_err(|_| DecryptionError::BadPadding)
     }
+
+    fn decrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &mut SliceVec<u8>,
+    ) -> Result<(), DecryptionError> {
+        Self::decrypt(key, iv, ciphertext).map(|result| {
+            ciphertext.clear();
+            ciphertext.extend_from_slice(&result);
+        })
+    }
 }
 
 /// OpenSSL implementation of AES-CTR-128
 pub struct OpenSslAesCtr128 {
     enc_cipher: Crypter,
-    dec_cipher: Crypter,
 }
 
 impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr128 {
@@ -161,33 +190,19 @@
                 Some(&nonce_and_counter.as_block_array()),
             )
             .unwrap(),
-            dec_cipher: Crypter::new(
-                Cipher::aes_128_ctr(),
-                Mode::Decrypt,
-                key.as_slice(),
-                Some(&nonce_and_counter.as_block_array()),
-            )
-            .unwrap(),
         }
     }
 
-    fn encrypt(&mut self, data: &mut [u8]) {
+    fn apply_keystream(&mut self, data: &mut [u8]) {
         let mut in_slice = vec![0u8; data.len()];
         in_slice.copy_from_slice(data);
         let _ = self.enc_cipher.update(&in_slice, data);
     }
-
-    fn decrypt(&mut self, data: &mut [u8]) {
-        let mut in_slice = vec![0u8; data.len()];
-        in_slice.copy_from_slice(data);
-        let _ = self.dec_cipher.update(&in_slice, data);
-    }
 }
 
 /// OpenSSL implementation of AES-CTR-256
 pub struct OpenSslAesCtr256 {
     enc_cipher: Crypter,
-    dec_cipher: Crypter,
 }
 
 impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr256 {
@@ -201,27 +216,14 @@
                 Some(&nonce_and_counter.as_block_array()),
             )
             .unwrap(),
-            dec_cipher: Crypter::new(
-                Cipher::aes_256_ctr(),
-                Mode::Decrypt,
-                key.as_slice(),
-                Some(&nonce_and_counter.as_block_array()),
-            )
-            .unwrap(),
         }
     }
 
-    fn encrypt(&mut self, data: &mut [u8]) {
+    fn apply_keystream(&mut self, data: &mut [u8]) {
         let mut in_slice = vec![0u8; data.len()];
         in_slice.copy_from_slice(data);
         let _ = self.enc_cipher.update(&in_slice, data);
     }
-
-    fn decrypt(&mut self, data: &mut [u8]) {
-        let mut in_slice = vec![0u8; data.len()];
-        in_slice.copy_from_slice(data);
-        let _ = self.dec_cipher.update(&in_slice, data);
-    }
 }
 
 #[cfg(test)]
diff --git a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
index 110daf2..111348a 100644
--- a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
@@ -13,7 +13,8 @@
 // limitations under the License.
 
 use crypto_provider::ed25519::{
-    InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError,
+    InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _,
+    SignatureError,
 };
 use openssl::pkey::{Id, PKey, Private};
 use openssl::sign::{Signer, Verifier};
@@ -32,7 +33,7 @@
     type PublicKey = PublicKey;
     type Signature = Signature;
 
-    fn private_key(&self) -> RawPrivateKey {
+    fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
         let private_key = self.0.raw_private_key().unwrap();
         let mut public_key = self.0.raw_public_key().unwrap();
         let mut result = private_key;
@@ -40,7 +41,7 @@
         result.try_into().unwrap()
     }
 
-    fn from_private_key(bytes: &RawPrivateKey) -> Self {
+    fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self {
         Self(PKey::private_key_from_raw_bytes(bytes, Id::ED25519).unwrap())
     }
 
diff --git a/nearby/crypto/crypto_provider_openssl/src/lib.rs b/nearby/crypto/crypto_provider_openssl/src/lib.rs
index d8157d2..8d1db1d 100644
--- a/nearby/crypto/crypto_provider_openssl/src/lib.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/lib.rs
@@ -82,6 +82,8 @@
     type Ed25519 = ed25519::Ed25519;
     type Aes128GcmSiv = crypto_provider_stubs::Aes128Stubs;
     type Aes256GcmSiv = crypto_provider_stubs::Aes256Stubs;
+    type Aes128Gcm = crypto_provider_stubs::Aes128Stubs;
+    type Aes256Gcm = crypto_provider_stubs::Aes256Stubs;
     type CryptoRng = OpenSslRng;
 
     fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
@@ -113,7 +115,7 @@
     use core::marker::PhantomData;
 
     use crypto_provider_test::sha2::*;
-    use crypto_provider_test::*;
+    use crypto_provider_test::{prelude::*, *};
 
     use crate::Openssl;
 
diff --git a/nearby/crypto/crypto_provider_openssl/src/p256.rs b/nearby/crypto/crypto_provider_openssl/src/p256.rs
index 2f5b0b1..17dd6ce 100644
--- a/nearby/crypto/crypto_provider_openssl/src/p256.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/p256.rs
@@ -12,8 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret};
-use crypto_provider::p256::P256;
+use crypto_provider::{
+    elliptic_curve::{EcdhProvider, EphemeralSecret},
+    p256::{PointCompression, P256},
+    tinyvec::ArrayVec,
+};
 use openssl::bn::{BigNum, BigNumContext};
 use openssl::derive::Deriver;
 use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm};
@@ -65,15 +68,24 @@
         Ok(Self(eckey.try_into()?))
     }
 
-    fn to_sec1_bytes(&self) -> Vec<u8> {
+    fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
+        let point_conversion_form = match point_compression {
+            PointCompression::Compressed => PointConversionForm::COMPRESSED,
+            PointCompression::Uncompressed => PointConversionForm::UNCOMPRESSED,
+        };
         let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
         let mut bncontext = BigNumContext::new().unwrap();
-        self.0
-            .ec_key()
-            .unwrap()
-            .public_key()
-            .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext)
-            .unwrap()
+        let mut bytes = ArrayVec::<[u8; 65]>::new();
+        bytes.extend_from_slice(
+            &self
+                .0
+                .ec_key()
+                .unwrap()
+                .public_key()
+                .to_bytes(&ecgroup, point_conversion_form, &mut bncontext)
+                .unwrap(),
+        );
+        bytes
     }
 
     fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> {
@@ -115,6 +127,7 @@
     type Impl = P256Ecdh;
     type Error = Error;
     type Rng = ();
+    type EncodedPublicKey = ArrayVec<[u8; 65]>;
 
     fn generate_random(_rng: &mut Self::Rng) -> Self {
         let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
@@ -122,15 +135,20 @@
         Self(eckey.try_into().unwrap())
     }
 
-    fn public_key_bytes(&self) -> Vec<u8> {
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
         let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
         let mut bncontext = BigNumContext::new().unwrap();
-        self.0
-            .ec_key()
-            .unwrap()
-            .public_key()
-            .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext)
-            .unwrap()
+        let mut bytes = Self::EncodedPublicKey::new();
+        bytes.extend_from_slice(
+            &self
+                .0
+                .ec_key()
+                .unwrap()
+                .public_key()
+                .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext)
+                .unwrap(),
+        );
+        bytes
     }
 
     fn diffie_hellman(
@@ -178,7 +196,7 @@
     use crypto_provider_test::p256::*;
 
     #[apply(p256_test_cases)]
-    fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>) {
-        testcase(PhantomData::<P256Ecdh>)
+    fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>, _name: &str) {
+        testcase(PhantomData)
     }
 }
diff --git a/nearby/crypto/crypto_provider_openssl/src/x25519.rs b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
index ff6f3b0..2cb4cab 100644
--- a/nearby/crypto/crypto_provider_openssl/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
@@ -30,14 +30,15 @@
 
 impl PublicKey<X25519> for X25519PublicKey {
     type Error = ErrorStack;
+    type EncodedPublicKey = [u8; 32];
 
     fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
         let key = PKey::public_key_from_raw_bytes(bytes, Id::X25519)?;
         Ok(X25519PublicKey(key))
     }
 
-    fn to_bytes(&self) -> Vec<u8> {
-        self.0.raw_public_key().unwrap()
+    fn to_bytes(&self) -> Self::EncodedPublicKey {
+        self.0.raw_public_key().unwrap().try_into().unwrap()
     }
 }
 
@@ -48,14 +49,15 @@
     type Impl = X25519Ecdh;
     type Error = ErrorStack;
     type Rng = ();
+    type EncodedPublicKey = [u8; 32];
 
     fn generate_random(_rng: &mut Self::Rng) -> Self {
         let private_key = openssl::pkey::PKey::generate_x25519().unwrap();
         Self(private_key)
     }
 
-    fn public_key_bytes(&self) -> Vec<u8> {
-        self.0.raw_public_key().unwrap()
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+        self.0.raw_public_key().unwrap().try_into().unwrap()
     }
 
     fn diffie_hellman(
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
index e0bf534..a0638ff 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
+++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -6,15 +6,22 @@
 
 [dependencies]
 aead = "0.5.1"
-aes-gcm-siv = { version = "0.11.1", features = ["aes"] }
-crypto_provider.workspace = true
+aes-gcm-siv = { version = "0.11.1", features = [
+    "aes",
+], default-features = false }
+aes-gcm = { version = "0.10.3", features = [
+    "aes",
+], default-features = false }
+crypto_provider = { workspace = true }
 hmac.workspace = true
 hkdf.workspace = true
 sha2.workspace = true
 x25519-dalek.workspace = true
 p256 = { workspace = true, features = ["ecdh"], default-features = false }
 sec1.workspace = true
-ed25519-dalek = { workspace = true, default-features = false, features = ["rand_core"] }
+ed25519-dalek = { workspace = true, default-features = false, features = [
+    "rand_core",
+] }
 rand = { workspace = true, default-features = false }
 rand_core.workspace = true
 subtle.workspace = true
@@ -31,5 +38,12 @@
 
 [features]
 default = ["alloc", "rand_chacha"]
-std = ["ed25519-dalek/default", "rand/std", "rand/std_rng", "crypto_provider/std", "crypto_provider/alloc"]
-alloc = ["aead/bytes"]
+std = [
+    "alloc",
+    "ed25519-dalek/default",
+    "rand/std",
+    "rand/std_rng",
+    "crypto_provider/std",
+    "crypto_provider/alloc",
+]
+alloc = ["aead/bytes", "aead/alloc", "cbc/alloc", "crypto_provider/alloc"]
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs
new file mode 100644
index 0000000..f1d9e0f
--- /dev/null
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs
@@ -0,0 +1,123 @@
+// 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.
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+#[cfg(feature = "alloc")]
+use aead::Payload;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+// RustCrypto defined traits and types
+use aes::cipher::typenum::consts::{U12, U16};
+use aes::cipher::BlockCipher;
+use aes::cipher::BlockEncrypt;
+#[cfg(feature = "alloc")]
+use aes_gcm::aead::Aead as _;
+use aes_gcm::aead::KeyInit;
+use aes_gcm::AeadInPlace as _;
+
+// CryptoProvider traits and types
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
+
+pub struct AesGcm<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>(
+    aes_gcm::AesGcm<A, U12>,
+);
+
+impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcm
+    for AesGcm<A>
+{
+}
+
+impl<K: crypto_provider::aes::AesKey, A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>
+    AeadInit<K> for AesGcm<A>
+{
+    fn new(key: &K) -> Self {
+        Self(aes_gcm::AesGcm::<A, U12>::new(key.as_slice().into()))
+    }
+}
+
+impl<A: aes::cipher::BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> Aead for AesGcm<A> {
+    const TAG_SIZE: usize = 16;
+    type Nonce = [u8; 12];
+    type Tag = [u8; 16];
+
+    #[cfg(feature = "alloc")]
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0
+            .encrypt(aes_gcm::Nonce::from_slice(nonce), Payload { msg, aad })
+            .map_err(|_| AeadError)
+    }
+
+    fn encrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError> {
+        self.0
+            .encrypt_in_place_detached(aes_gcm::Nonce::from_slice(nonce), aad, msg)
+            .map(|arr| arr.into())
+            .map_err(|_| AeadError)
+    }
+
+    #[cfg(feature = "alloc")]
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0
+            .decrypt(aes_gcm::Nonce::from_slice(nonce), Payload { msg, aad })
+            .map_err(|_| AeadError)
+    }
+
+    fn decrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+        tag: &Self::Tag,
+    ) -> Result<(), AeadError> {
+        self.0
+            .decrypt_in_place_detached(aes_gcm::Nonce::from_slice(nonce), aad, msg, tag.into())
+            .map_err(|_| AeadError)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use core::marker::PhantomData;
+
+    use crypto_provider_test::aead::aes_gcm::*;
+    use crypto_provider_test::aes::*;
+
+    use super::*;
+
+    #[apply(aes_128_gcm_test_cases)]
+    fn aes_gcm_128_test(testcase: CryptoProviderTestCase<AesGcm<aes::Aes128>>) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_128_gcm_test_cases_detached)]
+    fn aes_128_gcm_test_detached(testcase: CryptoProviderTestCase<AesGcm<aes::Aes128>>) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_256_gcm_test_cases)]
+    fn aes_gcm_256_test(testcase: CryptoProviderTestCase<AesGcm<aes::Aes256>>) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_256_gcm_test_cases_detached)]
+    fn aes_256_gcm_test_detached(testcase: CryptoProviderTestCase<AesGcm<aes::Aes256>>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
index 402c2ed..408017f 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
@@ -12,74 +12,118 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use aes_gcm_siv::{AeadInPlace, Aes128GcmSiv, Aes256GcmSiv, KeyInit, Nonce};
+#[cfg(feature = "alloc")]
 extern crate alloc;
+#[cfg(feature = "alloc")]
+use aead::Payload;
+#[cfg(feature = "alloc")]
 use alloc::vec::Vec;
-use crypto_provider::aead::{Aead, AeadError};
 
-use crypto_provider::aead::aes_gcm_siv::AesGcmSiv;
-use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey};
+// RustCrypto defined traits and types
+use aes::cipher::typenum::consts::U16;
+use aes::cipher::BlockCipher;
+use aes::cipher::BlockEncrypt;
+#[cfg(feature = "alloc")]
+use aes_gcm_siv::aead::Aead as _;
+use aes_gcm_siv::aead::KeyInit;
+use aes_gcm_siv::AeadInPlace as _;
 
-pub struct AesGcmSiv128(Aes128GcmSiv);
+// CryptoProvider traits and types
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
 
-impl AesGcmSiv for AesGcmSiv128 {}
+pub struct AesGcmSiv<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>(
+    aes_gcm_siv::AesGcmSiv<A>,
+);
 
-impl Aead for AesGcmSiv128 {
-    const TAG_SIZE: usize = 16;
-    type Nonce = [u8; 12];
-    type Key = Aes128Key;
+impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcmSiv
+    for AesGcmSiv<A>
+{
+}
 
-    fn new(key: &Self::Key) -> Self {
-        Self(Aes128GcmSiv::new(key.as_slice().into()))
-    }
-
-    fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
-        self.0.encrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
-    }
-
-    fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
-        self.0.decrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
+impl<K: crypto_provider::aes::AesKey, A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>
+    AeadInit<K> for AesGcmSiv<A>
+{
+    fn new(key: &K) -> Self {
+        Self(aes_gcm_siv::AesGcmSiv::<A>::new(key.as_slice().into()))
     }
 }
 
-pub struct AesGcmSiv256(Aes256GcmSiv);
-
-impl AesGcmSiv for AesGcmSiv256 {}
-
-impl Aead for AesGcmSiv256 {
+impl<A: aes::cipher::BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> Aead for AesGcmSiv<A> {
     const TAG_SIZE: usize = 16;
     type Nonce = [u8; 12];
-    type Key = Aes256Key;
+    type Tag = [u8; 16];
 
-    fn new(key: &Self::Key) -> Self {
-        Self(Aes256GcmSiv::new(key.as_slice().into()))
+    #[cfg(feature = "alloc")]
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0
+            .encrypt(aes_gcm_siv::Nonce::from_slice(nonce), Payload { msg, aad })
+            .map_err(|_| AeadError)
     }
 
-    fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
-        self.0.encrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
+    fn encrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError> {
+        self.0
+            .encrypt_in_place_detached(aes_gcm_siv::Nonce::from_slice(nonce), aad, msg)
+            .map(|arr| arr.into())
+            .map_err(|_| AeadError)
     }
 
-    fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
-        self.0.decrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
+    #[cfg(feature = "alloc")]
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+        self.0
+            .decrypt(aes_gcm_siv::Nonce::from_slice(nonce), Payload { msg, aad })
+            .map_err(|_| AeadError)
+    }
+
+    fn decrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+        tag: &Self::Tag,
+    ) -> Result<(), AeadError> {
+        self.0
+            .decrypt_in_place_detached(aes_gcm_siv::Nonce::from_slice(nonce), aad, msg, tag.into())
+            .map_err(|_| AeadError)
     }
 }
 
 #[cfg(test)]
 mod tests {
     use core::marker::PhantomData;
-
     use crypto_provider_test::aead::aes_gcm_siv::*;
     use crypto_provider_test::aes::*;
-
-    use super::*;
+    use crypto_provider_test::prelude::apply;
 
     #[apply(aes_128_gcm_siv_test_cases)]
-    fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv128>) {
+    fn aes_gcm_siv_128_test(
+        testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>>,
+    ) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_128_gcm_siv_test_cases_detached)]
+    fn aes_gcm_siv_128_test_detached(
+        testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>>,
+    ) {
         testcase(PhantomData);
     }
 
     #[apply(aes_256_gcm_siv_test_cases)]
-    fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv256>) {
+    fn aes_gcm_siv_256_test(
+        testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>>,
+    ) {
+        testcase(PhantomData);
+    }
+
+    #[apply(aes_256_gcm_siv_test_cases_detached)]
+    fn aes_gcm_siv_256_test_detached(
+        testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>>,
+    ) {
         testcase(PhantomData);
     }
 }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
index 7fc561b..e4c2c60 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
@@ -13,3 +13,5 @@
 // limitations under the License.
 
 pub(crate) mod aes_gcm_siv;
+
+pub(crate) mod aes_gcm;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
deleted file mode 100644
index 06d7224..0000000
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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.
-
-extern crate alloc;
-use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
-use aes::Aes256;
-use alloc::vec::Vec;
-use crypto_provider::aes::{
-    cbc::{AesCbcIv, DecryptionError},
-    Aes256Key, AesKey,
-};
-
-/// RustCrypto implementation of AES-CBC with PKCS7 padding
-pub enum AesCbcPkcs7Padded {}
-impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded {
-    fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
-        let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into());
-        encryptor.encrypt_padded_vec_mut::<Pkcs7>(message)
-    }
-
-    fn decrypt(
-        key: &Aes256Key,
-        iv: &AesCbcIv,
-        ciphertext: &[u8],
-    ) -> Result<Vec<u8>, DecryptionError> {
-        cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into())
-            .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
-            .map_err(|_| DecryptionError::BadPadding)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::AesCbcPkcs7Padded;
-    use core::marker::PhantomData;
-    use crypto_provider_test::aes::cbc::*;
-
-    #[apply(aes_256_cbc_test_cases)]
-    fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
-        testcase(PhantomData);
-    }
-}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs
new file mode 100644
index 0000000..c6d74f0
--- /dev/null
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs
@@ -0,0 +1,102 @@
+// 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.
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use aes::Aes256;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+use crypto_provider::{
+    aes::{
+        cbc::{AesCbcIv, DecryptionError, EncryptionError},
+        Aes256Key, AesKey,
+    },
+    tinyvec::SliceVec,
+};
+
+/// RustCrypto implementation of AES-CBC with PKCS7 padding
+pub enum AesCbcPkcs7Padded {}
+impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded {
+    #[cfg(feature = "alloc")]
+    fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
+        let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into());
+        encryptor.encrypt_padded_vec_mut::<Pkcs7>(message)
+    }
+
+    fn encrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        message: &mut SliceVec<u8>,
+    ) -> Result<(), EncryptionError> {
+        let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into());
+        let message_len = message.len();
+        // Set the length so encrypt_padded_mut can write using the full capacity
+        // (Unlike `Vec.set_len`, `SliceVec.set_len` is safe and won't panic if len <= capacity)
+        message.set_len(message.capacity());
+        encryptor
+            .encrypt_padded_mut::<Pkcs7>(message, message_len)
+            .map(|result| result.len())
+            // `SliceVec.set_len` is safe, and won't panic because `encrypt_padded_mut` never
+            // returns a slice longer than the given buffer.
+            .map(|new_len| message.set_len(new_len))
+            .map_err(|_| {
+                message.set_len(message_len); // Set the buffer back to its original length
+                EncryptionError::PaddingFailed
+            })
+    }
+
+    #[cfg(feature = "alloc")]
+    fn decrypt(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &[u8],
+    ) -> Result<Vec<u8>, DecryptionError> {
+        cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into())
+            .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+            .map_err(|_| DecryptionError::BadPadding)
+    }
+
+    fn decrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &mut SliceVec<u8>,
+    ) -> Result<(), DecryptionError> {
+        // Decrypted size is always smaller than the input size because of padding, so we don't need
+        // to set the length to the full capacity.
+        cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into())
+            .decrypt_padded_mut::<Pkcs7>(ciphertext)
+            .map(|result| result.len())
+            // `SliceVec.set_len` is safe, and won't panic because decrypted result length is always
+            // smaller than the input size.
+            .map(|new_len| ciphertext.set_len(new_len))
+            .map_err(|_| {
+                ciphertext.as_mut().fill(0);
+                DecryptionError::BadPadding
+            })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::AesCbcPkcs7Padded;
+    use core::marker::PhantomData;
+    use crypto_provider_test::aes::cbc::*;
+
+    #[apply(aes_256_cbc_test_cases)]
+    fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
+        testcase(PhantomData);
+    }
+}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
similarity index 88%
rename from nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
index 85cdac6..09ae557 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
@@ -33,11 +33,7 @@
         }
     }
 
-    fn encrypt(&mut self, data: &mut [u8]) {
-        self.cipher.apply_keystream(data);
-    }
-
-    fn decrypt(&mut self, data: &mut [u8]) {
+    fn apply_keystream(&mut self, data: &mut [u8]) {
         self.cipher.apply_keystream(data);
     }
 }
@@ -59,11 +55,7 @@
         }
     }
 
-    fn encrypt(&mut self, data: &mut [u8]) {
-        self.cipher.apply_keystream(data);
-    }
-
-    fn decrypt(&mut self, data: &mut [u8]) {
+    fn apply_keystream(&mut self, data: &mut [u8]) {
         self.cipher.apply_keystream(data);
     }
 }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
similarity index 98%
rename from nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
index c71f2ec..4c04bf5 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
@@ -22,7 +22,6 @@
 };
 
 /// Module implementing AES-CBC.
-#[cfg(feature = "alloc")]
 pub(crate) mod cbc;
 
 pub(crate) mod ctr;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
index d11a5ea..ce8089e 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
@@ -15,8 +15,8 @@
 use ed25519_dalek::Signer;
 
 use crypto_provider::ed25519::{
-    InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError,
-    PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
+    InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _,
+    SignatureError, PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
 };
 
 pub struct Ed25519;
@@ -33,11 +33,11 @@
     type PublicKey = PublicKey;
     type Signature = Signature;
 
-    fn private_key(&self) -> [u8; PRIVATE_KEY_LENGTH] {
+    fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> [u8; PRIVATE_KEY_LENGTH] {
         self.0.to_bytes()
     }
 
-    fn from_private_key(bytes: &RawPrivateKey) -> Self {
+    fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self {
         Self(ed25519_dalek::SigningKey::from_bytes(bytes))
     }
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
similarity index 86%
rename from nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
index aba0d4a..48380fa 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
@@ -22,7 +22,7 @@
 use hmac::digest::{HashMarker, OutputSizeUser};
 
 /// RustCrypto based hkdf implementation
-pub struct Hkdf<D>
+pub struct Hkdf<D>(hkdf::Hkdf<D>)
 where
     D: OutputSizeUser,
     D: CoreProxy,
@@ -33,10 +33,7 @@
         + Default
         + Clone,
     <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
-    hkdf_impl: hkdf::Hkdf<D>,
-}
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
 
 impl<D> crypto_provider::hkdf::Hkdf for Hkdf<D>
 where
@@ -52,7 +49,7 @@
     Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
-        Hkdf { hkdf_impl: hkdf::Hkdf::new(salt, ikm) }
+        Hkdf(hkdf::Hkdf::new(salt, ikm))
     }
 
     fn expand_multi_info(
@@ -60,11 +57,11 @@
         info_components: &[&[u8]],
         okm: &mut [u8],
     ) -> Result<(), InvalidLength> {
-        self.hkdf_impl.expand_multi_info(info_components, okm).map_err(|_| InvalidLength)
+        self.0.expand_multi_info(info_components, okm).map_err(|_| InvalidLength)
     }
 
     fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
-        self.hkdf_impl.expand(info, okm).map_err(|_| InvalidLength)
+        self.0.expand(info, okm).map_err(|_| InvalidLength)
     }
 }
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
similarity index 72%
rename from nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
index dfda208..d8cef33 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
@@ -23,7 +23,7 @@
 use hmac::Mac;
 
 /// RustCrypto based hmac implementation
-pub struct Hmac<D>
+pub struct Hmac<D>(hmac::Hmac<D>)
 where
     D: OutputSizeUser,
     D: CoreProxy,
@@ -34,43 +34,38 @@
         + Default
         + Clone,
     <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
-    hmac_impl: hmac::Hmac<D>,
-}
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
 
 impl crypto_provider::hmac::Hmac<32> for Hmac<sha2::Sha256> {
     #[allow(clippy::expect_used)]
     fn new_from_key(key: [u8; 32]) -> Self {
         hmac::Hmac::new_from_slice(&key)
-            .map(|hmac| Self { hmac_impl: hmac })
+            .map(Self)
             .expect("length will always be valid because input key is of fixed size")
     }
 
     fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> {
-        hmac::Hmac::new_from_slice(key)
-            .map(|hmac| Self { hmac_impl: hmac })
-            .map_err(|_| InvalidLength)
+        hmac::Hmac::new_from_slice(key).map(Self).map_err(|_| InvalidLength)
     }
 
     fn update(&mut self, data: &[u8]) {
-        self.hmac_impl.update(data);
+        self.0.update(data);
     }
 
     fn finalize(self) -> [u8; 32] {
-        self.hmac_impl.finalize().into_bytes().into()
+        self.0.finalize().into_bytes().into()
     }
 
     fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> {
-        self.hmac_impl.verify_slice(tag).map_err(|_| MacError)
+        self.0.verify_slice(tag).map_err(|_| MacError)
     }
 
     fn verify(self, tag: [u8; 32]) -> Result<(), MacError> {
-        self.hmac_impl.verify(&tag.into()).map_err(|_| MacError)
+        self.0.verify(&tag.into()).map_err(|_| MacError)
     }
 
     fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> {
-        self.hmac_impl.verify_truncated_left(tag).map_err(|_| MacError)
+        self.0.verify_truncated_left(tag).map_err(|_| MacError)
     }
 }
 
@@ -78,34 +73,32 @@
     #[allow(clippy::expect_used)]
     fn new_from_key(key: [u8; 64]) -> Self {
         hmac::Hmac::new_from_slice(&key)
-            .map(|hmac| Self { hmac_impl: hmac })
+            .map(Self)
             .expect("length will always be valid because input key is of fixed size")
     }
 
     fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> {
-        hmac::Hmac::new_from_slice(key)
-            .map(|hmac| Self { hmac_impl: hmac })
-            .map_err(|_| InvalidLength)
+        hmac::Hmac::new_from_slice(key).map(Self).map_err(|_| InvalidLength)
     }
 
     fn update(&mut self, data: &[u8]) {
-        self.hmac_impl.update(data);
+        self.0.update(data);
     }
 
     fn finalize(self) -> [u8; 64] {
-        self.hmac_impl.finalize().into_bytes().into()
+        self.0.finalize().into_bytes().into()
     }
 
     fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> {
-        self.hmac_impl.verify_slice(tag).map_err(|_| MacError)
+        self.0.verify_slice(tag).map_err(|_| MacError)
     }
 
     fn verify(self, tag: [u8; 64]) -> Result<(), MacError> {
-        self.hmac_impl.verify(&tag.into()).map_err(|_| MacError)
+        self.0.verify(&tag.into()).map_err(|_| MacError)
     }
 
     fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> {
-        self.hmac_impl.verify_truncated_left(tag).map_err(|_| MacError)
+        self.0.verify_truncated_left(tag).map_err(|_| MacError)
     }
 }
 
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
index e95712c..628f679 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
@@ -25,6 +25,7 @@
 
 use core::{fmt::Debug, marker::PhantomData};
 
+pub use aes;
 use cfg_if::cfg_if;
 pub use hkdf;
 pub use hmac;
@@ -35,17 +36,17 @@
 /// Contains the RustCrypto backed impls for AES-GCM-SIV operations
 mod aead;
 /// Contains the RustCrypto backed AES impl for CryptoProvider
-pub mod aes;
+pub mod aes_cp;
 /// Contains the RustCrypto backed impl for ed25519 key generation, signing, and verification
 mod ed25519;
 /// Contains the RustCrypto backed hkdf impl for CryptoProvider
-mod hkdf_rc;
+mod hkdf_cp;
 /// Contains the RustCrypto backed hmac impl for CryptoProvider
-mod hmac_rc;
+mod hmac_cp;
 /// Contains the RustCrypto backed P256 impl for CryptoProvider
 mod p256;
 /// Contains the RustCrypto backed SHA2 impl for CryptoProvider
-mod sha2_rc;
+mod sha2_cp;
 /// Contains the RustCrypto backed X25519 impl for CryptoProvider
 mod x25519;
 
@@ -54,9 +55,11 @@
         /// Providing a type alias for compatibility with existing usage of RustCrypto
         /// by default we use StdRng for the underlying csprng
         pub type RustCrypto = RustCryptoImpl<rand::rngs::StdRng>;
-    } else {
+    } else if #[cfg(feature = "rand_chacha")] {
         /// A no_std compatible implementation of CryptoProvider backed by RustCrypto crates
         pub type RustCrypto = RustCryptoImpl<rand_chacha::ChaCha20Rng>;
+    } else {
+        compile_error!("Must specify either --features std or --features rand_chacha");
     }
 }
 
@@ -76,23 +79,24 @@
 impl<R: CryptoRng + SeedableRng + RngCore + Eq + PartialEq + Debug + Clone + Send>
     crypto_provider::CryptoProvider for RustCryptoImpl<R>
 {
-    type HkdfSha256 = hkdf_rc::Hkdf<sha2::Sha256>;
-    type HmacSha256 = hmac_rc::Hmac<sha2::Sha256>;
-    type HkdfSha512 = hkdf_rc::Hkdf<sha2::Sha512>;
-    type HmacSha512 = hmac_rc::Hmac<sha2::Sha512>;
-    #[cfg(feature = "alloc")]
-    type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded;
+    type HkdfSha256 = hkdf_cp::Hkdf<sha2::Sha256>;
+    type HmacSha256 = hmac_cp::Hmac<sha2::Sha256>;
+    type HkdfSha512 = hkdf_cp::Hkdf<sha2::Sha512>;
+    type HmacSha512 = hmac_cp::Hmac<sha2::Sha512>;
+    type AesCbcPkcs7Padded = aes_cp::cbc::AesCbcPkcs7Padded;
     type X25519 = x25519::X25519Ecdh<R>;
     type P256 = p256::P256Ecdh<R>;
-    type Sha256 = sha2_rc::RustCryptoSha256;
-    type Sha512 = sha2_rc::RustCryptoSha512;
-    type Aes128 = aes::Aes128;
-    type Aes256 = aes::Aes256;
-    type AesCtr128 = aes::ctr::AesCtr128;
-    type AesCtr256 = aes::ctr::AesCtr256;
+    type Sha256 = sha2_cp::RustCryptoSha256;
+    type Sha512 = sha2_cp::RustCryptoSha512;
+    type Aes128 = aes_cp::Aes128;
+    type Aes256 = aes_cp::Aes256;
+    type AesCtr128 = aes_cp::ctr::AesCtr128;
+    type AesCtr256 = aes_cp::ctr::AesCtr256;
     type Ed25519 = ed25519::Ed25519;
-    type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv128;
-    type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv256;
+    type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>;
+    type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>;
+    type Aes128Gcm = aead::aes_gcm::AesGcm<aes::Aes128>;
+    type Aes256Gcm = aead::aes_gcm::AesGcm<aes::Aes256>;
     type CryptoRng = RcRng<R>;
 
     fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
@@ -124,6 +128,7 @@
 mod tests {
     use core::marker::PhantomData;
 
+    use crypto_provider_test::prelude::*;
     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 539ccc0..98a6cd4 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
@@ -12,14 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-extern crate alloc;
-
 use crate::RcRng;
-use alloc::vec::Vec;
 use core::marker::PhantomData;
 use crypto_provider::{
     elliptic_curve::{EcdhProvider, EphemeralSecret},
-    p256::P256,
+    p256::{PointCompression, P256},
+    tinyvec::ArrayVec,
 };
 use p256::{
     elliptic_curve,
@@ -52,8 +50,12 @@
         p256::PublicKey::from_sec1_bytes(bytes).map(Self)
     }
 
-    fn to_sec1_bytes(&self) -> Vec<u8> {
-        self.0.to_encoded_point(true).as_bytes().to_vec()
+    fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
+        let mut bytes = ArrayVec::<[u8; 65]>::new();
+        bytes.extend_from_slice(
+            self.0.to_encoded_point(point_compression == PointCompression::Compressed).as_bytes(),
+        );
+        bytes
     }
 
     #[allow(clippy::expect_used)]
@@ -87,6 +89,7 @@
     type Impl = P256Ecdh<R>;
     type Error = sec1::Error;
     type Rng = RcRng<R>;
+    type EncodedPublicKey = ArrayVec<[u8; 65]>;
 
     fn generate_random(rng: &mut Self::Rng) -> Self {
         Self {
@@ -95,8 +98,10 @@
         }
     }
 
-    fn public_key_bytes(&self) -> Vec<u8> {
-        self.secret.public_key().to_encoded_point(false).as_bytes().into()
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+        let mut bytes = Self::EncodedPublicKey::new();
+        bytes.extend_from_slice(self.secret.public_key().to_encoded_point(false).as_bytes());
+        bytes
     }
 
     fn diffie_hellman(
@@ -136,7 +141,7 @@
     use rand::rngs::StdRng;
 
     #[apply(p256_test_cases)]
-    fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>) {
+    fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>, _name: &str) {
         testcase(PhantomData::<P256Ecdh<StdRng>>)
     }
 }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/sha2_cp.rs
similarity index 100%
rename from nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/sha2_cp.rs
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
index 445e858..ad67777 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
@@ -12,10 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-extern crate alloc;
-
 use crate::RcRng;
-use alloc::vec::Vec;
 use core::marker::PhantomData;
 use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey};
 use crypto_provider::x25519::X25519;
@@ -45,6 +42,7 @@
     type Impl = X25519Ecdh<R>;
     type Error = Error;
     type Rng = RcRng<R>;
+    type EncodedPublicKey = [u8; 32];
 
     fn generate_random(rng: &mut Self::Rng) -> Self {
         Self {
@@ -53,9 +51,9 @@
         }
     }
 
-    fn public_key_bytes(&self) -> Vec<u8> {
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
         let pubkey: x25519_dalek::PublicKey = (&self.secret).into();
-        pubkey.to_bytes().into()
+        pubkey.to_bytes()
     }
 
     fn diffie_hellman(
@@ -90,14 +88,15 @@
 
 impl PublicKey<X25519> for X25519PublicKey {
     type Error = Error;
+    type EncodedPublicKey = [u8; 32];
 
     fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
         let byte_sized: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?;
         Ok(Self(byte_sized.into()))
     }
 
-    fn to_bytes(&self) -> Vec<u8> {
-        self.0.as_bytes().to_vec()
+    fn to_bytes(&self) -> Self::EncodedPublicKey {
+        self.0.to_bytes()
     }
 }
 
diff --git a/nearby/crypto/crypto_provider_stubs/Cargo.toml b/nearby/crypto/crypto_provider_stubs/Cargo.toml
index d7c4d45..4e8bdec 100644
--- a/nearby/crypto/crypto_provider_stubs/Cargo.toml
+++ b/nearby/crypto/crypto_provider_stubs/Cargo.toml
@@ -5,4 +5,4 @@
 publish.workspace = true
 
 [dependencies]
-crypto_provider = {workspace = true, features = ["std", "alloc"] }
\ No newline at end of file
+crypto_provider = {workspace = true, features = ["std", "alloc"] }
diff --git a/nearby/crypto/crypto_provider_stubs/src/lib.rs b/nearby/crypto/crypto_provider_stubs/src/lib.rs
index 5d54b72..32c9381 100644
--- a/nearby/crypto/crypto_provider_stubs/src/lib.rs
+++ b/nearby/crypto/crypto_provider_stubs/src/lib.rs
@@ -20,21 +20,23 @@
 
 use std::fmt::Debug;
 
-use crypto_provider::ed25519::{RawPrivateKey, RawPublicKey, RawSignature};
+use crypto_provider::aead::AeadInit;
 use crypto_provider::{
-    aead::aes_gcm_siv::AesGcmSiv,
-    aead::{Aead, AeadError},
+    aead::{Aead, AeadError, AesGcm, AesGcmSiv},
     aes::{
-        cbc::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError},
+        cbc::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError, EncryptionError},
         ctr::{AesCtr, NonceAndCounter},
         Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher,
     },
-    ed25519,
-    ed25519::{Ed25519Provider, InvalidBytes, KeyPair, Signature, SignatureError},
+    ed25519::{
+        self, Ed25519Provider, InvalidBytes, KeyPair, RawPrivateKey, RawPrivateKeyPermit,
+        RawPublicKey, RawSignature, Signature, SignatureError,
+    },
     elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
     hkdf::{Hkdf, InvalidLength},
     hmac::{Hmac, MacError},
-    p256::{P256PublicKey, P256},
+    p256::{P256PublicKey, PointCompression, P256},
+    tinyvec::{ArrayVec, SliceVec},
     x25519::X25519,
 };
 
@@ -58,6 +60,8 @@
     type Ed25519 = Ed25519Stubs;
     type Aes128GcmSiv = Aes128Stubs;
     type Aes256GcmSiv = Aes256Stubs;
+    type Aes128Gcm = Aes128Stubs;
+    type Aes256Gcm = Aes256Stubs;
     type CryptoRng = ();
 
     fn constant_time_eq(_a: &[u8], _b: &[u8]) -> bool {
@@ -171,6 +175,14 @@
         unimplemented!()
     }
 
+    fn encrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        message: &mut SliceVec<u8>,
+    ) -> Result<(), EncryptionError> {
+        unimplemented!()
+    }
+
     fn decrypt(
         _key: &Aes256Key,
         _iv: &AesCbcIv,
@@ -178,6 +190,14 @@
     ) -> Result<Vec<u8>, DecryptionError> {
         unimplemented!()
     }
+
+    fn decrypt_in_place(
+        key: &Aes256Key,
+        iv: &AesCbcIv,
+        ciphertext: &mut SliceVec<u8>,
+    ) -> Result<(), DecryptionError> {
+        unimplemented!()
+    }
 }
 
 pub struct X25519Stubs;
@@ -194,12 +214,13 @@
     type Impl = X25519Stubs;
     type Error = ();
     type Rng = ();
+    type EncodedPublicKey = [u8; 32];
 
     fn generate_random(_rng: &mut Self::Rng) -> Self {
         unimplemented!()
     }
 
-    fn public_key_bytes(&self) -> Vec<u8> {
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
         unimplemented!()
     }
 
@@ -215,12 +236,13 @@
     type Impl = P256Stubs;
     type Error = ();
     type Rng = ();
+    type EncodedPublicKey = ArrayVec<[u8; 65]>;
 
     fn generate_random(_rng: &mut Self::Rng) -> Self {
         unimplemented!()
     }
 
-    fn public_key_bytes(&self) -> Vec<u8> {
+    fn public_key_bytes(&self) -> Self::EncodedPublicKey {
         unimplemented!()
     }
 
@@ -237,12 +259,13 @@
 
 impl PublicKey<X25519> for EcdhPubKey {
     type Error = ();
+    type EncodedPublicKey = [u8; 32];
 
     fn from_bytes(_bytes: &[u8]) -> Result<Self, Self::Error> {
         unimplemented!()
     }
 
-    fn to_bytes(&self) -> Vec<u8> {
+    fn to_bytes(&self) -> Self::EncodedPublicKey {
         unimplemented!()
     }
 }
@@ -257,7 +280,7 @@
         unimplemented!()
     }
 
-    fn to_sec1_bytes(&self) -> Vec<u8> {
+    fn to_sec1_bytes(&self, _point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
         unimplemented!()
     }
 
@@ -294,6 +317,12 @@
 
 pub struct Aes128Stubs;
 
+impl AeadInit<Aes128Key> for Aes128Stubs {
+    fn new(key: &Aes128Key) -> Self {
+        unimplemented!()
+    }
+}
+
 impl AesCipher for Aes128Stubs {
     type Key = Aes128Key;
 
@@ -321,11 +350,7 @@
         unimplemented!()
     }
 
-    fn encrypt(&mut self, _data: &mut [u8]) {
-        unimplemented!()
-    }
-
-    fn decrypt(&mut self, _data: &mut [u8]) {
+    fn apply_keystream(&mut self, data: &mut [u8]) {
         unimplemented!()
     }
 }
@@ -333,25 +358,48 @@
 impl Aead for Aes128Stubs {
     const TAG_SIZE: usize = 16;
     type Nonce = [u8; 12];
-    type Key = Aes128Key;
+    type Tag = [u8; 16];
 
-    fn new(key: &Self::Key) -> Self {
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
         unimplemented!()
     }
 
-    fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+    fn encrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError> {
         unimplemented!()
     }
 
-    fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
+        unimplemented!()
+    }
+
+    fn decrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+        tag: &Self::Tag,
+    ) -> Result<(), AeadError> {
         unimplemented!()
     }
 }
 
 impl AesGcmSiv for Aes128Stubs {}
 
+impl AesGcm for Aes128Stubs {}
+
 pub struct Aes256Stubs;
 
+impl AeadInit<Aes256Key> for Aes256Stubs {
+    fn new(key: &Aes256Key) -> Self {
+        unimplemented!()
+    }
+}
+
 impl AesCipher for Aes256Stubs {
     type Key = Aes256Key;
 
@@ -379,11 +427,7 @@
         unimplemented!()
     }
 
-    fn encrypt(&mut self, _data: &mut [u8]) {
-        unimplemented!()
-    }
-
-    fn decrypt(&mut self, _data: &mut [u8]) {
+    fn apply_keystream(&mut self, data: &mut [u8]) {
         unimplemented!()
     }
 }
@@ -391,23 +435,40 @@
 impl Aead for Aes256Stubs {
     const TAG_SIZE: usize = 16;
     type Nonce = [u8; 12];
-    type Key = Aes256Key;
+    type Tag = [u8; 16];
 
-    fn new(key: &Self::Key) -> Self {
+    fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
         unimplemented!()
     }
 
-    fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+    fn encrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+    ) -> Result<Self::Tag, AeadError> {
         unimplemented!()
     }
 
-    fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+    fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
+        unimplemented!()
+    }
+
+    fn decrypt_detached(
+        &self,
+        msg: &mut [u8],
+        aad: &[u8],
+        nonce: &Self::Nonce,
+        tag: &Self::Tag,
+    ) -> Result<(), AeadError> {
         unimplemented!()
     }
 }
 
 impl AesGcmSiv for Aes256Stubs {}
 
+impl AesGcm for Aes256Stubs {}
+
 pub struct Ed25519Stubs;
 
 impl Ed25519Provider for Ed25519Stubs {
@@ -457,11 +518,11 @@
     type PublicKey = PublicKeyStubs;
     type Signature = SignatureStubs;
 
-    fn private_key(&self) -> RawPrivateKey {
+    fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
         unimplemented!()
     }
 
-    fn from_private_key(_bytes: &RawPrivateKey) -> Self
+    fn from_raw_private_key(_bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self
     where
         Self: Sized,
     {
diff --git a/nearby/crypto/crypto_provider_test/Cargo.toml b/nearby/crypto/crypto_provider_test/Cargo.toml
index a4d92ec..4064330 100644
--- a/nearby/crypto/crypto_provider_test/Cargo.toml
+++ b/nearby/crypto/crypto_provider_test/Cargo.toml
@@ -5,7 +5,7 @@
 publish.workspace = true
 
 [dependencies]
-crypto_provider = {  workspace = true, features = ["test_vectors"] }
+crypto_provider = {  workspace = true, features = ["raw_private_key_permit", "test_vectors", "alloc"] }
 rand_ext.workspace = true
 test_helper.workspace = true
 
@@ -14,4 +14,4 @@
 rstest.workspace = true
 rstest_reuse.workspace = true
 wycheproof.workspace = true
-hex.workspace = true
\ No newline at end of file
+hex.workspace = true
diff --git a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
index 9e1d140..bd8f6f9 100644
--- a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
+++ b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
@@ -25,6 +25,20 @@
 ]
 
 [[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
 name = "aes-gcm-siv"
 version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -165,6 +179,9 @@
 [[package]]
 name = "crypto_provider"
 version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
 
 [[package]]
 name = "crypto_provider_default"
@@ -193,6 +210,7 @@
 dependencies = [
  "aead",
  "aes",
+ "aes-gcm",
  "aes-gcm-siv",
  "cbc",
  "cfg-if",
@@ -239,9 +257,9 @@
 
 [[package]]
 name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
 dependencies = [
  "cfg-if",
  "cpufeatures",
@@ -307,9 +325,9 @@
 
 [[package]]
 name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
 dependencies = [
  "curve25519-dalek",
  "ed25519",
@@ -390,6 +408,16 @@
 ]
 
 [[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
 name = "group"
 version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -732,6 +760,12 @@
 ]
 
 [[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
 name = "typenum"
 version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -773,9 +807,9 @@
 
 [[package]]
 name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
 dependencies = [
  "curve25519-dalek",
  "rand_core",
diff --git a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs
new file mode 100644
index 0000000..88eaf32
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs
@@ -0,0 +1,316 @@
+// 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;
+
+pub use crate::prelude;
+use crypto_provider::aead::{AeadInit, AesGcm};
+
+/// Test AES-GCM-128 encryption
+pub fn aes_128_gcm_test_encrypt<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC4
+    let test_key = hex!("bedcfb5a011ebc84600fcb296c15af0d");
+    let nonce = hex!("438a547a94ea88dce46c6c85");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("");
+    let tag = hex!("960247ba5cde02e41a313c4c0136edc3");
+    let result = aes.encrypt(&msg, b"", &nonce).expect("Should succeed");
+    assert_eq!(&result[..], &tag);
+}
+
+pub fn aes_128_gcm_test_encrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC4
+    let test_key = hex!("bedcfb5a011ebc84600fcb296c15af0d");
+    let nonce = hex!("438a547a94ea88dce46c6c85");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("");
+    let tag = hex!("960247ba5cde02e41a313c4c0136edc3");
+    let mut buf = Vec::from(msg.as_slice());
+    let actual_tag: [u8; 16] = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+    assert_eq!(&buf, &[0_u8; 0]);
+    assert_eq!(&actual_tag, &tag);
+}
+
+/// Test AES-GCM-128 decryption
+pub fn aes_128_gcm_test_decrypt<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC2
+    let test_key = hex!("5b9604fe14eadba931b0ccf34843dab9");
+    let nonce = hex!("921d2507fa8007b7bd067d34");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("001d0c231287c1182784554ca3a21908");
+    let ct = hex!("49d8b9783e911913d87094d1f63cc765");
+    let ad = hex!("00112233445566778899aabbccddeeff");
+    let tag = hex!("1e348ba07cca2cf04c618cb4d43a5b92");
+    let result = aes.encrypt(&msg, &ad, &nonce).expect("should succeed");
+    assert_eq!(&result[..16], &ct);
+    assert_eq!(&result[16..], &tag);
+    assert_eq!(A::TAG_SIZE, result[16..].len());
+    let result = aes.decrypt(&result[..], &ad, &nonce).expect("should succeed");
+    assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-GCM-128 decryption
+pub fn aes_128_gcm_test_decrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC2
+    let test_key = hex!("5b9604fe14eadba931b0ccf34843dab9");
+    let nonce = hex!("921d2507fa8007b7bd067d34");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("001d0c231287c1182784554ca3a21908");
+    let ct = hex!("49d8b9783e911913d87094d1f63cc765");
+    let ad = hex!("00112233445566778899aabbccddeeff");
+    let tag = hex!("1e348ba07cca2cf04c618cb4d43a5b92");
+    let mut buf = Vec::from(msg.as_slice());
+    let actual_tag = aes.encrypt_detached(&mut buf, &ad, &nonce).unwrap();
+    assert_eq!(&buf, &ct);
+    assert_eq!(actual_tag, tag);
+    assert!(aes.decrypt_detached(&mut buf, &ad, &nonce, &tag).is_ok());
+    assert_eq!(&buf[..], &msg);
+}
+
+/// Test AES-GCM-128 decryption
+pub fn aes_128_gcm_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC23
+    let test_key = hex!("000102030405060708090a0b0c0d0e0f");
+    let nonce = hex!("505152535455565758595a5b");
+    let aes = A::new(&test_key.into());
+    let ct = hex!("eb156d081ed6b6b55f4612f021d87b39");
+    let mut buf = Vec::from(ct.as_slice());
+    let bad_tag = hex!("d9847dbc326a06e988c77ad3863e6083");
+    aes.decrypt_detached(&mut buf, b"", &nonce, &bad_tag)
+        .expect_err("decryption tag verification should fail");
+    // assert that the buffer does not change if tag verification fails
+    assert_eq!(buf, ct);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc74<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC74
+    let test_key = hex!("29d3a44f8723dc640239100c365423a312934ac80239212ac3df3421a2098123");
+    let nonce = hex!("00112233445566778899aabb");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("");
+    let ad = hex!("aabbccddeeff");
+    let tag = hex!("2a7d77fa526b8250cb296078926b5020");
+    let result = aes.encrypt(&msg, &ad, &nonce).expect("should succeed");
+    assert_eq!(&result[..], &tag);
+    assert_eq!(A::TAG_SIZE, result.len());
+    let result = aes.decrypt(&result, &ad, &nonce).expect("should succeed");
+    assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc74_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC74
+    let test_key = hex!("29d3a44f8723dc640239100c365423a312934ac80239212ac3df3421a2098123");
+    let nonce = hex!("00112233445566778899aabb");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("");
+    let ad = hex!("aabbccddeeff");
+    let ct = hex!("");
+    let tag = hex!("2a7d77fa526b8250cb296078926b5020");
+    let mut buf = Vec::new();
+    buf.extend_from_slice(&msg);
+    let actual_tag = aes.encrypt_detached(&mut buf, &ad, &nonce).unwrap();
+    assert_eq!(&buf, &ct);
+    assert_eq!(&actual_tag, &tag);
+    assert_eq!(A::TAG_SIZE, tag.len());
+    assert!(aes.decrypt_detached(&mut buf, &ad, &nonce, &actual_tag).is_ok());
+    assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc79<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC79
+    let test_key = hex!("59d4eafb4de0cfc7d3db99a8f54b15d7b39f0acc8da69763b019c1699f87674a");
+    let nonce = hex!("2fcb1b38a99e71b84740ad9b");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("549b365af913f3b081131ccb6b825588");
+    let ct = hex!("f58c16690122d75356907fd96b570fca");
+    let tag = hex!("28752c20153092818faba2a334640d6e");
+    let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..16], &ct);
+    assert_eq!(&result[16..], &tag);
+    let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc79_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC79
+    let test_key = hex!("59d4eafb4de0cfc7d3db99a8f54b15d7b39f0acc8da69763b019c1699f87674a");
+    let nonce = hex!("2fcb1b38a99e71b84740ad9b");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("549b365af913f3b081131ccb6b825588");
+    let ct = hex!("f58c16690122d75356907fd96b570fca");
+    let tag = hex!("28752c20153092818faba2a334640d6e");
+    let mut buf = Vec::from(msg.as_slice());
+    let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+    assert_eq!(&buf, &ct);
+    assert_eq!(&actual_tag, &tag);
+    assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok());
+    assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption where the tag given to decryption doesn't match.
+pub fn aes_256_gcm_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+    // TC94
+    let test_key = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
+    let nonce = hex!("505152535455565758595a5b");
+    let aes = A::new(&test_key.into());
+    let aad = hex!("");
+    let ct = hex!("b2061457c0759fc1749f174ee1ccadfa");
+    let bad_tag = hex!("9de8fef6d8ab1bf1bf887232eab590dd");
+    let mut buf = Vec::from(ct.as_slice());
+    aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag)
+        .expect_err("Decrypting with bad tag should fail");
+    // assert that the buffer does not change if tag verification fails
+    assert_eq!(buf, ct);
+}
+
+/// Generates the test cases to validate the AES-128-GCM implementation.
+/// For example, to test `MyAesGcm128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_gcm_test_cases)]
+///     fn aes_128_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcmImpl>) {
+///         testcase(MyAesGcm128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_128_gcm_test_encrypt)]
+#[case::decrypt(aes_128_gcm_test_decrypt)]
+fn aes_128_gcm_test_cases<F: AesGcmFactory<Key = Aes128Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-128-GCM implementation.
+/// For example, to test `MyAesGcm128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_gcm_test_cases_detached)]
+///     fn aes_128_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcmImpl>) {
+///         testcase(MyAesGcm128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt_detached(aes_128_gcm_test_encrypt_detached)]
+#[case::decrypt_detached(aes_128_gcm_test_decrypt_detached)]
+#[case::decrypt_detached_bad_tag(aes_128_gcm_test_decrypt_detached_bad_tag)]
+fn aes_128_gcm_test_cases_detached<F: AesGcmFactory<Key = Aes128Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256-GCM implementation.
+/// For example, to test `MyAesGcm256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_gcm_test_cases)]
+///     fn aes_256_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcm256Impl>) {
+///         testcase(MyAesGcm256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::tc74(aes_256_gcm_test_tc74)]
+#[case::tc79(aes_256_gcm_test_tc79)]
+fn aes_256_gcm_test_cases<F: AesGcmFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256-GCM implementation.
+/// For example, to test `MyAesGcm256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_gcm_test_cases_detached)]
+///     fn aes_256_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcm256Impl>) {
+///         testcase(MyAesGcm256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::tc74_detached(aes_256_gcm_test_tc74_detached)]
+#[case::tc79_detached(aes_256_gcm_test_tc79_detached)]
+#[case::decrypt_detached_bad_tag(aes_256_gcm_test_decrypt_detached_bad_tag)]
+fn aes_256_gcm_test_cases_detached<F: AesGcmFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
index 893fba2..56d2215 100644
--- a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
+++ b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
@@ -18,12 +18,28 @@
 use rstest_reuse::template;
 
 pub use crate::prelude;
-use crypto_provider::aes::{Aes128Key, Aes256Key};
+use crypto_provider::aead::{AeadInit, AesGcmSiv};
 
-use crypto_provider::aead::aes_gcm_siv::AesGcmSiv;
+/// Test AES-GCM-SIV-128 encryption
+pub fn aes_128_gcm_siv_test_encrypt<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // 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 tag = hex!("dc20e2d83f25705bb49e439eca56de25");
+    let result = aes.encrypt(&msg, b"", &nonce).expect("Should succeed");
+    assert_eq!(&result[..], &tag);
+}
 
-/// Test AES-GCM-SIV-128 encryption/decryption
-pub fn aes_128_gcm_siv_test<A: AesGcmSiv<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+pub fn aes_128_gcm_siv_test_encrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
     // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
     // TC1
     let test_key = hex!("01000000000000000000000000000000");
@@ -32,23 +48,102 @@
     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);
+    let actual_tag: [u8; 16] = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+    assert_eq!(&buf, &[0_u8; 0]);
+    assert_eq!(&actual_tag, &tag);
+}
+
+/// Test AES-GCM-SIV-128 decryption
+pub fn aes_128_gcm_siv_test_decrypt<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
     // TC2
+    let test_key = hex!("01000000000000000000000000000000");
+    let nonce = hex!("030000000000000000000000");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("0100000000000000");
+    let ct = hex!("b5d839330ac7b786");
+    let tag = hex!("578782fff6013b815b287c22493a364c");
+    let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..8], &ct);
+    assert_eq!(&result[8..], &tag);
+    assert_eq!(A::TAG_SIZE, result[8..].len());
+    let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-GCM-SIV-128 decryption
+pub fn aes_128_gcm_siv_test_decrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+    // TC2
+    let test_key = hex!("01000000000000000000000000000000");
+    let nonce = hex!("030000000000000000000000");
+    let aes = A::new(&test_key.into());
     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_eq!(A::TAG_SIZE, buf[8..].len());
-    assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
+    let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+    assert_eq!(&buf, &ct);
+    assert_eq!(actual_tag, tag);
+    assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok());
     assert_eq!(&buf[..], &msg);
 }
 
+/// Test AES-GCM-SIV-128 decryption where the tag given to decryption doesn't match
+pub fn aes_128_gcm_siv_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+    // TC45
+    let test_key = hex!("00112233445566778899aabbccddeeff");
+    let nonce = hex!("000000000000000000000000");
+    let aad = hex!("9ea3371e258288d5a01b15384e2c99ee");
+    let aes = A::new(&test_key.into());
+    // Use a longer ciphertext as the test case to make sure it's not unchanged only for the most
+    // recent block.
+    let ct = hex!(
+        "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
+        "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
+    );
+    let bad_tag = hex!("13a1883272188b4c8d2727178198fe95");
+    let mut buf = Vec::from(ct.as_slice());
+    aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag).expect_err("Decryption should fail");
+    assert_eq!(&buf, &ct); // Buffer should be unchanged if decryption failed
+}
+
 /// Test AES-256-GCM-SIV encryption/decryption
-pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+pub fn aes_256_gcm_siv_test_tc77<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // 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 ct = hex!("c2ef328e5c71c83b");
+    let tag = hex!("843122130f7364b761e0b97427e3df28");
+    let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..8], &ct);
+    assert_eq!(&result[8..], &tag);
+    assert_eq!(A::TAG_SIZE, result[8..].len());
+    let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test_tc77_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
     // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
     // TC77
     let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
@@ -59,22 +154,72 @@
     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_eq!(A::TAG_SIZE, buf[8..].len());
-    assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
-    assert_eq!(&buf[..], &msg);
+    let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+    assert_eq!(&buf, &ct);
+    assert_eq!(&actual_tag, &tag);
+    assert_eq!(A::TAG_SIZE, tag.len());
+    assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &actual_tag).is_ok());
+    assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test_tc78<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
     // TC78
+    let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
+    let nonce = hex!("030000000000000000000000");
+    let aes = A::new(&test_key.into());
+    let msg = hex!("010000000000000000000000");
+    let ct = hex!("9aab2aeb3faa0a34aea8e2b1");
+    let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e");
+    let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..12], &ct);
+    assert_eq!(&result[12..], &tag);
+    let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+    assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test_tc78_detached<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+    // TC78
+    let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
+    let nonce = hex!("030000000000000000000000");
+    let aes = A::new(&test_key.into());
     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);
+    let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+    assert_eq!(&buf, &ct);
+    assert_eq!(&actual_tag, &tag);
+    assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok());
+    assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption where the tag given to decryption doesn't match.
+pub fn aes_256_gcm_siv_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+    A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+    // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+    // TC122
+    let test_key = hex!("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff");
+    let nonce = hex!("000000000000000000000000");
+    let aes = A::new(&test_key.into());
+    let aad = hex!("0289eaa93eb084107d2088435ef2a0cd");
+    let ct = hex!("ffffffffffffffff");
+    let bad_tag = hex!("ffffffffffffffffffffffffffffffff");
+    let mut buf = Vec::from(ct.as_slice());
+    aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag)
+        .expect_err("Decrypting with bad tag should fail");
+    assert_eq!(&buf, &ct); // The buffer should be unchanged if the decryption failed
 }
 
 /// Generates the test cases to validate the AES-128-GCM-SIV implementation.
@@ -93,13 +238,37 @@
 #[template]
 #[export]
 #[rstest]
-#[case::encrypt(aes_128_gcm_siv_test)]
-#[case::decrypt(aes_128_gcm_siv_test)]
+#[case::encrypt(aes_128_gcm_siv_test_encrypt)]
+#[case::decrypt(aes_128_gcm_siv_test_decrypt)]
 fn aes_128_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes128Key>>(
     #[case] testcase: CryptoProviderTestCase<F>,
 ) {
 }
 
+/// Generates the test cases to validate the AES-128-GCM-SIV implementation.
+/// For example, to test `MyAesGcmSiv128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm_siv::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_128_gcm_siv_test_cases_detached)]
+///     fn aes_128_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSivImpl>) {
+///         testcase(MyAesGcmSiv128Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt_detached(aes_128_gcm_siv_test_encrypt_detached)]
+#[case::decrypt_detached(aes_128_gcm_siv_test_decrypt_detached)]
+#[case::decrypt_detached_bad_tag(aes_128_gcm_siv_test_decrypt_detached_bad_tag)]
+fn aes_128_gcm_siv_test_cases_detached<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`:
 ///
@@ -116,9 +285,33 @@
 #[template]
 #[export]
 #[rstest]
-#[case::encrypt(aes_256_gcm_siv_test)]
-#[case::decrypt(aes_256_gcm_siv_test)]
+#[case::tc77(aes_256_gcm_siv_test_tc77)]
+#[case::tc78(aes_256_gcm_siv_test_tc78)]
 fn aes_256_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes256Key>>(
     #[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::aes_gcm_siv::testing::*;
+///
+/// mod tests {
+///     #[apply(aes_256_gcm_siv_test_cases_detached)]
+///     fn aes_256_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSiv256Impl>) {
+///         testcase(MyAesGcmSiv256Impl);
+///     }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::tc77_detached(aes_256_gcm_siv_test_tc77_detached)]
+#[case::tc78_detached(aes_256_gcm_siv_test_tc78_detached)]
+#[case::decrypt_detached_bad_tag(aes_256_gcm_siv_test_decrypt_detached_bad_tag)]
+fn aes_256_gcm_siv_test_cases_detached<F: AesGcmSivFactory<Key = Aes256Key>>(
+    #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/aead/mod.rs b/nearby/crypto/crypto_provider_test/src/aead/mod.rs
index 962aa49..3fe857c 100644
--- a/nearby/crypto/crypto_provider_test/src/aead/mod.rs
+++ b/nearby/crypto/crypto_provider_test/src/aead/mod.rs
@@ -14,3 +14,6 @@
 
 /// Contains test cases for aes_gcm_siv implementations.
 pub mod aes_gcm_siv;
+
+/// Contains test cases for aes_gcm implementations.
+pub mod aes_gcm;
diff --git a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
index b22c828..46cbebe 100644
--- a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
@@ -15,13 +15,17 @@
 use crate::aes::Aes256Key;
 pub use crate::prelude::*;
 use core::marker::PhantomData;
-use crypto_provider::aes::cbc::{AesCbcIv, AesCbcPkcs7Padded};
+use crypto_provider::{
+    aes::cbc::{AesCbcIv, AesCbcPkcs7Padded},
+    tinyvec::SliceVec,
+};
 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
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+    // tcId: 132
     let key: Aes256Key =
         hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
     let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
@@ -30,9 +34,47 @@
     assert_eq!(A::encrypt(&key, &iv, &msg), expected_ciphertext);
 }
 
+/// Tests for AES-256-CBC in-place encryption
+pub fn aes_256_cbc_test_encrypt_in_place<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+    // tcId: 132
+    let key: Aes256Key =
+        hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
+    let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
+    let msg = hex!("3f56935def3f");
+    let expected_ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
+    let mut msg_buffer_backing = [0_u8; 16];
+    let mut msg_buffer = SliceVec::from_slice_len(&mut msg_buffer_backing, 0);
+    msg_buffer.extend_from_slice(&msg);
+    A::encrypt_in_place(&key, &iv, &mut msg_buffer).unwrap();
+    assert_eq!(msg_buffer.as_slice(), &expected_ciphertext);
+}
+
+/// Tests for AES-256-CBC encryption, where the given buffer `SliceVec` is too short to contain the
+/// output.
+pub fn aes_256_cbc_test_encrypt_in_place_too_short<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1612
+    // tcId: 144
+    let key: Aes256Key =
+        hex!("4f097858a1aec62cf18f0966b2b120783aa4ae9149d3213109740506ae47adfe").into();
+    let iv: AesCbcIv = hex!("400aab92803bcbb44a96ef789655b34e");
+    let msg = hex!("ee53d8e5039e82d9fcca114e375a014febfea117a7e709d9008d43858e3660");
+    let mut msg_buffer_backing = [0_u8; 31];
+    let mut msg_buffer = SliceVec::from_slice_len(&mut msg_buffer_backing, 0);
+    msg_buffer.extend_from_slice(&msg);
+    A::encrypt_in_place(&key, &iv, &mut msg_buffer)
+        .expect_err("Encrypting AES with 15-byte buffer should fail");
+    // Buffer content is undefined, but test to make sure it doesn't contain half-decrypted data
+    assert!(
+        msg_buffer.as_slice() == [0_u8; 32] || msg_buffer.as_slice() == msg,
+        "Unrecognized content in buffer after decryption failure"
+    )
+}
+
 /// 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
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+    // tcId: 132
     let key: Aes256Key =
         hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
     let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
@@ -41,6 +83,52 @@
     assert_eq!(A::decrypt(&key, &iv, &ciphertext).unwrap(), expected_msg);
 }
 
+/// Tests for AES-256-CBC decryption with bad padding
+pub fn aes_256_cbc_test_decrypt_bad_padding<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1690
+    // tcId: 151
+    let key: Aes256Key =
+        hex!("7c78f34dbce8f0557d43630266f59babd1cb92ba624bd1a8f45a2a91c84a804a").into();
+    let iv: AesCbcIv = hex!("f010f61c31c9aa8fa0d5be5f6b0f2f70");
+    let ciphertext = hex!("8881e9e02fa9e3037b397957ba1fb7ce64679a46621b792f643542a735f0bbbf");
+    A::decrypt(&key, &iv, &ciphertext).expect_err("Decryption with bad padding should fail");
+}
+
+/// Tests for AES-256-CBC in-place decryption
+pub fn aes_256_cbc_test_decrypt_in_place<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+    // tcId: 132
+    let key: Aes256Key =
+        hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
+    let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
+    let mut ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
+    let expected_msg = hex!("3f56935def3f");
+    let mut msg_buffer = SliceVec::from(&mut ciphertext);
+    A::decrypt_in_place(&key, &iv, &mut msg_buffer).unwrap();
+    assert_eq!(msg_buffer.as_slice(), expected_msg);
+}
+
+/// Tests for AES-256-CBC in-place decryption with bad padding
+pub fn aes_256_cbc_test_decrypt_in_place_bad_padding<A: AesCbcPkcs7Padded>(
+    _marker: PhantomData<A>,
+) {
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1690
+    // tcId: 151
+    let key: Aes256Key =
+        hex!("7c78f34dbce8f0557d43630266f59babd1cb92ba624bd1a8f45a2a91c84a804a").into();
+    let iv: AesCbcIv = hex!("f010f61c31c9aa8fa0d5be5f6b0f2f70");
+    let ciphertext = hex!("8881e9e02fa9e3037b397957ba1fb7ce64679a46621b792f643542a735f0bbbf");
+    let mut msg_buffer_backing = ciphertext;
+    let mut msg_buffer = SliceVec::from(&mut msg_buffer_backing);
+    A::decrypt_in_place(&key, &iv, &mut msg_buffer)
+        .expect_err("Decryption with bad padding should fail");
+    // Buffer content is undefined, but test to make sure it doesn't contain half-decrypted data
+    assert!(
+        msg_buffer.as_slice() == [0_u8; 32] || msg_buffer.as_slice() == ciphertext,
+        "Unrecognized content in buffer after decryption failure"
+    )
+}
+
 /// Generates the test cases to validate the AES-256-CBC implementation.
 /// For example, to test `MyAesCbc256Impl`:
 ///
@@ -59,5 +147,9 @@
 #[export]
 #[rstest]
 #[case::encrypt(aes_256_cbc_test_encrypt)]
+#[case::encrypt_in_place(aes_256_cbc_test_encrypt_in_place)]
+#[case::encrypt_in_place_too_short(aes_256_cbc_test_encrypt_in_place_too_short)]
 #[case::decrypt(aes_256_cbc_test_decrypt)]
+#[case::decrypt_bad_padding(aes_256_cbc_test_decrypt_bad_padding)]
+#[case::decrypt_in_place_bad_padding(aes_256_cbc_test_decrypt_in_place_bad_padding)]
 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
index b1d6b8b..de71740 100644
--- a/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
@@ -27,22 +27,22 @@
     let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
 
     block = hex!("6bc1bee22e409f96e93d7e117393172a");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_1 = hex!("874d6191b620e3261bef6864990db6ce");
     assert_eq!(expected_ciphertext_1, block);
 
     block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_2 = hex!("9806f66b7970fdff8617187bb9fffdff");
     assert_eq!(expected_ciphertext_2, block);
 
     block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_3 = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
     assert_eq!(expected_ciphertext_3, block);
 
     block = hex!("f69f2445df4f9b17ad2b417be66c3710");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_3 = hex!("1e031dda2fbe03d1792170a0f3009cee");
     assert_eq!(expected_ciphertext_3, block);
 }
@@ -56,22 +56,22 @@
     let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
 
     block = hex!("874d6191b620e3261bef6864990db6ce");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
     assert_eq!(expected_plaintext_1, block);
 
     block = hex!("9806f66b7970fdff8617187bb9fffdff");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
     assert_eq!(expected_plaintext_2, block);
 
     block = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
     assert_eq!(expected_plaintext_3, block);
 
     block = hex!("1e031dda2fbe03d1792170a0f3009cee");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
     assert_eq!(expected_plaintext_3, block);
 }
@@ -86,22 +86,22 @@
     let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
 
     block = hex!("6bc1bee22e409f96e93d7e117393172a");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_1 = hex!("601ec313775789a5b7a7f504bbf3d228");
     assert_eq!(expected_ciphertext_1, block);
 
     block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_2 = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
     assert_eq!(expected_ciphertext_2, block);
 
     block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_3 = hex!("2b0930daa23de94ce87017ba2d84988d");
     assert_eq!(expected_ciphertext_3, block);
 
     block = hex!("f69f2445df4f9b17ad2b417be66c3710");
-    cipher.encrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_ciphertext_3 = hex!("dfc9c58db67aada613c2dd08457941a6");
     assert_eq!(expected_ciphertext_3, block);
 }
@@ -116,22 +116,22 @@
     let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
 
     block = hex!("601ec313775789a5b7a7f504bbf3d228");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
     assert_eq!(expected_plaintext_1, block);
 
     block = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
     assert_eq!(expected_plaintext_2, block);
 
     block = hex!("2b0930daa23de94ce87017ba2d84988d");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
     assert_eq!(expected_plaintext_3, block);
 
     block = hex!("dfc9c58db67aada613c2dd08457941a6");
-    cipher.decrypt(&mut block);
+    cipher.apply_keystream(&mut block);
     let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
     assert_eq!(expected_plaintext_3, block);
 }
diff --git a/nearby/crypto/crypto_provider_test/src/ed25519.rs b/nearby/crypto/crypto_provider_test/src/ed25519.rs
index d99605c..0fdb484 100644
--- a/nearby/crypto/crypto_provider_test/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_test/src/ed25519.rs
@@ -18,7 +18,9 @@
 use alloc::borrow::ToOwned;
 use alloc::string::String;
 use alloc::vec::Vec;
-use crypto_provider::ed25519::{Ed25519Provider, KeyPair, PublicKey, Signature};
+use crypto_provider::ed25519::{
+    Ed25519Provider, KeyPair, PublicKey, RawPrivateKeyPermit, RawSignature, Signature,
+};
 use wycheproof::TestResult;
 
 // These are test vectors from the creators of Ed25519: https://ed25519.cr.yp.to/ which are referenced
@@ -27,7 +29,7 @@
 // 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";
+    "crypto/crypto_provider_test/src/testdata/EdDSA/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>
@@ -39,10 +41,7 @@
         .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;
-
+        let public_key = test_group.key.pk.to_vec();
         for test in test_group.tests {
             let tc_id = test.tc_id;
             let comment = test.comment;
@@ -53,8 +52,7 @@
                 TestResult::Invalid => false,
                 TestResult::Valid | TestResult::Acceptable => true,
             };
-            let result =
-                run_test::<E>(public_key.clone(), secret_key.clone(), sig.clone(), msg.clone());
+            let result = run_wycheproof_test::<E>(public_key.to_vec(), sig.to_vec(), msg.to_vec());
             if valid {
                 if let Err(desc) = result {
                     panic!(
@@ -73,6 +71,20 @@
     }
 }
 
+fn run_wycheproof_test<E>(pub_key: Vec<u8>, sig: Vec<u8>, msg: Vec<u8>) -> Result<(), &'static str>
+where
+    E: Ed25519Provider,
+{
+    let pub_key = E::PublicKey::from_bytes(pub_key.as_slice().try_into().unwrap())
+        .map_err(|_| "Invalid public key bytes")?;
+
+    let raw_sig: RawSignature =
+        sig.as_slice().try_into().map_err(|_| "Invalid length signature")?;
+    let signature = E::Signature::from_bytes(&raw_sig);
+
+    pub_key.verify_strict(msg.as_slice(), &signature).map_err(|_| "Signature verification failed")
+}
+
 /// Runs the RFC specified test vectors against an Ed25519 implementation
 pub fn run_rfc_test_vectors<E>()
 where
@@ -126,7 +138,10 @@
 {
     let private_key_bytes: [u8; 32] =
         private_key.as_slice().try_into().expect("Secret key is the wrong length");
-    let kp = E::KeyPair::from_private_key(&private_key_bytes);
+
+    // Permits: Test-only code, not a production leak of the private key
+    let permit = RawPrivateKeyPermit::default();
+    let kp = E::KeyPair::from_raw_private_key(&private_key_bytes, &permit);
 
     let sig_result = kp.sign(msg.as_slice());
     (sig.as_slice() == sig_result.to_bytes()).then_some(()).ok_or("sig not matching expected")?;
diff --git a/nearby/crypto/crypto_provider_test/src/lib.rs b/nearby/crypto/crypto_provider_test/src/lib.rs
index 8e63f44..2209a3e 100644
--- a/nearby/crypto/crypto_provider_test/src/lib.rs
+++ b/nearby/crypto/crypto_provider_test/src/lib.rs
@@ -36,9 +36,11 @@
 /// Common items that needs to be imported to use these test cases
 pub mod prelude {
     pub use super::CryptoProviderTestCase;
+    pub use ::rstest;
     pub use rstest::rstest;
     pub use rstest_reuse;
     pub use rstest_reuse::apply;
+    pub extern crate std;
 }
 
 /// A test case for Crypto Provider. A test case is a function that panics if the test fails.
diff --git a/nearby/crypto/crypto_provider_test/src/p256.rs b/nearby/crypto/crypto_provider_test/src/p256.rs
index 9869c9e..5ebbd50 100644
--- a/nearby/crypto/crypto_provider_test/src/p256.rs
+++ b/nearby/crypto/crypto_provider_test/src/p256.rs
@@ -12,12 +12,12 @@
 // 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 core::ops::Deref;
+use crypto_provider::p256::{P256PublicKey, PointCompression, P256};
 use crypto_provider::{
     elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
     CryptoRng,
@@ -62,9 +62,35 @@
              8bbe76c6dc1643088107636deff8aa79e8002a157b92"
     );
     let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+    // Not part of the API contract, but `to_bytes()` should prefer to use uncompressed
+    // representation since support for compressed point is optional.
+    let key_bytes = key.to_bytes();
+    assert_eq!(sec1_bytes.to_vec(), key_bytes.deref());
+}
+
+/// Test for P256PublicKey::to_sec1_bytes(Compressed). Support for compressed representation is
+/// optional.
+pub fn to_bytes_compressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    let sec1_bytes = hex!(
+        "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+    );
+    let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+    let key_bytes = key.to_sec1_bytes(PointCompression::Compressed);
     let sec1_bytes_compressed =
         hex!("02756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
-    assert_eq!(sec1_bytes_compressed.to_vec(), key.to_bytes());
+    assert_eq!(sec1_bytes_compressed.to_vec(), key_bytes.deref());
+}
+
+/// Test for P256PublicKey::to_sec1_bytes(Uncompressed)
+pub fn to_bytes_uncompressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+    let sec1_bytes = hex!(
+        "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+             8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+    );
+    let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+    let key_bytes = key.to_sec1_bytes(PointCompression::Uncompressed);
+    assert_eq!(sec1_bytes.to_vec(), key_bytes.deref());
 }
 
 /// Random test for P256PublicKey::to_bytes
@@ -75,7 +101,7 @@
                 P256,
             >>::Rng::new())
             .public_key_bytes();
-        let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+        let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap();
         assert_eq!(
             E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
             public_key,
@@ -192,7 +218,7 @@
 /// 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
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/ecdh_secp256r1_ecpoint_test.json#L22
     // sec1 public key manually extracted from the ASN encoded test data
     let public_key_sec1 = hex!(
         "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f
@@ -230,20 +256,24 @@
             };
             let result = p256_ecdh_test_impl::<E>(
                 &test.public_key,
-                &test.private_key.try_into().expect("Private key should be 32 bytes long"),
+                &test
+                    .private_key
+                    .as_slice()
+                    .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());
+                    assert_eq!(test.shared_secret.as_slice(), 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());
+                        assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
                     }
                     // Test passes if `result` is an error because this test is "acceptable"
                 }
@@ -268,22 +298,43 @@
 #[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_sec1_bytes_not_on_infinity(from_sec1_bytes_at_infinity_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::to_bytes(to_bytes_test, "to_bytes")]
+#[case::to_bytes_compressed(to_bytes_compressed_test, "to_bytes_compressed")]
+#[case::to_bytes_uncompressed(to_bytes_uncompressed_test, "to_bytes_uncompressed")]
+#[case::to_bytes_random(to_bytes_random_test, "to_bytes_random")]
+#[case::from_sec1_bytes_not_on_curve(
+    from_sec1_bytes_not_on_curve_test,
+    "from_sec1_bytes_not_on_curve"
+)]
+#[case::from_sec1_bytes_not_on_infinity(
+    from_sec1_bytes_at_infinity_test,
+    "from_sec1_bytes_not_on_infinity"
+)]
+#[case::from_affine_coordinates(from_affine_coordinates_test, "from_affine_coordinates")]
+#[case::from_affine_coordinates_not_on_curve(
+    from_affine_coordinates_not_on_curve_test,
+    "from_affine_coordinates_not_on_curve"
+)]
+#[case::public_key_to_affine_coordinates(
+    public_key_to_affine_coordinates_test,
+    "public_key_to_affine_coordinates"
+)]
 #[case::public_key_to_affine_coordinates_compressed02(
-    public_key_to_affine_coordinates_compressed02_test
+    public_key_to_affine_coordinates_compressed02_test,
+    "public_key_to_affine_coordinates_compressed02"
 )]
 #[case::public_key_to_affine_coordinates_compressed03(
-    public_key_to_affine_coordinates_compressed03_test
+    public_key_to_affine_coordinates_compressed03_test,
+    "public_key_to_affine_coordinates_compressed03"
 )]
 #[case::public_key_to_affine_coordinates_zero_top_byte(
-    public_key_to_affine_coordinates_zero_top_byte_test
+    public_key_to_affine_coordinates_zero_top_byte_test,
+    "public_key_to_affine_coordinates_zero_top_byte"
 )]
-#[case::p256_ecdh(p256_ecdh_test)]
-#[case::wycheproof_p256(wycheproof_p256_test)]
-fn p256_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
+#[case::p256_ecdh(p256_ecdh_test, "p256_ecdh")]
+#[case::wycheproof_p256(wycheproof_p256_test, "wycheproof_p256")]
+fn p256_test_cases<C: CryptoProvider>(
+    #[case] testcase: CryptoProviderTestCase<C>,
+    #[case] name: &str,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/sha2.rs b/nearby/crypto/crypto_provider_test/src/sha2.rs
index 330f284..66a3025 100644
--- a/nearby/crypto/crypto_provider_test/src/sha2.rs
+++ b/nearby/crypto/crypto_provider_test/src/sha2.rs
@@ -14,7 +14,7 @@
 
 extern crate alloc;
 extern crate std;
-pub use crate::prelude::*;
+use crate::prelude::*;
 use crate::CryptoProvider;
 use alloc::vec::Vec;
 use core::{marker::PhantomData, str::FromStr};
diff --git a/nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt b/nearby/crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt
similarity index 100%
rename from nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt
rename to nearby/crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt
diff --git a/nearby/crypto/crypto_provider_test/src/x25519.rs b/nearby/crypto/crypto_provider_test/src/x25519.rs
index 0fcaa12..1fe2019 100644
--- a/nearby/crypto/crypto_provider_test/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_test/src/x25519.rs
@@ -58,7 +58,7 @@
 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());
+    assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes().as_ref());
 }
 
 /// Random test for `PublicKey<X25519>::to_bytes`
@@ -69,9 +69,9 @@
                 X25519,
             >>::Rng::new())
             .public_key_bytes();
-        let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+        let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap();
         assert_eq!(
-            E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
+            E::PublicKey::from_bytes(public_key.to_bytes().as_ref()).unwrap(),
             public_key,
             "from_bytes should return the same key for `{public_key_bytes:?}`",
         );
@@ -81,7 +81,7 @@
 /// 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
+    // https://github.com/google/wycheproof/blob/b063b4a/testvectors/x25519_test.json#L23
     // sec1 public key manually extracted from the ASN encoded test data
     let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
     let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475");
@@ -109,20 +109,24 @@
         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"),
+                &test
+                    .private_key
+                    .as_slice()
+                    .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());
+                    assert_eq!(&test.shared_secret.as_slice(), &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());
+                        assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
                     }
                     // Test passes if `result` is an error because this test is "acceptable"
                 }