Project import generated by Copybara.

GitOrigin-RevId: 89e6b51eb1f772d98e85f45fc0d5c7fe23230c16
Change-Id: Icf50d4604bba16d6925221eb14132e6e8057dac8
diff --git a/nearby/.gitignore b/nearby/.gitignore
index b7d2235..49cb45e 100644
--- a/nearby/.gitignore
+++ b/nearby/.gitignore
@@ -3,3 +3,4 @@
 /*.mdb
 /auth_token.txt
 .DS_Store
+presence/cmake-build-debug
diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock
index 657ac34..dd4f6ff 100644
--- a/nearby/Cargo.lock
+++ b/nearby/Cargo.lock
@@ -780,17 +780,6 @@
 ]
 
 [[package]]
-name = "derive-getters"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0122f262bf9c9a367829da84f808d9fb128c10ef283bbe7b0922a77cf07b2747"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
 name = "diff"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1138,12 +1127,6 @@
 ]
 
 [[package]]
-name = "init_with"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0175f63815ce00183bf755155ad0cb48c65226c5d17a724e369c25418d2b7699"
-
-[[package]]
 name = "inout"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1386,7 +1369,6 @@
  "crypto_provider",
  "crypto_provider_default",
  "hex",
- "init_with",
  "lazy_static",
  "ldt",
  "ldt_np_adv",
@@ -1407,6 +1389,17 @@
 ]
 
 [[package]]
+name = "np_adv_dynamic"
+version = "0.1.0"
+dependencies = [
+ "array_view",
+ "crypto_provider",
+ "np_adv",
+ "sink",
+ "thiserror",
+]
+
+[[package]]
 name = "np_ed25519"
 version = "0.1.0"
 dependencies = [
@@ -1425,8 +1418,10 @@
  "crypto_provider_default",
  "handle_map",
  "lazy_static",
+ "ldt_np_adv",
  "lock_adapter",
  "np_adv",
+ "np_hkdf",
 ]
 
 [[package]]
@@ -2307,7 +2302,6 @@
 dependencies = [
  "crypto_provider",
  "crypto_provider_default",
- "derive-getters",
  "log",
  "num-bigint",
  "rand",
diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml
index df92f64..774e1d8 100644
--- a/nearby/Cargo.toml
+++ b/nearby/Cargo.toml
@@ -20,6 +20,7 @@
     "presence/ldt_np_jni",
     "presence/ldt_tbc",
     "presence/np_adv",
+    "presence/np_adv_dynamic",
     "presence/np_ed25519",
     "presence/np_ffi_core",
     "presence/np_hkdf",
@@ -40,6 +41,22 @@
     "presence/np_c_ffi",
 ]
 
+[workspace.lints.rust]
+unsafe_code = "deny"
+missing_docs = "deny"
+trivial_casts = "deny"
+trivial_numeric_casts = "deny"
+unused_extern_crates = "deny"
+unused_import_braces = "deny"
+unused_results = "deny"
+
+[workspace.lints.clippy]
+indexing_slicing = "deny"
+unwrap_used = "deny"
+panic = "deny"
+expect_used = "deny"
+
+
 [workspace.dependencies]
 # local crates
 array_ref = { path = "presence/array_ref" }
@@ -63,6 +80,7 @@
 ldt_np_adv = { path = "presence/ldt_np_adv" }
 ldt_tbc = { path = "presence/ldt_tbc" }
 np_adv = { path = "presence/np_adv" }
+np_adv_dynamic = { path = "presence/np_adv_dynamic" }
 np_ed25519 = { path = "presence/np_ed25519" }
 np_ffi_core = { path = "presence/np_ffi_core" }
 sink = { path = "presence/sink" }
@@ -122,6 +140,7 @@
 strum = { version = "0.25.0", default-features=false }
 strum_macros = { version = "0.25.3", default-features=false }
 owo-colors = "3.5.0"
+rhai = { version = "1.16.3", features = ["sync"] }
 
 [workspace.package]
 version = "0.1.0"
diff --git a/nearby/connections/connections_adv/connections_adv/Cargo.toml b/nearby/connections/connections_adv/connections_adv/Cargo.toml
index ff17ad2..2b0710d 100644
--- a/nearby/connections/connections_adv/connections_adv/Cargo.toml
+++ b/nearby/connections/connections_adv/connections_adv/Cargo.toml
@@ -4,6 +4,5 @@
 edition.workspace = true
 publish.workspace = true
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
+[lints]
+workspace = true
\ No newline at end of file
diff --git a/nearby/connections/connections_adv/connections_adv/src/lib.rs b/nearby/connections/connections_adv/connections_adv/src/lib.rs
index 4f7198b..4cfcdfa 100644
--- a/nearby/connections/connections_adv/connections_adv/src/lib.rs
+++ b/nearby/connections/connections_adv/connections_adv/src/lib.rs
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! Placeholder crate for connections advertisements
+
+/// placeholder
 pub fn add(left: usize, right: usize) -> usize {
     left + right
 }
diff --git a/nearby/connections/ukey2/ukey2/Cargo.toml b/nearby/connections/ukey2/ukey2/Cargo.toml
index afae71e..161c5b4 100644
--- a/nearby/connections/ukey2/ukey2/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 test_rustcrypto = ["crypto_provider_default/rustcrypto"]
@@ -16,7 +19,6 @@
 ukey2_proto.workspace = true
 log.workspace = true
 
-derive-getters = "0.2.0"
 num-bigint = "0.4.3"
 
 [dev-dependencies]
diff --git a/nearby/connections/ukey2/ukey2/src/lib.rs b/nearby/connections/ukey2/ukey2/src/lib.rs
index d8f2270..dc17b83 100644
--- a/nearby/connections/ukey2/ukey2/src/lib.rs
+++ b/nearby/connections/ukey2/ukey2/src/lib.rs
@@ -18,15 +18,6 @@
 //! establish a secure channel.
 //!
 //! For a full description of the protocol, see <https://github.com/google/ukey2>.
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    trivial_casts,
-    trivial_numeric_casts,
-    unused_extern_crates,
-    unused_import_braces,
-    unused_results
-)]
 
 mod proto_adapter;
 mod state_machine;
diff --git a/nearby/connections/ukey2/ukey2/src/proto_adapter.rs b/nearby/connections/ukey2/ukey2/src/proto_adapter.rs
index de3c4b9..2986276 100644
--- a/nearby/connections/ukey2/ukey2/src/proto_adapter.rs
+++ b/nearby/connections/ukey2/ukey2/src/proto_adapter.rs
@@ -17,7 +17,6 @@
 use crypto_provider::elliptic_curve::EcdhProvider;
 use crypto_provider::p256::{P256EcdhProvider, P256PublicKey, P256};
 use crypto_provider::CryptoProvider;
-use derive_getters::Getters;
 use ukey2_proto::ukey2_all_proto::{securemessage, ukey};
 
 /// For generated proto types for UKEY2 messages
@@ -79,23 +78,44 @@
     ClientFinish,
 }
 
-#[derive(Getters)]
 pub(crate) struct ClientInit {
     version: i32,
     commitments: Vec<CipherCommitment>,
     next_protocol: String,
 }
 
+impl ClientInit {
+    pub fn version(&self) -> i32 {
+        self.version
+    }
+
+    pub fn commitments(&self) -> &[CipherCommitment] {
+        &self.commitments
+    }
+
+    pub fn next_protocol(&self) -> &str {
+        &self.next_protocol
+    }
+}
+
 #[allow(dead_code)]
-#[derive(Getters)]
 pub(crate) struct ServerInit {
     version: i32,
     random: [u8; 32],
     handshake_cipher: HandshakeCipher,
-    #[getter(skip)]
     pub(crate) public_key: Vec<u8>,
 }
 
+impl ServerInit {
+    pub fn version(&self) -> i32 {
+        self.version
+    }
+
+    pub fn handshake_cipher(&self) -> HandshakeCipher {
+        self.handshake_cipher
+    }
+}
+
 pub(crate) struct ClientFinished {
     pub(crate) public_key: Vec<u8>,
 }
@@ -119,12 +139,22 @@
     }
 }
 
-#[derive(Clone, Getters)]
+#[derive(Clone)]
 pub(crate) struct CipherCommitment {
     cipher: HandshakeCipher,
     commitment: Vec<u8>,
 }
 
+impl CipherCommitment {
+    pub fn cipher(&self) -> HandshakeCipher {
+        self.cipher
+    }
+
+    pub fn commitment(&self) -> &[u8] {
+        &self.commitment
+    }
+}
+
 pub(crate) enum GenericPublicKey<C: CryptoProvider> {
     Ec256(<C::P256 as EcdhProvider<P256>>::PublicKey),
     // Other public key types are not supported
@@ -256,6 +286,7 @@
 /// representation. If the input byte array is not positive or cannot be fit into 32 byte unsigned
 /// int range, then `None` is returned.
 fn positive_twos_complement_to_32_byte_unsigned(twos_complement: &[u8]) -> Option<[u8; 32]> {
+    #[allow(clippy::indexing_slicing)]
     if !twos_complement.is_empty() && (twos_complement[0] & 0x80) == 0 {
         let mut twos_complement_iter = twos_complement.iter().rev();
         let mut result = [0_u8; 32];
diff --git a/nearby/connections/ukey2/ukey2/src/state_machine.rs b/nearby/connections/ukey2/ukey2/src/state_machine.rs
index c7cefc3..d2021e6 100644
--- a/nearby/connections/ukey2/ukey2/src/state_machine.rs
+++ b/nearby/connections/ukey2/ukey2/src/state_machine.rs
@@ -43,7 +43,8 @@
             error_message: self.msg,
             ..Default::default()
         };
-        alert_message.to_wrapped_msg().write_to_bytes().unwrap()
+        #[allow(clippy::expect_used)]
+        alert_message.to_wrapped_msg().write_to_bytes().expect("Writing to proto should succeed")
     }
 }
 
diff --git a/nearby/connections/ukey2/ukey2/src/tests.rs b/nearby/connections/ukey2/ukey2/src/tests.rs
index 65274a6..92c0358 100644
--- a/nearby/connections/ukey2/ukey2/src/tests.rs
+++ b/nearby/connections/ukey2/ukey2/src/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use crate::{
     proto_adapter::{IntoAdapter as _, MessageType, ToWrappedMessage as _},
     ukey2_handshake::HandshakeCipher,
diff --git a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
index 70db4a3..0b2be0e 100644
--- a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
+++ b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
@@ -12,6 +12,9 @@
 // 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.
+#![allow(clippy::expect_used)]
+// TODO: remove this and convert all unwraps to expects
+#![allow(clippy::unwrap_used)]
 
 pub(crate) use crate::proto_adapter::{
     CipherCommitment, ClientFinished, ClientInit, GenericPublicKey, HandshakeCipher,
@@ -89,7 +92,7 @@
                 HandshakeCipher::P256Sha512 => {
                     let p256_key =
                         <C::P256 as P256EcdhProvider>::PublicKey::from_bytes(key.as_slice())
-                            .unwrap();
+                            .expect("");
                     let (x, y) = p256_key.to_affine_coordinates().unwrap();
                     let bigboi_x = num_bigint::BigInt::from_biguint(
                         num_bigint::Sign::Plus,
@@ -167,7 +170,7 @@
         client_init: ClientInit,
         client_init_msg_bytes: Vec<u8>,
     ) -> Result<Ukey2ServerStage2<C>, ClientInitError> {
-        if client_init.version() != &1 {
+        if client_init.version() != 1 {
             return Err(ClientInitError::BadVersion);
         }
 
@@ -188,7 +191,7 @@
             // proto enum uses the priority as the numeric value
             .max_by_key(|c| c.cipher().as_proto() as i32)
             .ok_or(ClientInitError::BadHandshakeCipher)?;
-        match *commitment.cipher() {
+        match commitment.cipher() {
             // pick in priority order
             HandshakeCipher::Curve25519Sha512 => {
                 let secret = ServerKeyPair::Curve25519(
@@ -474,7 +477,7 @@
         server_init: ServerInit,
         server_init_bytes: Vec<u8>,
     ) -> Result<Ukey2Client, ServerInitError> {
-        if server_init.version() != &1 {
+        if server_init.version() != 1 {
             return Err(ServerInitError::BadVersion);
         }
 
@@ -483,7 +486,7 @@
             .commitment_ciphers
             .iter()
             .fold(None, |accum, c| {
-                if server_init.handshake_cipher() == c {
+                if server_init.handshake_cipher() == *c {
                     match accum {
                         None => Some(c),
                         Some(_) => accum,
diff --git a/nearby/connections/ukey2/ukey2/tests/tests.rs b/nearby/connections/ukey2/ukey2/tests/tests.rs
index 2f51a8c..85b972c 100644
--- a/nearby/connections/ukey2/ukey2/tests/tests.rs
+++ b/nearby/connections/ukey2/ukey2/tests/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use crypto_provider_default::CryptoProviderImpl;
 use rand::{rngs::StdRng, SeedableRng};
 use std::collections::hash_set;
diff --git a/nearby/connections/ukey2/ukey2_connections/Cargo.toml b/nearby/connections/ukey2/ukey2_connections/Cargo.toml
index 8337e96..e7884c4 100644
--- a/nearby/connections/ukey2/ukey2_connections/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_connections/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 test_boringssl = ["crypto_provider_default/boringssl"]
diff --git a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
index e48400a..f816165 100644
--- a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
+++ b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs, clippy::expect_used, clippy::unwrap_used)]
+
 use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
 use rand::{Rng, SeedableRng};
 
@@ -65,10 +67,10 @@
     let (mut initiator_ctx, mut server_ctx) =
         run_handshake_with_rng::<CryptoProviderImpl, _>(rand::rngs::StdRng::from_entropy());
     for len in [10 * kib, 1024 * kib] {
-        group.throughput(Throughput::Bytes(len as u64));
+        let _ = group.throughput(Throughput::Bytes(len as u64));
         plaintext.resize(len, 0);
         rand::thread_rng().fill(&mut plaintext[..]);
-        group.bench_function(format!("UKEY2 encrypt/decrypt {}KiB", len / kib), |b| {
+        let _ = group.bench_function(format!("UKEY2 encrypt/decrypt {}KiB", len / kib), |b| {
             b.iter(|| {
                 let msg = initiator_ctx
                     .encode_message_to_peer::<CryptoProviderImpl, &[u8]>(&plaintext, None);
diff --git a/nearby/connections/ukey2/ukey2_connections/src/lib.rs b/nearby/connections/ukey2/ukey2_connections/src/lib.rs
index 3a441bb..b3ed937 100644
--- a/nearby/connections/ukey2/ukey2_connections/src/lib.rs
+++ b/nearby/connections/ukey2/ukey2_connections/src/lib.rs
@@ -24,7 +24,9 @@
 //! from the handshake context once the handshake is complete, and controls the encryption and
 //! decryption of the payload messages.
 
-#![deny(missing_docs)]
+#![allow(clippy::expect_used)]
+//TODO: remove this and fix instances of unwrap
+#![allow(clippy::unwrap_used, clippy::panic)]
 
 mod crypto_utils;
 mod d2d_connection_context_v1;
diff --git a/nearby/connections/ukey2/ukey2_connections/src/tests.rs b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
index 32a6b28..d6acecc 100644
--- a/nearby/connections/ukey2/ukey2_connections/src/tests.rs
+++ b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::indexing_slicing)]
+
 use crypto_provider::CryptoProvider;
 use crypto_provider_default::CryptoProviderImpl;
 use rand::SeedableRng;
diff --git a/nearby/connections/ukey2/ukey2_jni/Cargo.toml b/nearby/connections/ukey2/ukey2_jni/Cargo.toml
index 4852444..08566dd 100644
--- a/nearby/connections/ukey2/ukey2_jni/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_jni/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
diff --git a/nearby/connections/ukey2/ukey2_jni/src/lib.rs b/nearby/connections/ukey2/ukey2_jni/src/lib.rs
index 82d317e..b7764fa 100644
--- a/nearby/connections/ukey2/ukey2_jni/src/lib.rs
+++ b/nearby/connections/ukey2/ukey2_jni/src/lib.rs
@@ -12,6 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! JNI bindings for the ukey2 rust implementation
+
+#![allow(unsafe_code, clippy::expect_used)]
+//TODO: remove this and fix instances of unwrap/panic
+#![allow(clippy::unwrap_used, clippy::panic)]
+
 use std::collections::HashMap;
 
 use jni::objects::{JByteArray, JClass};
@@ -52,14 +58,28 @@
 }
 
 pub(crate) fn insert_handshake_handle(item: D2DBox) -> u64 {
-    let handle = generate_handle();
-    HANDLE_MAPPING.lock().insert(handle, item);
+    let mut handle = generate_handle();
+    let map = HANDLE_MAPPING.lock();
+    while map.contains_key(&handle) {
+        handle = generate_handle();
+    }
+
+    let result = HANDLE_MAPPING.lock().insert(handle, item);
+    // result should always be None since we checked that handle map does not contain the key already
+    assert!(result.is_none());
     handle
 }
 
 pub(crate) fn insert_conn_handle(item: ConnectionBox) -> u64 {
-    let handle = generate_handle();
-    CONNECTION_HANDLE_MAPPING.lock().insert(handle, item);
+    let mut handle = generate_handle();
+    let map = CONNECTION_HANDLE_MAPPING.lock();
+    while map.contains_key(&handle) {
+        handle = generate_handle();
+    }
+
+    let result = CONNECTION_HANDLE_MAPPING.lock().insert(handle, item);
+    // result should always be None since we checked that handle map does not contain the key already
+    assert!(result.is_none());
     handle
 }
 
@@ -71,7 +91,8 @@
     HandshakeError(HandshakeError),
 }
 
-// D2DHandshakeContext
+/// Tells the caller whether the handshake has completed or not. If the handshake is complete,
+/// the caller may call `to_connection_context`to obtain a connection context.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_is_1handshake_1complete(
     mut env: JNIEnv,
@@ -88,6 +109,7 @@
     is_complete as jboolean
 }
 
+/// Creates a new handshake context
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_create_1context(
     _: JNIEnv,
@@ -107,6 +129,7 @@
     }
 }
 
+/// Constructs the next message that should be sent in the handshake.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_get_1next_1handshake_1message(
     mut env: JNIEnv,
@@ -130,9 +153,10 @@
     .into_raw()
 }
 
-#[no_mangle]
+/// Parses a handshake message and advances the internal state of the context.
+// Safety: We know the message pointer is safe as it is coming directly from the JVM.
 #[allow(clippy::not_unsafe_ptr_arg_deref)]
-/// Safety: We know the message pointer is safe as it is coming directly from the JVM.
+#[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_parse_1handshake_1message(
     mut env: JNIEnv,
     _: JClass,
@@ -163,6 +187,8 @@
     }
 }
 
+/// Returns the `CompletedHandshake` using the results from this handshake context. May only
+/// be called if `is_handshake_complete` returns true.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_get_1verification_1string(
     mut env: JNIEnv,
@@ -201,6 +227,8 @@
     .into_raw()
 }
 
+/// Creates a [`D2DConnectionContextV1`] using the results of the handshake. May only be called
+/// if `is_handshake_complete` returns true.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_to_1connection_1context(
     mut env: JNIEnv,
@@ -224,16 +252,17 @@
         .expect("failed to find error class");
         return -1;
     } else {
-        HANDLE_MAPPING.lock().remove(&(context_handle as u64));
+        let _ = HANDLE_MAPPING.lock().remove(&(context_handle as u64));
     }
     insert_conn_handle(Box::new(conn_context.unwrap())) as jlong
 }
 
-// D2DConnectionContextV1
-#[no_mangle]
+/// Once initiator and responder have exchanged public keys, use this method to encrypt and
+/// sign a payload. Both initiator and responder devices can use this message.
+// Safety: We know the payload and associated_data pointers are safe as they are coming directly
+// from the JVM.
 #[allow(clippy::not_unsafe_ptr_arg_deref)]
-/// Safety: We know the payload and associated_data pointers are safe as they are coming directly
-/// from the JVM.
+#[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_encode_1message_1to_1peer(
     mut env: JNIEnv,
     _: JClass,
@@ -271,10 +300,13 @@
     .into_raw()
 }
 
-#[no_mangle]
+/// Once `InitiatorHello` and `ResponderHello` (and payload) are exchanged, use this method to
+/// decrypt and verify a message received from the other device. Both initiator and responder
+/// devices can use this message.
+// Safety: We know the message and associated_data pointers are safe as they are coming directly
+// from the JVM.
 #[allow(clippy::not_unsafe_ptr_arg_deref)]
-/// Safety: We know the message and associated_data pointers are safe as they are coming directly
-/// from the JVM.
+#[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_decode_1message_1from_1peer(
     mut env: JNIEnv,
     _: JClass,
@@ -322,6 +354,7 @@
     .into_raw()
 }
 
+/// Returns the last sequence number used to encode a message.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_get_1sequence_1number_1for_1encoding(
     mut env: JNIEnv,
@@ -329,14 +362,15 @@
     context_handle: jlong,
 ) -> jint {
     if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
-        ctx.get_sequence_number_for_encoding() as jint
+        ctx.get_sequence_number_for_encoding()
     } else {
         env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "")
             .expect("failed to find error class");
-        -1 as jint
+        -1
     }
 }
 
+/// Returns the last sequence number used to decode a message.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_get_1sequence_1number_1for_1decoding(
     mut env: JNIEnv,
@@ -344,14 +378,16 @@
     context_handle: jlong,
 ) -> jint {
     if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) {
-        ctx.get_sequence_number_for_decoding() as jint
+        ctx.get_sequence_number_for_decoding()
     } else {
         env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "")
             .expect("failed to find error class");
-        -1 as jint
+        -1
     }
 }
 
+/// Creates a saved session that can later be used for resumption. The session data may be
+/// persisted, but it must be stored in a secure location.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_save_1session(
     mut env: JNIEnv,
@@ -369,9 +405,10 @@
     .into_raw()
 }
 
+/// Creates a connection context from a saved session.
+// Safety: We know the session_info pointer is safe because it is coming directly from the JVM.
 #[no_mangle]
 #[allow(clippy::not_unsafe_ptr_arg_deref)]
-/// Safety: We know the session_info pointer is safe because it is coming directly from the JVM.
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_from_1saved_1session(
     mut env: JNIEnv,
     _: JClass,
@@ -399,6 +436,9 @@
     insert_conn_handle(conn_context_final) as jlong
 }
 
+/// Returns a cryptographic digest (SHA256) of the session keys prepended by the SHA256 hash
+/// of the ASCII string "D2D". Since the server and client share the same session keys, the
+/// resulting session unique is also the same.
 #[no_mangle]
 pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_get_1session_1unique(
     mut env: JNIEnv,
diff --git a/nearby/connections/ukey2/ukey2_shell/Cargo.toml b/nearby/connections/ukey2/ukey2_shell/Cargo.toml
index d1f48e4..9a2856d 100644
--- a/nearby/connections/ukey2/ukey2_shell/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_shell/Cargo.toml
@@ -4,11 +4,11 @@
 edition.workspace = true
 publish.workspace = true
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lints]
+workspace = true
 
 [dependencies]
 crypto_provider_rustcrypto = { workspace = true, features = [ "alloc" ] }
 ukey2_rs = { version = "0.1.0", path = "../ukey2" }
 ukey2_connections = { version = "0.1.0", path = "../ukey2_connections" }
-
 clap = { version = "4.0.17", default-features = false, features = ["std", "derive"] }
diff --git a/nearby/connections/ukey2/ukey2_shell/src/main.rs b/nearby/connections/ukey2/ukey2_shell/src/main.rs
index e4bcc0d..3eca7b0 100644
--- a/nearby/connections/ukey2/ukey2_shell/src/main.rs
+++ b/nearby/connections/ukey2/ukey2_shell/src/main.rs
@@ -12,6 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! Provides a sample ukey2 shell app which can be run from the command line
+
+#![allow(clippy::expect_used)]
+//TODO: remove this and fix instances of unwrap
+#![allow(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing)]
+
 use std::io::{Read, Write};
 use std::process::exit;
 
@@ -191,9 +197,9 @@
     let args = Ukey2Cli::parse();
     let shell = Ukey2Shell::new(args.verification_string_length);
     if args.mode == MODE_INITIATOR {
-        shell.run_as_initiator();
+        let _ = shell.run_as_initiator();
     } else if args.mode == MODE_RESPONDER {
-        shell.run_as_responder();
+        let _ = shell.run_as_responder();
     } else {
         exit(1);
     }
diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml
index e7dd8ba..1a588bb 100644
--- a/nearby/crypto/crypto_provider/Cargo.toml
+++ b/nearby/crypto/crypto_provider/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 tinyvec.workspace = true
 
diff --git a/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs b/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs
index f401327..70c65cf 100644
--- a/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs
+++ b/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs, clippy::indexing_slicing)]
+
 use criterion::{
     criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup, Criterion,
 };
@@ -33,7 +35,7 @@
     fn add_benches<C: CryptoProvider>(group: &mut BenchmarkGroup<WallTime>, rng: &mut ThreadRng) {
         const TEST_LEN: usize = 1000;
         for i in (0..=TEST_LEN).step_by(100) {
-            group.bench_function(
+            let _ = group.bench_function(
                 &format!(
                     "constant_time_eq impl {} differ by {:04} bytes",
                     std::any::type_name::<C>(),
diff --git a/nearby/crypto/crypto_provider/benches/hkdf_bench.rs b/nearby/crypto/crypto_provider/benches/hkdf_bench.rs
index 4c11d0d..e73f8cf 100644
--- a/nearby/crypto/crypto_provider/benches/hkdf_bench.rs
+++ b/nearby/crypto/crypto_provider/benches/hkdf_bench.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs, clippy::expect_used)]
+
 use criterion::{criterion_group, criterion_main, Criterion};
 use hex_literal::hex;
 
@@ -26,15 +28,16 @@
     let salt = hex!("000102030405060708090a0b0c");
     let info = hex!("f0f1f2f3f4f5f6f7f8f9");
 
-    c.bench_function(&format!("bench hkdf with salt {}", std::any::type_name::<C>()), |b| {
-        b.iter(|| {
-            let hk = C::HkdfSha256::new(Some(&salt[..]), &ikm);
-            let mut okm = [0u8; 42];
-            hk.expand(&info, &mut okm).expect("42 is a valid length for Sha256 to output");
+    let _ =
+        c.bench_function(&format!("bench hkdf with salt {}", std::any::type_name::<C>()), |b| {
+            b.iter(|| {
+                let hk = C::HkdfSha256::new(Some(&salt[..]), &ikm);
+                let mut okm = [0u8; 42];
+                hk.expand(&info, &mut okm).expect("42 is a valid length for Sha256 to output");
+            });
         });
-    });
 
-    c.bench_function(&format!("bench hkdf no salt {}", std::any::type_name::<C>()), |b| {
+    let _ = c.bench_function(&format!("bench hkdf no salt {}", std::any::type_name::<C>()), |b| {
         b.iter(|| {
             let hk = C::HkdfSha256::new(None, &ikm);
             let mut okm = [0u8; 42];
diff --git a/nearby/crypto/crypto_provider/benches/hmac_bench.rs b/nearby/crypto/crypto_provider/benches/hmac_bench.rs
index c7ccf1a..9bcfde0 100644
--- a/nearby/crypto/crypto_provider/benches/hmac_bench.rs
+++ b/nearby/crypto/crypto_provider/benches/hmac_bench.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs)]
+
 use criterion::{criterion_group, criterion_main, Criterion};
 
 use crypto_provider::hmac::Hmac;
@@ -26,7 +28,7 @@
     let key: [u8; 32] = rand_ext::random_bytes::<32, C>(&mut rng);
     let update_data: [u8; 16] = rand_ext::random_bytes::<16, C>(&mut rng);
 
-    c.bench_function("bench for hmac sha256 single update", |b| {
+    let _ = c.bench_function("bench for hmac sha256 single update", |b| {
         b.iter(|| {
             let mut hmac = C::HmacSha256::new_from_key(key);
             hmac.update(&update_data);
@@ -40,7 +42,7 @@
     let key: [u8; 64] = rand_ext::random_bytes::<64, C>(&mut rng);
     let update_data: [u8; 16] = random_bytes::<16, C>(&mut rng);
 
-    c.bench_function("bench for hmac sha512 single update", |b| {
+    let _ = c.bench_function("bench for hmac sha512 single update", |b| {
         b.iter(|| {
             let mut hmac = C::HmacSha512::new_from_key(key);
             hmac.update(&update_data);
diff --git a/nearby/crypto/crypto_provider/src/aes/cbc.rs b/nearby/crypto/crypto_provider/src/aes/cbc.rs
index 59ce52d..82a857b 100644
--- a/nearby/crypto/crypto_provider/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider/src/aes/cbc.rs
@@ -29,6 +29,7 @@
 pub trait AesCbcPkcs7Padded {
     /// Calculate the padded output length (e.g. output of `encrypt`) from the unpadded length (e.g.
     /// input message of `encrypt`).
+    #[allow(clippy::expect_used)]
     fn padded_output_len(unpadded_len: usize) -> usize {
         (unpadded_len - (unpadded_len % 16))
             .checked_add(16)
@@ -109,7 +110,7 @@
     #[test]
     #[should_panic]
     fn test_padded_output_len_overflow() {
-        AesCbcPkcs7PaddedStub::padded_output_len(usize::MAX);
+        let _ = AesCbcPkcs7PaddedStub::padded_output_len(usize::MAX);
     }
 
     struct AesCbcPkcs7PaddedStub;
diff --git a/nearby/crypto/crypto_provider/src/lib.rs b/nearby/crypto/crypto_provider/src/lib.rs
index 0031ebb..214175d 100644
--- a/nearby/crypto/crypto_provider/src/lib.rs
+++ b/nearby/crypto/crypto_provider/src/lib.rs
@@ -11,12 +11,11 @@
 // 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.'
-#![no_std]
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
 
 //! Crypto abstraction trait only crate, which provides traits for cryptographic primitives
 
+#![no_std]
+
 use core::fmt::Debug;
 
 use crate::aes::{Aes128Key, Aes256Key};
diff --git a/nearby/crypto/crypto_provider_default/Cargo.toml b/nearby/crypto/crypto_provider_default/Cargo.toml
index 2a155bf..4c906e4 100644
--- a/nearby/crypto/crypto_provider_default/Cargo.toml
+++ b/nearby/crypto/crypto_provider_default/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 crypto_provider.workspace = true
 crypto_provider_rustcrypto = {workspace = true, optional = true}
diff --git a/nearby/crypto/crypto_provider_default/src/lib.rs b/nearby/crypto/crypto_provider_default/src/lib.rs
index b880ba8..52da9d4 100644
--- a/nearby/crypto/crypto_provider_default/src/lib.rs
+++ b/nearby/crypto/crypto_provider_default/src/lib.rs
@@ -16,8 +16,6 @@
 //! feature flag.
 
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
 
 cfg_if::cfg_if! {
     if #[cfg(feature = "rustcrypto")] {
diff --git a/nearby/crypto/crypto_provider_openssl/Cargo.toml b/nearby/crypto/crypto_provider_openssl/Cargo.toml
index 84d4380..19ef3e4 100644
--- a/nearby/crypto/crypto_provider_openssl/Cargo.toml
+++ b/nearby/crypto/crypto_provider_openssl/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 crypto_provider = { workspace = true, features = ["alloc", "std"] }
 crypto_provider_stubs.workspace = true
diff --git a/nearby/crypto/crypto_provider_openssl/src/aes.rs b/nearby/crypto/crypto_provider_openssl/src/aes.rs
index c2ac5b1..857be74 100644
--- a/nearby/crypto/crypto_provider_openssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/aes.rs
@@ -71,7 +71,7 @@
         let mut crypter =
             Crypter::new(Cipher::aes_128_ecb(), Mode::Encrypt, self.0.as_slice(), None).unwrap();
         crypter.pad(false);
-        crypter.update(block, &mut output).unwrap();
+        let _ = crypter.update(block, &mut output).unwrap();
         block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]);
     }
 }
@@ -83,7 +83,7 @@
         let mut crypter =
             Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, self.0.as_slice(), None).unwrap();
         crypter.pad(false);
-        crypter.update(block, &mut output).unwrap();
+        let _ = crypter.update(block, &mut output).unwrap();
         block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]);
     }
 }
@@ -106,7 +106,7 @@
         let mut crypter =
             Crypter::new(Cipher::aes_256_ecb(), Mode::Encrypt, self.0.as_slice(), None).unwrap();
         crypter.pad(false);
-        crypter.update(block, &mut output).unwrap();
+        let _ = crypter.update(block, &mut output).unwrap();
         block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]);
     }
 }
@@ -118,7 +118,7 @@
         let mut crypter =
             Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, self.0.as_slice(), None).unwrap();
         crypter.pad(false);
-        crypter.update(block, &mut output).unwrap();
+        let _ = crypter.update(block, &mut output).unwrap();
         block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]);
     }
 }
diff --git a/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs b/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs
index d429a4d..b726f5f 100644
--- a/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs
@@ -42,7 +42,9 @@
         let md = H::get_md();
         ctx.derive_init().expect("hkdf derive init should not fail");
         ctx.set_hkdf_md(md).expect("hkdf set md should not fail");
-        self.salt.as_ref().map(|salt| ctx.set_hkdf_salt(salt.as_slice()));
+        let _ = self.salt.as_ref().map(|salt| {
+            ctx.set_hkdf_salt(salt.as_slice()).expect("setting the salt is infallible")
+        });
         ctx.set_hkdf_key(self.ikm.as_slice()).expect("should be able to set key");
         ctx.add_hkdf_info(&info_components.concat()).expect("should be able to add info");
         ctx.derive(Some(okm)).map_err(|_| InvalidLength).map(|_| ())
diff --git a/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs b/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs
index 9b86726..5272995 100644
--- a/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs
@@ -50,7 +50,7 @@
 
     fn finalize(mut self) -> [u8; N] {
         let mut buf = [0_u8; N];
-        self.ctx.finalize(&mut buf).expect("wrong length");
+        let _ = self.ctx.finalize(&mut buf).expect("wrong length");
         buf
     }
 
diff --git a/nearby/crypto/crypto_provider_openssl/src/lib.rs b/nearby/crypto/crypto_provider_openssl/src/lib.rs
index 8d1db1d..04b4e4c 100644
--- a/nearby/crypto/crypto_provider_openssl/src/lib.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/lib.rs
@@ -12,10 +12,13 @@
 // 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.
-#![deny(missing_docs, clippy::indexing_slicing, clippy::panic)]
 
 //! Crate which provides impls for CryptoProvider backed by openssl
 
+// This crate treats allocation errors as handleable, which leads to unwraps everywhere, so they
+// have to be allowed here. This will be fixed when we can migrate over to the new boringssl bindings
+#![allow(clippy::expect_used, clippy::unwrap_used)]
+
 use cfg_if::cfg_if;
 pub use openssl;
 use openssl::hash::MessageDigest;
diff --git a/nearby/crypto/crypto_provider_openssl/src/sha2.rs b/nearby/crypto/crypto_provider_openssl/src/sha2.rs
index 92e663e..68fee79 100644
--- a/nearby/crypto/crypto_provider_openssl/src/sha2.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/sha2.rs
@@ -38,7 +38,8 @@
         mdctx.digest_init(Self::get_md()).unwrap();
         mdctx.digest_update(input).unwrap();
         let mut buf = [0_u8; 32];
-        mdctx.digest_final(&mut buf).unwrap();
+        let size = mdctx.digest_final(&mut buf).unwrap();
+        debug_assert_eq!(size, 32);
         buf
     }
 }
@@ -63,7 +64,8 @@
         mdctx.digest_init(Self::get_md()).unwrap();
         mdctx.digest_update(input).unwrap();
         let mut buf = [0_u8; 64];
-        mdctx.digest_final(&mut buf).unwrap();
+        let size = mdctx.digest_final(&mut buf).unwrap();
+        debug_assert_eq!(size, 64);
         buf
     }
 }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
index a0638ff..14e8ad9 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
+++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 aead = "0.5.1"
 aes-gcm-siv = { version = "0.11.1", features = [
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
index 628f679..2985dec 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
@@ -11,15 +11,8 @@
 // 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.
+
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 //! Crate which provides impls for CryptoProvider backed by RustCrypto crates
 
diff --git a/nearby/crypto/rand_core_05_adapter/Cargo.toml b/nearby/crypto/rand_core_05_adapter/Cargo.toml
index 0a8fc47..1a02526 100644
--- a/nearby/crypto/rand_core_05_adapter/Cargo.toml
+++ b/nearby/crypto/rand_core_05_adapter/Cargo.toml
@@ -4,9 +4,10 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 rand.workspace = true
 #  an older rand_core used by ed25519-dalek so we can adapt newer rand to it
 rand_core05 = { package = "rand_core", version = "0.5.1" }
-
-[dev-dependencies]
diff --git a/nearby/crypto/rand_core_05_adapter/src/lib.rs b/nearby/crypto/rand_core_05_adapter/src/lib.rs
index 6484983..9de0020 100644
--- a/nearby/crypto/rand_core_05_adapter/src/lib.rs
+++ b/nearby/crypto/rand_core_05_adapter/src/lib.rs
@@ -13,15 +13,8 @@
 // limitations under the License.
 
 //! Adapter for using rand_core 0.5 RNGs with code that expects rand_core 0.5 RNGs.
+
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 /// A trivial adapter to expose rand 1.0/rand_core 0.6 rngs to ed25519-dalek's rand_core 0.5 types,
 /// which we import under a separate name so there's no clash.
diff --git a/nearby/presence/array_ref/Cargo.toml b/nearby/presence/array_ref/Cargo.toml
index f912c41..b74168b 100644
--- a/nearby/presence/array_ref/Cargo.toml
+++ b/nearby/presence/array_ref/Cargo.toml
@@ -4,8 +4,10 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
 
-[dependencies]
diff --git a/nearby/presence/array_ref/src/lib.rs b/nearby/presence/array_ref/src/lib.rs
index 3ff84fc..769d3ef 100644
--- a/nearby/presence/array_ref/src/lib.rs
+++ b/nearby/presence/array_ref/src/lib.rs
@@ -12,8 +12,6 @@
 // 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.
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
 
 //! Crate exposing macros to take array references of slices
 
diff --git a/nearby/presence/array_view/Cargo.toml b/nearby/presence/array_view/Cargo.toml
index 48693c4..8ac53d2 100644
--- a/nearby/presence/array_view/Cargo.toml
+++ b/nearby/presence/array_view/Cargo.toml
@@ -4,11 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
-
-[dependencies]
-
-[dev-dependencies]
-
diff --git a/nearby/presence/array_view/src/lib.rs b/nearby/presence/array_view/src/lib.rs
index c57e959..e2ea8d4 100644
--- a/nearby/presence/array_view/src/lib.rs
+++ b/nearby/presence/array_view/src/lib.rs
@@ -14,14 +14,6 @@
 
 //! A no_std friendly array wrapper to expose a variable length prefix of the array.
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 #[cfg(feature = "std")]
 extern crate std;
diff --git a/nearby/presence/ldt/Cargo.toml b/nearby/presence/ldt/Cargo.toml
index 2ee7b4c..337dd0d 100644
--- a/nearby/presence/ldt/Cargo.toml
+++ b/nearby/presence/ldt/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
@@ -13,7 +16,7 @@
 ldt_tbc.workspace = true
 
 [dev-dependencies]
-crypto_provider_default = {workspace = true, features = ["rustcrypto"]}
+crypto_provider_default = { workspace = true, features = ["rustcrypto"] }
 rand_ext.workspace = true
 test_helper.workspace = true
 xts_aes.workspace = true
@@ -21,7 +24,7 @@
 rand.workspace = true
 rand_pcg.workspace = true
 base64.workspace = true
-serde_json = {workspace = true, features = ["std"]}
+serde_json = { workspace = true, features = ["std"] }
 anyhow.workspace = true
 hex.workspace = true
 
diff --git a/nearby/presence/ldt/benches/ldt_scan.rs b/nearby/presence/ldt/benches/ldt_scan.rs
index dac1374..b1711ad 100644
--- a/nearby/presence/ldt/benches/ldt_scan.rs
+++ b/nearby/presence/ldt/benches/ldt_scan.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs, unused_results, clippy::indexing_slicing, clippy::unwrap_used)]
+
 use criterion::{black_box, criterion_group, criterion_main, Criterion};
 use crypto_provider::{CryptoProvider, CryptoRng};
 use crypto_provider_rustcrypto::RustCrypto;
diff --git a/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs b/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs
index 517e7ec..7e7c943 100644
--- a/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs
+++ b/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! Generates LDT test vectors which can be used to verify implementations
+
+#![allow(clippy::unwrap_used)]
+
 use crypto_provider::aes::BLOCK_SIZE;
 use crypto_provider::{aes, CryptoProvider, CryptoRng};
 use crypto_provider_rustcrypto::RustCrypto;
diff --git a/nearby/presence/ldt/examples/ldt_benchmark.rs b/nearby/presence/ldt/examples/ldt_benchmark.rs
index df87c28..101e17b 100644
--- a/nearby/presence/ldt/examples/ldt_benchmark.rs
+++ b/nearby/presence/ldt/examples/ldt_benchmark.rs
@@ -14,6 +14,8 @@
 
 //! A manual benchmark for more interactive parameter-twiddling.
 
+#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
+
 use clap::Parser as _;
 use crypto_provider_rustcrypto::RustCrypto;
 use ldt::{LdtDecryptCipher, LdtEncryptCipher, LdtKey, Mix, Swap, XorPadder};
diff --git a/nearby/presence/ldt/examples/ldt_prp.rs b/nearby/presence/ldt/examples/ldt_prp.rs
index f8e055c..7734952 100644
--- a/nearby/presence/ldt/examples/ldt_prp.rs
+++ b/nearby/presence/ldt/examples/ldt_prp.rs
@@ -20,6 +20,9 @@
 //!
 //! The output shows how many times a change to the first n bytes wasn't detected, as well as a
 //! histogram of how many bits were flipped in the entire plaintext.
+
+#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
+
 use clap::{self, Parser as _};
 use crypto_provider::aes::BLOCK_SIZE;
 use crypto_provider::{CryptoProvider, CryptoRng};
diff --git a/nearby/presence/ldt/src/lib.rs b/nearby/presence/ldt/src/lib.rs
index 9065e12..8596ad2 100644
--- a/nearby/presence/ldt/src/lib.rs
+++ b/nearby/presence/ldt/src/lib.rs
@@ -15,8 +15,6 @@
 //! Provides an implementation of [LDT](https://eprint.iacr.org/2017/841.pdf).
 
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(clippy::indexing_slicing, clippy::unwrap_used, clippy::panic, clippy::expect_used)]
 
 #[cfg(feature = "std")]
 extern crate std;
diff --git a/nearby/presence/ldt/tests/ldt_roundtrip.rs b/nearby/presence/ldt/tests/ldt_roundtrip.rs
index b5f930e..251cefe 100644
--- a/nearby/presence/ldt/tests/ldt_roundtrip.rs
+++ b/nearby/presence/ldt/tests/ldt_roundtrip.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use crypto_provider::aes::BLOCK_SIZE;
 use crypto_provider::{CryptoProvider, CryptoRng};
 use crypto_provider_default::CryptoProviderImpl;
diff --git a/nearby/presence/ldt/tests/ldt_test_vectors.rs b/nearby/presence/ldt/tests/ldt_test_vectors.rs
index 477243d..03b5d3f 100644
--- a/nearby/presence/ldt/tests/ldt_test_vectors.rs
+++ b/nearby/presence/ldt/tests/ldt_test_vectors.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
+
 use anyhow::anyhow;
 use crypto_provider_default::CryptoProviderImpl;
 use ldt::{DefaultPadder, LdtDecryptCipher, LdtEncryptCipher, LdtKey, Swap, XorPadder};
@@ -26,7 +28,7 @@
     );
     let mut file = fs::File::open(full_path)?;
     let mut data = String::new();
-    file.read_to_string(&mut data)?;
+    let _ = file.read_to_string(&mut data)?;
     let test_cases = match serde_json::de::from_str(&data)? {
         serde_json::Value::Array(a) => a,
         _ => return Err(anyhow!("bad json")),
@@ -73,7 +75,7 @@
         test_helper::get_data_file("presence/ldt/resources/test/ldt-xor-pad-testvectors.json");
     let mut file = fs::File::open(full_path)?;
     let mut data = String::new();
-    file.read_to_string(&mut data)?;
+    let _ = file.read_to_string(&mut data)?;
     let test_cases = match serde_json::de::from_str(&data)? {
         serde_json::Value::Array(a) => a,
         _ => return Err(anyhow!("bad json")),
diff --git a/nearby/presence/ldt/tests/tests.rs b/nearby/presence/ldt/tests/tests.rs
index b9e061a..25938fb 100644
--- a/nearby/presence/ldt/tests/tests.rs
+++ b/nearby/presence/ldt/tests/tests.rs
@@ -70,7 +70,8 @@
 fn normal_pad_too_big_panics() {
     let padder = DefaultPadder;
     let input = [0x99; 16];
-    <DefaultPadder as Padder<16, XtsAes128<CryptoProviderImpl>>>::pad_tweak(&padder, &input);
+    let _ =
+        <DefaultPadder as Padder<16, XtsAes128<CryptoProviderImpl>>>::pad_tweak(&padder, &input);
 }
 
 #[test]
@@ -129,7 +130,7 @@
     let padder = [0x24; BLOCK_SIZE].into();
     // need 1 byte for padding, and 2 more for salt
     let input = [0x99; 16];
-    <XorPadder<BLOCK_SIZE> as Padder<BLOCK_SIZE, XtsAes128<CryptoProviderImpl>>>::pad_tweak(
+    let _ = <XorPadder<BLOCK_SIZE> as Padder<BLOCK_SIZE, XtsAes128<CryptoProviderImpl>>>::pad_tweak(
         &padder, &input,
     );
 }
diff --git a/nearby/presence/ldt_np_adv/Cargo.toml b/nearby/presence/ldt_np_adv/Cargo.toml
index 522e97b..3f19bdd 100644
--- a/nearby/presence/ldt_np_adv/Cargo.toml
+++ b/nearby/presence/ldt_np_adv/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
diff --git a/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs b/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs
index d7d7fad..7c0b4aa 100644
--- a/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs
+++ b/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs)]
+
 use criterion::{black_box, criterion_group, criterion_main, Criterion};
 use ldt_np_adv::*;
 
@@ -30,7 +32,7 @@
     let mut rng = rand_pcg::Pcg64::from_seed(seed);
 
     for &len in &[1_usize, 10, 1000] {
-        c.bench_function(&format!("Scan adv with fresh ciphers/{len}"), |b| {
+        let _ = c.bench_function(&format!("Scan adv with fresh ciphers/{len}"), |b| {
             let configs = random_configs::<C, _>(&mut rng, len);
             let payload_len = rng.gen_range(crypto_provider::aes::BLOCK_SIZE..=LDT_XTS_AES_MAX_LEN);
             let payload = random_vec(&mut rng, payload_len);
@@ -42,7 +44,7 @@
                 black_box(find_matching_item::<C>(&ciphers, salt, &payload))
             });
         });
-        c.bench_function(&format!("Scan adv with existing ciphers/{len}"), |b| {
+        let _ = c.bench_function(&format!("Scan adv with existing ciphers/{len}"), |b| {
             let configs = random_configs::<C, _>(&mut rng, len);
             let payload_len = rng.gen_range(crypto_provider::aes::BLOCK_SIZE..=LDT_XTS_AES_MAX_LEN);
             let payload = random_vec(&mut rng, payload_len);
@@ -64,7 +66,7 @@
     payload: &[u8],
 ) {
     let padder = salt_padder::<16, C>(salt);
-    ciphers
+    let _ = ciphers
         .iter()
         .enumerate()
         .filter_map(|(index, item)| {
diff --git a/nearby/presence/ldt_np_adv/src/lib.rs b/nearby/presence/ldt_np_adv/src/lib.rs
index 31aa50d..542d6a5 100644
--- a/nearby/presence/ldt_np_adv/src/lib.rs
+++ b/nearby/presence/ldt_np_adv/src/lib.rs
@@ -14,14 +14,6 @@
 
 //! Nearby Presence-specific usage of LDT.
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 #[cfg(feature = "std")]
 extern crate std;
diff --git a/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs b/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs
index 90d09b1..cf176dd 100644
--- a/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs
+++ b/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs
@@ -33,7 +33,7 @@
         test_helper::get_data_file("presence/ldt_np_adv/resources/test/np_adv_test_vectors.json");
     let mut file = fs::File::open(full_path)?;
     let mut data = String::new();
-    file.read_to_string(&mut data)?;
+    let _ = file.read_to_string(&mut data)?;
     let test_cases = match serde_json::de::from_str(&data)? {
         serde_json::Value::Array(a) => a,
         _ => return Err(anyhow!("bad json")),
diff --git a/nearby/presence/ldt_np_jni/Cargo.toml b/nearby/presence/ldt_np_jni/Cargo.toml
index 6803d56..2551c14 100644
--- a/nearby/presence/ldt_np_jni/Cargo.toml
+++ b/nearby/presence/ldt_np_jni/Cargo.toml
@@ -4,12 +4,15 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 ldt.workspace = true
 ldt_np_adv.workspace = true
 np_hkdf.workspace = true
 crypto_provider.workspace = true
-crypto_provider_default = {workspace = true, default-features = false}
+crypto_provider_default = { workspace = true, default-features = false }
 
 cfg-if.workspace = true
 jni.workspace = true
diff --git a/nearby/presence/ldt_np_jni/src/lib.rs b/nearby/presence/ldt_np_jni/src/lib.rs
index 538a44e..e232c1e 100644
--- a/nearby/presence/ldt_np_jni/src/lib.rs
+++ b/nearby/presence/ldt_np_jni/src/lib.rs
@@ -23,7 +23,7 @@
 // We are not actually no_std because the jni crate is pulling it in, but at least this enforces
 // that this lib isn't using anything from the std lib
 #![no_std]
-#![deny(missing_docs)]
+#![allow(unsafe_code)]
 
 // Allow using Box in no_std
 extern crate alloc;
@@ -86,6 +86,7 @@
             })?;
 
         let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new(
+            #[allow(clippy::expect_used)]
             key_seed.as_slice().try_into().expect("Length is checked above"),
         );
 
@@ -122,9 +123,11 @@
                 }
             })?;
         let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new(
+            #[allow(clippy::expect_used)]
             key_seed.as_slice().try_into().expect("Length is checked above"),
         );
 
+        #[allow(clippy::expect_used)]
         let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed::<CryptoProviderImpl>(
             &hkdf_key_seed,
             hmac_tag.as_slice().try_into().expect("Length is checked above"),
@@ -252,7 +255,7 @@
     let ret = f(&boxed);
 
     // don't consume the box -- need to keep the handle alive
-    Box::leak(boxed);
+    let _ = Box::leak(boxed);
     ret
 }
 
diff --git a/nearby/presence/ldt_tbc/Cargo.toml b/nearby/presence/ldt_tbc/Cargo.toml
index 0177f87..8941051 100644
--- a/nearby/presence/ldt_tbc/Cargo.toml
+++ b/nearby/presence/ldt_tbc/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
diff --git a/nearby/presence/ldt_tbc/src/lib.rs b/nearby/presence/ldt_tbc/src/lib.rs
index d24da7e..aefeb17 100644
--- a/nearby/presence/ldt_tbc/src/lib.rs
+++ b/nearby/presence/ldt_tbc/src/lib.rs
@@ -12,14 +12,6 @@
 // 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.
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 //! Defining traits for an LDT specific Tweakable Block Cipher
 
diff --git a/nearby/presence/np_adv/Cargo.toml b/nearby/presence/np_adv/Cargo.toml
index 97e12f5..e502c35 100644
--- a/nearby/presence/np_adv/Cargo.toml
+++ b/nearby/presence/np_adv/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 array_view = { path = "../array_view" }
 ldt_np_adv.workspace = true
@@ -29,8 +32,7 @@
 hex.workspace = true
 rand.workspace = true
 rand_ext = { path = "../rand_ext" }
-init_with = "1.1.0"
-serde_json = {workspace = true, features = ["std"]}
+serde_json = { workspace = true, features = ["std"] }
 serde.workspace = true
 anyhow.workspace = true
 test_helper = { path = "../test_helper" }
diff --git a/nearby/presence/np_adv/benches/deser_adv.rs b/nearby/presence/np_adv/benches/deser_adv.rs
index e998047..08fd5c4 100644
--- a/nearby/presence/np_adv/benches/deser_adv.rs
+++ b/nearby/presence/np_adv/benches/deser_adv.rs
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(
+    missing_docs,
+    unused_results,
+    clippy::unwrap_used,
+    clippy::expect_used,
+    clippy::indexing_slicing,
+    clippy::panic
+)]
+
 use core::marker::PhantomData;
 use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
 use crypto_provider::{CryptoProvider, CryptoRng};
diff --git a/nearby/presence/np_adv/src/array_vec.rs b/nearby/presence/np_adv/src/array_vec.rs
index a92ff5b..bcc387e 100644
--- a/nearby/presence/np_adv/src/array_vec.rs
+++ b/nearby/presence/np_adv/src/array_vec.rs
@@ -89,12 +89,14 @@
     /// (i.e., does not allocate), and *O*(*m* \* *n* \* log(*n*)) worst-case,
     /// where the key function is *O*(*m*).
     pub fn sort_unstable_by_key<K: Ord>(&mut self, f: impl Fn(&A) -> K) {
-        self.0.sort_unstable_by_key(|a| f(a.as_ref().unwrap()))
+        self.0.sort_unstable_by_key(|a| {
+            f(a.as_ref().expect("Iterated values in ArrayVecOption should never be None"))
+        })
     }
 
     /// Remove an element, swapping the end of the vec into its place.
     pub fn swap_remove(&mut self, index: usize) -> A {
-        self.0.swap_remove(index).unwrap()
+        self.0.swap_remove(index).expect("since index is is bounds, the value at index will always be Some which is safe to unwrap")
     }
 
     /// Converts this vector into a regular `Vec`, unwrapping all of the
@@ -125,7 +127,7 @@
 impl<A, const N: usize> core::ops::Index<usize> for ArrayVecOption<A, N> {
     type Output = A;
     fn index(&self, index: usize) -> &Self::Output {
-        self.0[index].as_ref().unwrap()
+        self.0[index].as_ref().expect("This panics if provided index is out of bounds")
     }
 }
 
@@ -167,7 +169,7 @@
         assert_eq!(Some(&5_u32), vec.get(4));
         assert_eq!(vec![1_u32, 2, 3, 4, 5], vec.clone().into_vec());
 
-        vec.swap_remove(2);
+        let _ = vec.swap_remove(2);
         assert_eq!(vec![1_u32, 2, 5, 4], vec.clone().into_vec());
     }
 
diff --git a/nearby/presence/np_adv/src/credential/v1.rs b/nearby/presence/np_adv/src/credential/v1.rs
index 7d060f2..386799f 100644
--- a/nearby/presence/np_adv/src/credential/v1.rs
+++ b/nearby/presence/np_adv/src/credential/v1.rs
@@ -18,7 +18,7 @@
     protocol_version_seal, BroadcastCryptoMaterial, DiscoveryCryptoMaterial, ProtocolVersion,
 };
 use crate::MetadataKey;
-use crypto_provider::{aes::Aes128Key, ed25519, CryptoProvider, CryptoRng};
+use crypto_provider::{aes::Aes128Key, ed25519, ed25519::PublicKey, CryptoProvider, CryptoRng};
 use np_hkdf::UnsignedSectionKeys;
 
 /// Cryptographic information about a particular V1 discovery credential
@@ -32,17 +32,17 @@
 }
 impl V1DiscoveryCredential {
     /// Construct a V1 discovery credential from the provided identity data.
-    pub fn new<C: CryptoProvider>(
+    pub fn new(
         key_seed: [u8; 32],
         expected_unsigned_metadata_key_hmac: [u8; 32],
         expected_signed_metadata_key_hmac: [u8; 32],
-        pub_key: np_ed25519::PublicKey<C>,
+        pub_key: ed25519::RawPublicKey,
     ) -> Self {
         Self {
             key_seed,
             expected_unsigned_metadata_key_hmac,
             expected_signed_metadata_key_hmac,
-            pub_key: pub_key.to_bytes(),
+            pub_key,
         }
     }
 
@@ -400,7 +400,7 @@
         let key_seed = self.key_seed();
         let metadata_key = self.metadata_key();
         let pub_key = self.signing_key().derive_public_key::<C::Ed25519>();
-        let pub_key = np_ed25519::PublicKey::<C>::new(pub_key);
+        let pub_key = pub_key.to_bytes();
 
         let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
         let unsigned = hkdf
diff --git a/nearby/presence/np_adv/src/deser_v0_tests.rs b/nearby/presence/np_adv/src/deser_v0_tests.rs
index 93c3b97..f6643de 100644
--- a/nearby/presence/np_adv/src/deser_v0_tests.rs
+++ b/nearby/presence/np_adv/src/deser_v0_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use rand::{seq::SliceRandom as _, SeedableRng as _};
 
 extern crate std;
diff --git a/nearby/presence/np_adv/src/deser_v1_tests.rs b/nearby/presence/np_adv/src/deser_v1_tests.rs
index 903ed3d..cea0063 100644
--- a/nearby/presence/np_adv/src/deser_v1_tests.rs
+++ b/nearby/presence/np_adv/src/deser_v1_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use core::marker::PhantomData;
 use rand::{rngs::StdRng, seq::SliceRandom as _, Rng as _, SeedableRng as _};
 
diff --git a/nearby/presence/np_adv/src/extended/data_elements/tests.rs b/nearby/presence/np_adv/src/extended/data_elements/tests.rs
index a0f9a2f..6f085cd 100644
--- a/nearby/presence/np_adv/src/extended/data_elements/tests.rs
+++ b/nearby/presence/np_adv/src/extended/data_elements/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use super::*;
diff --git a/nearby/presence/np_adv/src/extended/de_type.rs b/nearby/presence/np_adv/src/extended/de_type.rs
index 105d15e..975df90 100644
--- a/nearby/presence/np_adv/src/extended/de_type.rs
+++ b/nearby/presence/np_adv/src/extended/de_type.rs
@@ -36,7 +36,7 @@
 
 impl From<u8> for DeType {
     fn from(value: u8) -> Self {
-        DeType { code: value as u32 }
+        DeType { code: value.into() }
     }
 }
 
@@ -46,6 +46,12 @@
     }
 }
 
+impl From<DeType> for u32 {
+    fn from(value: DeType) -> Self {
+        value.code
+    }
+}
+
 pub(crate) trait ExtendedDataElementType: Sized {
     /// A type code for use in the DE header.
     fn type_code(&self) -> DeType;
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
index 9bd54b8..bf78b7d 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use super::*;
@@ -190,7 +192,7 @@
         let mut expected = Vec::new();
         // battery de
         expected.extend_from_slice(txpower_de.clone().de_header().serialize().as_slice());
-        txpower_de.write_de_contents(&mut expected);
+        let _ = txpower_de.write_de_contents(&mut expected);
 
         assert_eq!(metadata_key, decrypted.metadata_key_plaintext);
         assert_eq!(&expected, decrypted.plaintext_contents);
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
index 2236630..eb2fe01 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
@@ -123,8 +123,7 @@
         let mut result = Vec::new();
         result.extend_from_slice(&self.metadata_key_plaintext.0);
         result.extend_from_slice(self.plaintext_contents);
-        // Won't panic because of the involved lengths
-        ArrayView::try_from_slice(&result).unwrap()
+        ArrayView::try_from_slice(&result).expect("Won't panic because of the involved lengths")
     }
 }
 
@@ -181,8 +180,10 @@
         &self,
     ) -> SectionIdentityResolutionContents {
         let nonce = self.compute_nonce::<C>();
-        let metadata_key_ciphertext: [u8; METADATA_KEY_LEN] =
-            self.all_ciphertext[..METADATA_KEY_LEN].try_into().unwrap();
+        let metadata_key_ciphertext: [u8; METADATA_KEY_LEN] = self.all_ciphertext
+            [..METADATA_KEY_LEN]
+            .try_into()
+            .expect("slice will always fit into same size array");
 
         SectionIdentityResolutionContents { nonce, metadata_key_ciphertext }
     }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
index 2370aeb..af70228 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use crate::deserialization_arena;
@@ -127,11 +129,11 @@
 
     // plaintext is correct
     {
-        let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>(
+        let crypto_material = V1DiscoveryCredential::new(
             key_seed,
             key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
             key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
-            key_pair.public(),
+            key_pair.public().to_bytes(),
         );
         let signed_identity_resolution_material =
             crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>();
@@ -150,7 +152,7 @@
 
         let mut expected = Vec::new();
         expected.extend_from_slice(txpower_de.de_header().serialize().as_slice());
-        txpower_de.write_de_contents(&mut expected);
+        let _ = txpower_de.write_de_contents(&mut expected);
 
         let nonce: AesCtrNonce = section_salt.derive(Some(1.into())).unwrap();
 
@@ -177,11 +179,11 @@
 
     // deserialization to Section works
     {
-        let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>(
+        let crypto_material = V1DiscoveryCredential::new(
             key_seed,
             key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
             key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
-            key_pair.public(),
+            key_pair.public().to_bytes(),
         );
         let signed_identity_resolution_material =
             crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>();
@@ -524,11 +526,11 @@
         contents
     );
 
-    let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>(
+    let crypto_material = V1DiscoveryCredential::new(
         key_seed,
         key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
         key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
-        key_pair.public(),
+        key_pair.public().to_bytes(),
     );
     let identity_resolution_material =
         crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>();
diff --git a/nearby/presence/np_adv/src/extended/deserialize/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
index 4b78a32..6ec7881 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/mod.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
@@ -17,7 +17,7 @@
 #[cfg(any(test, feature = "alloc"))]
 extern crate alloc;
 
-#[cfg(any(test, feature = "alloc"))]
+#[cfg(any(test, feature = "alloc", feature = "devtools"))]
 use alloc::vec::Vec;
 use core::array::TryFromSliceError;
 
@@ -845,8 +845,11 @@
     }
 
     fn salt(&self) -> RawV1Salt {
-        // should never panic
-        RawV1Salt(self.bytes[Self::TOTAL_DE_LEN - 16..].try_into().ok().unwrap())
+        RawV1Salt(
+            self.bytes[Self::TOTAL_DE_LEN - 16..]
+                .try_into()
+                .expect("a 16 byte slice will always fit into a 16 byte array"),
+        )
     }
 }
 
diff --git a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
index 4afa3ec..41eb054 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 use super::*;
 use crate::extended::deserialize::encrypted_section::{
@@ -461,7 +463,7 @@
             adv_body.extend_from_slice(&[0x81]);
         }
         if remove_section_len {
-            adv_body.remove(0);
+            let _ = adv_body.remove(0);
         }
 
         let ordered_adv = adv_body.clone();
@@ -476,7 +478,7 @@
         // * the identity does not follow the section length
         // * the section length is 0
         if remove_section_len || !add_public_identity || (ordered_adv != adv_body) {
-            parse_sections(V1Header { header_byte: 0x20 }, &adv_body)
+            let _ = parse_sections(V1Header { header_byte: 0x20 }, &adv_body)
                 .expect_err("Expected to fail because of missing section length or identity");
         }
     }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
index 5b1290f..f7e2025 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use super::*;
@@ -143,7 +145,7 @@
 
         // Verify that the decrypted metadata contains the chosen index
         let decrypted_metadata = matched_section.decrypt_metadata::<CryptoProviderImpl>().unwrap();
-        assert_eq!(&[chosen_index as u8] as &[u8], &decrypted_metadata);
+        assert_eq!([chosen_index as u8].as_slice(), &decrypted_metadata);
 
         // Verify that the section contents passed through unaltered
         let section = matched_section.contents();
@@ -254,7 +256,7 @@
 
         // Verify that the decrypted metadata contains the chosen index
         let decrypted_metadata = matched_section.decrypt_metadata::<CryptoProviderImpl>().unwrap();
-        assert_eq!(&[chosen_index as u8] as &[u8], &decrypted_metadata);
+        assert_eq!([chosen_index as u8].as_slice(), &decrypted_metadata);
 
         // Verify that the section contents passed through unaltered
         let section = matched_section.contents();
@@ -411,7 +413,7 @@
     } else {
         panic!("incorrect header");
     };
-    parse_sections(v1_header, remaining).expect_err("Expected an error");
+    let _ = parse_sections(v1_header, remaining).expect_err("Expected an error");
 }
 
 #[test]
@@ -424,7 +426,7 @@
     } else {
         panic!("incorrect header");
     };
-    parse_sections(v1_header, remaining).expect_err("Expected an error");
+    let _ = parse_sections(v1_header, remaining).expect_err("Expected an error");
 }
 
 #[test]
@@ -463,7 +465,7 @@
     } else {
         panic!("incorrect header");
     };
-    parse_sections(v1_header, remaining)
+    let _ = parse_sections(v1_header, remaining)
         .expect_err("Expected an error because number of sections is over limit");
 }
 
@@ -557,7 +559,7 @@
     des.iter()
         .map(|de| {
             let mut buf = vec![];
-            de.write_de_contents(&mut buf);
+            let _ = de.write_de_contents(&mut buf);
             de.de_header().serialize().len() + buf.len()
         })
         .sum()
diff --git a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
index 36e4e5f..ee44c75 100644
--- a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 use super::*;
 use crate::extended::serialize::section_tests::{fill_section_builder, DummyDataElement};
diff --git a/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs b/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs
index eda8b69..cb6c9ef 100644
--- a/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use super::*;
 use crate::extended::deserialize;
 use core::cmp;
diff --git a/nearby/presence/np_adv/src/extended/serialize/mod.rs b/nearby/presence/np_adv/src/extended/serialize/mod.rs
index f736dea..e753281 100644
--- a/nearby/presence/np_adv/src/extended/serialize/mod.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/mod.rs
@@ -537,7 +537,7 @@
             before_sig.split_at(EncryptionInfo::TOTAL_DE_LEN);
 
         let encryption_info: &[u8; EncryptionInfo::TOTAL_DE_LEN] =
-            encryption_info.try_into().unwrap();
+            encryption_info.try_into().expect("encryption info should always be the correct size");
 
         // we need to sign the 16-byte IV, which doesn't have to actually fit in the adv, but we
         // don't need a bigger buffer here since we won't be including the 66 bytes for the sig +
diff --git a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
index d418a50..68e5b60 100644
--- a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use super::*;
@@ -129,7 +131,7 @@
 
         for de in extra_des {
             plaintext.extend_from_slice(de.de_header().serialize().as_slice());
-            de.write_de_contents(&mut plaintext);
+            let _ = de.write_de_contents(&mut plaintext);
         }
 
         cipher.apply_keystream(&mut plaintext);
@@ -232,7 +234,7 @@
         let mut section_body = Vec::new();
         for de in extra_des {
             section_body.extend_from_slice(de.de_header().serialize().as_slice());
-            de.write_de_contents(&mut section_body);
+            let _ = de.write_de_contents(&mut section_body);
         }
 
         let sig_payload = SectionSignaturePayload::from_deserialized_parts(
@@ -692,7 +694,7 @@
 
     for de in extra_des {
         plaintext.extend_from_slice(de.de_header().serialize().as_slice());
-        de.write_de_contents(&mut plaintext);
+        let _ = de.write_de_contents(&mut plaintext);
     }
 
     cipher.apply_keystream(&mut plaintext);
@@ -765,7 +767,7 @@
     let mut section_body = Vec::new();
     for de in extra_des {
         section_body.extend_from_slice(de.de_header().serialize().as_slice());
-        de.write_de_contents(&mut section_body);
+        let _ = de.write_de_contents(&mut section_body);
     }
 
     let nonce = section_salt.derive(Some(1.into())).unwrap();
diff --git a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
index 24e6046..d03ad9b 100644
--- a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use crate::extended::serialize::AdvertisementType;
@@ -42,7 +44,7 @@
     );
     let mut file = fs::File::open(full_path)?;
     let mut data = String::new();
-    file.read_to_string(&mut data)?;
+    let _ = file.read_to_string(&mut data)?;
 
     let test_cases = match serde_json::de::from_str(&data)? {
         serde_json::Value::Array(a) => a,
diff --git a/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs
index 946c969..ffdeeb9 100644
--- a/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs
+++ b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use super::super::*;
 use crate::legacy::actions::{ActionBits, InstantTethering, NearbyShare};
 use crate::legacy::{Ciphertext, Plaintext};
diff --git a/nearby/presence/np_adv/src/header_parse_tests.rs b/nearby/presence/np_adv/src/header_parse_tests.rs
index 2da1b4a..f6b5533 100644
--- a/nearby/presence/np_adv/src/header_parse_tests.rs
+++ b/nearby/presence/np_adv/src/header_parse_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use super::*;
 
 extern crate std;
diff --git a/nearby/presence/np_adv/src/legacy/actions/tests.rs b/nearby/presence/np_adv/src/legacy/actions/tests.rs
index 86b7a08..d45d968 100644
--- a/nearby/presence/np_adv/src/legacy/actions/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/actions/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use crate::legacy::{
diff --git a/nearby/presence/np_adv/src/legacy/de_type/mod.rs b/nearby/presence/np_adv/src/legacy/de_type/mod.rs
index 5c7ebfa..304347d 100644
--- a/nearby/presence/np_adv/src/legacy/de_type/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/de_type/mod.rs
@@ -47,6 +47,7 @@
     }
 }
 
+/// The DE type code is out of range for v0 DE types.
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
 pub(crate) struct DeTypeCodeOutOfRange;
 
diff --git a/nearby/presence/np_adv/src/legacy/de_type/tests.rs b/nearby/presence/np_adv/src/legacy/de_type/tests.rs
index 59075aa..fc64321 100644
--- a/nearby/presence/np_adv/src/legacy/de_type/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/de_type/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use super::*;
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
index 938045d..d6c6b36 100644
--- a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
@@ -359,6 +359,33 @@
     TxPower(TxPowerDataElement),
 }
 
+impl<F: PacketFlavor> PlainDataElement<F> {
+    /// Returns the DE type as a u8
+    #[cfg(feature = "devtools")]
+    pub fn de_type_code(&self) -> u8 {
+        match self {
+            PlainDataElement::Actions(_) => DataElementType::Actions.type_code().as_u8(),
+            PlainDataElement::TxPower(_) => DataElementType::TxPower.type_code().as_u8(),
+        }
+    }
+
+    /// Returns the serialized contents of the DE
+    #[cfg(feature = "devtools")]
+    pub fn de_contents(&self) -> alloc::vec::Vec<u8> {
+        use crate::legacy::serialize::{DataElementBundle, ToDataElementBundle};
+        match self {
+            PlainDataElement::Actions(a) => {
+                let bundle: DataElementBundle<F> = a.to_de_bundle();
+                bundle.contents_as_slice().to_vec()
+            }
+            PlainDataElement::TxPower(t) => {
+                let bundle: DataElementBundle<F> = t.to_de_bundle();
+                bundle.contents_as_slice().to_vec()
+            }
+        }
+    }
+}
+
 /// The contents of a plaintext advertisement after deserializing DE contents
 #[derive(Debug, PartialEq, Eq)]
 pub struct PlaintextAdvContents<'d> {
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
index 5725e0b..3dfecd1 100644
--- a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
@@ -12,32 +12,32 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(unused_results, clippy::unwrap_used)]
+
 extern crate alloc;
 extern crate std;
 
 use super::*;
-use crate::legacy::actions::{ActionBits, ActionsDataElement, Finder, NearbyShare};
-use crate::legacy::serialize::id_de_type_as_generic_de_type;
-use crate::shared_data::TxPower;
 use crate::{
     credential::{v0::V0, SimpleBroadcastCryptoMaterial},
     de_type::IdentityDataElementType,
     legacy::{
-        actions,
+        actions::{self, ActionBits, ActionsDataElement, Finder, NearbyShare},
         de_type::DeActualLength,
         random_data_elements::{random_de_ciphertext, random_de_plaintext},
         serialize::{
-            encode_de_header_actual_len, AdvBuilder, DataElementBundle, Identity, LdtIdentity,
-            ToDataElementBundle as _,
+            encode_de_header_actual_len, id_de_type_as_generic_de_type, AdvBuilder,
+            DataElementBundle, Identity, LdtIdentity, ToDataElementBundle,
         },
         PacketFlavorEnum, BLE_ADV_SVC_CONTENT_LEN,
     },
-    parse_adv_header, shared_data, AdvHeader, PublicIdentity,
+    parse_adv_header, shared_data,
+    shared_data::TxPower,
+    AdvHeader, PublicIdentity,
 };
 use alloc::vec::Vec;
 use array_view::ArrayView;
 use crypto_provider_default::CryptoProviderImpl;
-use init_with::InitWith as _;
 use ldt_np_adv::LdtEncrypterXtsAes128;
 use nom::error::{self, ErrorKind};
 use rand_ext::rand::{prelude::SliceRandom, Rng as _};
@@ -357,7 +357,10 @@
 #[test]
 fn parse_encrypted_identity_contents_too_short_error() {
     // 2 byte salt + 15 byte ciphertext: 1 too short
-    let input = <[u8; 17]>::init_with_indices(|i| i as u8);
+    let mut input = [0u8; 17];
+    for (pos, e) in input.iter_mut().enumerate() {
+        *e = pos as u8
+    }
     assert_eq!(
         nom::Err::Error(error::Error { input: &input[2..], code: error::ErrorKind::TakeWhileMN }),
         parse_encrypted_identity_de_contents(&input).unwrap_err()
@@ -367,7 +370,10 @@
 #[test]
 fn parse_encrypted_identity_contents_ok() {
     // 2 byte salt + minimum 16 byte ciphertext
-    let input = <[u8; 18]>::init_with_indices(|i| i as u8);
+    let mut input = [0u8; 18];
+    for (pos, e) in input.iter_mut().enumerate() {
+        *e = pos as u8
+    }
     assert_eq!(
         ([].as_slice(), (ldt_np_adv::LegacySalt::from([0, 1]), &input[2..])),
         parse_encrypted_identity_de_contents(&input).unwrap()
diff --git a/nearby/presence/np_adv/src/legacy/random_data_elements.rs b/nearby/presence/np_adv/src/legacy/random_data_elements.rs
index f416ffe..00a2e93 100644
--- a/nearby/presence/np_adv/src/legacy/random_data_elements.rs
+++ b/nearby/presence/np_adv/src/legacy/random_data_elements.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use crate::{
diff --git a/nearby/presence/np_adv/src/legacy/serialize/mod.rs b/nearby/presence/np_adv/src/legacy/serialize/mod.rs
index 2920b67..3746796 100644
--- a/nearby/presence/np_adv/src/legacy/serialize/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/serialize/mod.rs
@@ -111,7 +111,7 @@
 lazy_static::lazy_static! {
     // Avoid either a panic-able code path or an error case that never happens by precalculating.
     static ref PUBLIC_IDENTITY_DE_HEADER: u8 =
-        encode_de_header_actual_len(DataElementType::PublicIdentity, DeActualLength::ZERO).unwrap();
+        encode_de_header_actual_len(DataElementType::PublicIdentity, DeActualLength::ZERO).expect("de length is in range");
 }
 
 impl Identity for PublicIdentity {
diff --git a/nearby/presence/np_adv/src/legacy/serialize/tests.rs b/nearby/presence/np_adv/src/legacy/serialize/tests.rs
index b4adc9f..7be30ba 100644
--- a/nearby/presence/np_adv/src/legacy/serialize/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/serialize/tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 extern crate std;
 
 use crate::legacy::actions::FastPairSass;
diff --git a/nearby/presence/np_adv/src/lib.rs b/nearby/presence/np_adv/src/lib.rs
index 1c42796..0b6d7ae 100644
--- a/nearby/presence/np_adv/src/lib.rs
+++ b/nearby/presence/np_adv/src/lib.rs
@@ -11,6 +11,7 @@
 // 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.
+
 //! Serialization and deserialization for v0 (legacy) and v1 (extended) Nearby Presence
 //! advertisements.
 //!
@@ -18,16 +19,13 @@
 //! deserialization scenarios.
 
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
+#![allow(clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
 
-#[cfg(any(test, feature = "alloc"))]
-extern crate alloc;
-extern crate core;
 use crate::{
     credential::{
-        book::CredentialBook, v0::V0DiscoveryCryptoMaterial, v1::V1DiscoveryCryptoMaterial,
-        DiscoveryCryptoMaterial, MatchedCredential, ProtocolVersion,
+        book::CredentialBook, v0::V0DiscoveryCryptoMaterial, v0::V0, v1::V1DiscoveryCryptoMaterial,
+        v1::V1, DiscoveryCryptoMaterial, MatchedCredential, ProtocolVersion,
+        ReferencedMatchedCredential,
     },
     deserialization_arena::ArenaOutOfSpace,
     extended::deserialize::{
@@ -41,7 +39,10 @@
 };
 
 #[cfg(any(test, feature = "alloc"))]
+extern crate alloc;
+#[cfg(any(test, feature = "alloc"))]
 use alloc::vec::Vec;
+
 use array_vec::ArrayVecOption;
 #[cfg(feature = "devtools")]
 use array_view::ArrayView;
@@ -307,7 +308,7 @@
                     // By default, if we have an identity match, assume that decrypting the section worked,
                     // or that the section was somehow invalid.
                     // We don't care about maintaining order, so use O(1) remove
-                    self.encrypted_sections.swap_remove(i);
+                    let _ = self.encrypted_sections.swap_remove(i);
                     // don't advance i -- it now points to a new element
                 }
             }
@@ -564,6 +565,20 @@
     fn metadata_key(&self) -> <Self::Version as ProtocolVersion>::MetadataKey;
 }
 
+impl HasIdentityMatch for legacy::ShortMetadataKey {
+    type Version = V0;
+    fn metadata_key(&self) -> Self {
+        *self
+    }
+}
+
+impl HasIdentityMatch for MetadataKey {
+    type Version = V1;
+    fn metadata_key(&self) -> Self {
+        *self
+    }
+}
+
 #[cfg(any(test, feature = "alloc"))]
 /// Type for errors from [`WithMatchedCredential#decrypt_metadata`]
 #[derive(Debug)]
@@ -589,10 +604,34 @@
     metadata_nonce: [u8; 12],
     contents: T,
 }
+impl<'a, M: MatchedCredential + Clone, T: HasIdentityMatch>
+    WithMatchedCredential<ReferencedMatchedCredential<'a, M>, T>
+{
+    /// Clones the referenced match-data to update this container
+    /// so that the match-data is owned, rather than borrowed.
+    pub fn clone_match_data(self) -> WithMatchedCredential<M, T> {
+        let matched = self.matched.as_ref().clone();
+        let metadata_nonce = self.metadata_nonce;
+        let contents = self.contents;
+
+        WithMatchedCredential { matched, metadata_nonce, contents }
+    }
+}
 impl<M: MatchedCredential, T: HasIdentityMatch> WithMatchedCredential<M, T> {
     fn new(matched: M, metadata_nonce: [u8; 12], contents: T) -> Self {
         Self { matched, metadata_nonce, contents }
     }
+    /// Applies the given function to the wrapped contents, yielding
+    /// a new instance with the same matched-credential.
+    pub fn map<R: HasIdentityMatch>(
+        self,
+        mapping: impl FnOnce(T) -> R,
+    ) -> WithMatchedCredential<M, R> {
+        let contents = mapping(self.contents);
+        let matched = self.matched;
+        let metadata_nonce = self.metadata_nonce;
+        WithMatchedCredential { matched, metadata_nonce, contents }
+    }
     /// Credential data for the credential that decrypted the content.
     pub fn matched_credential(&self) -> &M {
         &self.matched
diff --git a/nearby/presence/np_adv/tests/examples_v0.rs b/nearby/presence/np_adv/tests/examples_v0.rs
index 8310531..f7b7d5e 100644
--- a/nearby/presence/np_adv/tests/examples_v0.rs
+++ b/nearby/presence/np_adv/tests/examples_v0.rs
@@ -11,6 +11,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
+
 use crypto_provider_default::CryptoProviderImpl;
 use ldt_np_adv::*;
 use np_adv::legacy::data_elements::TxPowerDataElement;
diff --git a/nearby/presence/np_adv/tests/examples_v1.rs b/nearby/presence/np_adv/tests/examples_v1.rs
index bf633e5..94b9d75 100644
--- a/nearby/presence/np_adv/tests/examples_v1.rs
+++ b/nearby/presence/np_adv/tests/examples_v1.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
+
 use crypto_provider::{CryptoProvider, CryptoRng};
 use crypto_provider_default::CryptoProviderImpl;
 use np_adv::extended::data_elements::TxPowerDataElement;
@@ -144,7 +146,7 @@
         key_seed,
         [0; 32], // Zeroing out MIC HMAC, since it's unused in examples here.
         hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
-        key_pair.public(),
+        key_pair.public().to_bytes(),
     );
 
     let credentials: [MatchableCredential<V1, MetadataMatchedCredential<_>>; 1] =
diff --git a/nearby/presence/np_adv_dynamic/Cargo.toml b/nearby/presence/np_adv_dynamic/Cargo.toml
new file mode 100644
index 0000000..74d6e3a
--- /dev/null
+++ b/nearby/presence/np_adv_dynamic/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "np_adv_dynamic"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[lints]
+workspace = true
+
+[dependencies]
+array_view.workspace = true
+np_adv = { workspace = true, features = ["alloc"] }
+crypto_provider.workspace = true
+thiserror.workspace = true
+sink.workspace = true
diff --git a/nearby/presence/np_adv_dynamic/src/extended.rs b/nearby/presence/np_adv_dynamic/src/extended.rs
new file mode 100644
index 0000000..f961bd6
--- /dev/null
+++ b/nearby/presence/np_adv_dynamic/src/extended.rs
@@ -0,0 +1,265 @@
+// 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::CryptoProvider;
+use np_adv::{extended::data_elements::*, extended::serialize::*, shared_data::*};
+use sink::Sink;
+use thiserror::Error;
+
+/// An advertisement builder for V1 advertisements where the
+/// presence/absence of salt is determined at run-time instead of compile-time.
+pub struct BoxedAdvBuilder {
+    adv_builder: AdvBuilder,
+}
+
+impl From<AdvBuilder> for BoxedAdvBuilder {
+    fn from(adv_builder: AdvBuilder) -> Self {
+        BoxedAdvBuilder { adv_builder }
+    }
+}
+
+/// Error possibly generated when attempting to add a section to
+/// a BoxedAdvBuilder.
+#[derive(Debug, Error)]
+pub enum BoxedAddSectionError {
+    /// An error which was generated by the underlying AdvBuilder wrapped by the BoxedAdvBuilder
+    #[error("{0}")]
+    Underlying(AddSectionError),
+    /// An error generated when the boxed advertisement builder is unsalted, but the section
+    /// identity requires salt.
+    #[error("Error generated when the BoxedAdvBuilder is unsalted, but the section identity requires salt.")]
+    IdentityRequiresSaltError,
+}
+
+impl From<AddSectionError> for BoxedAddSectionError {
+    fn from(wrapped: AddSectionError) -> Self {
+        BoxedAddSectionError::Underlying(wrapped)
+    }
+}
+
+fn wrap_section_builder<'a, C: CryptoProvider, S: Into<BoxedSectionBuilder<'a, C>>>(
+    maybe_section_builder: Result<S, AddSectionError>,
+) -> Result<BoxedSectionBuilder<'a, C>, BoxedAddSectionError> {
+    let section_builder = maybe_section_builder?;
+    Ok(section_builder.into())
+}
+
+impl BoxedAdvBuilder {
+    /// Create a section builder using the given identity.
+    ///
+    /// Returns `Err` if the underlying advertisement builder
+    /// yields an error when attempting to add a new section
+    /// (typically because there's no more available adv space),
+    /// or if the requested identity requires salt, and the
+    /// advertisement builder is salt-less.
+    pub fn section_builder<C: CryptoProvider>(
+        &mut self,
+        identity: BoxedIdentity<C>,
+    ) -> Result<BoxedSectionBuilder<C>, BoxedAddSectionError> {
+        match identity {
+            BoxedIdentity::PublicIdentity => wrap_section_builder(
+                self.adv_builder.section_builder(PublicSectionEncoder::default()),
+            ),
+            BoxedIdentity::MicEncrypted(ident) => {
+                wrap_section_builder(self.adv_builder.section_builder(ident))
+            }
+            BoxedIdentity::SignedEncrypted(ident) => {
+                wrap_section_builder(self.adv_builder.section_builder(ident))
+            }
+        }
+    }
+
+    /// Convert the builder into an encoded advertisement.
+    pub fn into_advertisement(self) -> EncodedAdvertisement {
+        self.adv_builder.into_advertisement()
+    }
+}
+
+/// A wrapped v1 identity whose type is given at run-time.
+pub enum BoxedIdentity<C: CryptoProvider> {
+    /// Public identity.
+    PublicIdentity,
+    /// An encrypted identity leveraging MIC for verification.
+    MicEncrypted(MicEncryptedSectionEncoder<C>),
+    /// An encrypted identity leveraging signatures for verification.
+    SignedEncrypted(SignedEncryptedSectionEncoder<C>),
+}
+
+/// A `SectionBuilder` whose corresponding Identity
+/// and salted-ness is given at run-time instead of
+/// at compile-time.
+pub enum BoxedSectionBuilder<'a, C: CryptoProvider> {
+    /// A builder for a public section.
+    Public(SectionBuilder<'a, PublicSectionEncoder>),
+    /// A builder for a MIC-verified section.
+    MicEncrypted(SectionBuilder<'a, MicEncryptedSectionEncoder<C>>),
+    /// A builder for a signature-verified section.
+    SignedEncrypted(SectionBuilder<'a, SignedEncryptedSectionEncoder<C>>),
+}
+
+impl<'a, C: CryptoProvider> BoxedSectionBuilder<'a, C> {
+    /// Returns true if this wraps a section builder which
+    /// leverages some encrypted identity.
+    pub fn is_encrypted(&self) -> bool {
+        match self {
+            BoxedSectionBuilder::Public(_) => false,
+            BoxedSectionBuilder::MicEncrypted(_) => true,
+            BoxedSectionBuilder::SignedEncrypted(_) => true,
+        }
+    }
+    /// Add this builder to the advertisement that created it.
+    pub fn add_to_advertisement(self) {
+        match self {
+            BoxedSectionBuilder::Public(x) => x.add_to_advertisement(),
+            BoxedSectionBuilder::MicEncrypted(x) => x.add_to_advertisement(),
+            BoxedSectionBuilder::SignedEncrypted(x) => x.add_to_advertisement(),
+        }
+    }
+    /// Add a data element to the section with a closure that returns a `Result`.
+    ///
+    /// The provided `build_de` closure will be invoked with the derived salt for this DE,
+    /// if any salt has been specified for the surrounding advertisement.
+    pub fn add_de_res<E>(
+        &mut self,
+        build_de: impl FnOnce(Option<DeSalt<C>>) -> Result<BoxedWriteDataElement, E>,
+    ) -> Result<(), AddDataElementError<E>> {
+        match self {
+            BoxedSectionBuilder::Public(x) => {
+                let build_de_modified = |()| build_de(None);
+                x.add_de_res(build_de_modified)
+            }
+            BoxedSectionBuilder::MicEncrypted(x) => {
+                let build_de_modified = |de_salt: DeSalt<C>| build_de(Some(de_salt));
+                x.add_de_res(build_de_modified)
+            }
+            BoxedSectionBuilder::SignedEncrypted(x) => {
+                let build_de_modified = |de_salt: DeSalt<C>| build_de(Some(de_salt));
+                x.add_de_res(build_de_modified)
+            }
+        }
+    }
+    /// Like add_de_res, but for infalliable closures
+    pub fn add_de(
+        &mut self,
+        build_de: impl FnOnce(Option<DeSalt<C>>) -> BoxedWriteDataElement,
+    ) -> Result<(), AddDataElementError<()>> {
+        self.add_de_res(|derived_salt| Ok::<_, ()>(build_de(derived_salt)))
+    }
+}
+
+impl<'a, C: CryptoProvider> From<SectionBuilder<'a, PublicSectionEncoder>>
+    for BoxedSectionBuilder<'a, C>
+{
+    fn from(section_builder: SectionBuilder<'a, PublicSectionEncoder>) -> Self {
+        BoxedSectionBuilder::Public(section_builder)
+    }
+}
+
+impl<'a, C: CryptoProvider> From<SectionBuilder<'a, MicEncryptedSectionEncoder<C>>>
+    for BoxedSectionBuilder<'a, C>
+{
+    fn from(section_builder: SectionBuilder<'a, MicEncryptedSectionEncoder<C>>) -> Self {
+        BoxedSectionBuilder::MicEncrypted(section_builder)
+    }
+}
+
+impl<'a, C: CryptoProvider> From<SectionBuilder<'a, SignedEncryptedSectionEncoder<C>>>
+    for BoxedSectionBuilder<'a, C>
+{
+    fn from(section_builder: SectionBuilder<'a, SignedEncryptedSectionEncoder<C>>) -> Self {
+        BoxedSectionBuilder::SignedEncrypted(section_builder)
+    }
+}
+
+/// Mutable trait object reference to a `Sink<u8>`
+pub struct DynSink<'a> {
+    wrapped: &'a mut dyn Sink<u8>,
+}
+
+impl<'a> Sink<u8> for DynSink<'a> {
+    fn try_extend_from_slice(&mut self, items: &[u8]) -> Option<()> {
+        self.wrapped.try_extend_from_slice(items)
+    }
+    fn try_push(&mut self, item: u8) -> Option<()> {
+        self.wrapped.try_push(item)
+    }
+}
+
+impl<'a> From<&'a mut dyn Sink<u8>> for DynSink<'a> {
+    fn from(wrapped: &'a mut dyn Sink<u8>) -> Self {
+        DynSink { wrapped }
+    }
+}
+
+/// A version of the WriteDataElement trait which is object-safe
+pub trait DynWriteDataElement {
+    /// Gets the data-element header for the data element
+    fn de_header(&self) -> DeHeader;
+    /// Writes the contents of the DE payload to the given DynSink.
+    /// Returns Some(()) if the write operation was successful,
+    /// and None if it was unsuccessful
+    fn write_de_contents(&self, sink: DynSink) -> Option<()>;
+}
+
+impl<T: WriteDataElement> DynWriteDataElement for T {
+    fn de_header(&self) -> DeHeader {
+        WriteDataElement::de_header(self)
+    }
+    fn write_de_contents(&self, mut sink: DynSink) -> Option<()> {
+        WriteDataElement::write_de_contents(self, &mut sink)
+    }
+}
+
+/// Trait object wrapper for DynWriteDataElement instances
+pub struct BoxedWriteDataElement {
+    wrapped: Box<dyn DynWriteDataElement>,
+}
+
+impl BoxedWriteDataElement {
+    /// Constructs a new `BoxedWriteDataElement` from a `WriteDataElement`
+    /// whose trait impl is valid for a `'static` lifetime.
+    pub fn new<D: WriteDataElement + 'static>(wrapped: D) -> Self {
+        let wrapped = Box::new(wrapped);
+        Self { wrapped }
+    }
+}
+
+impl WriteDataElement for BoxedWriteDataElement {
+    fn de_header(&self) -> DeHeader {
+        self.wrapped.de_header()
+    }
+    fn write_de_contents<S: Sink<u8>>(&self, sink: &mut S) -> Option<()> {
+        let sink: &mut dyn Sink<u8> = sink;
+        let dyn_sink = DynSink::from(sink);
+        self.wrapped.write_de_contents(dyn_sink)
+    }
+}
+
+impl From<TxPower> for BoxedWriteDataElement {
+    fn from(tx_power: TxPower) -> Self {
+        BoxedWriteDataElement::new::<TxPowerDataElement>(tx_power.into())
+    }
+}
+
+impl From<ContextSyncSeqNum> for BoxedWriteDataElement {
+    fn from(context_sync_sequence_num: ContextSyncSeqNum) -> Self {
+        BoxedWriteDataElement::new::<ContextSyncSeqNumDataElement>(context_sync_sequence_num.into())
+    }
+}
+
+impl From<ActionsDataElement> for BoxedWriteDataElement {
+    fn from(data: ActionsDataElement) -> Self {
+        BoxedWriteDataElement::new(data)
+    }
+}
diff --git a/nearby/presence/np_adv_dynamic/src/legacy.rs b/nearby/presence/np_adv_dynamic/src/legacy.rs
new file mode 100644
index 0000000..191ef75
--- /dev/null
+++ b/nearby/presence/np_adv_dynamic/src/legacy.rs
@@ -0,0 +1,349 @@
+// 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 array_view::ArrayView;
+use crypto_provider::CryptoProvider;
+use np_adv::legacy::actions::*;
+use np_adv::legacy::data_elements::*;
+use np_adv::legacy::serialize::*;
+use np_adv::legacy::*;
+use np_adv::shared_data::*;
+use np_adv::PublicIdentity;
+use thiserror::Error;
+
+/// Wrapper around a V0 advertisement builder which
+/// is agnostic to the kind of identity used in the advertisement.
+/// Instead of compile-time errors for non-matching packet flavors,
+/// this builder instead defers any generated errors to run-time.
+/// Generic over the Aes algorithm used for any encrypted identities,
+/// since that is generally specified at compile-time.
+pub enum BoxedAdvBuilder<C: CryptoProvider> {
+    /// Builder for public advertisements.
+    Public(AdvBuilder<PublicIdentity>),
+    /// Builder for LDT-encryptedadvertisements.
+    Ldt(AdvBuilder<LdtIdentity<C>>),
+}
+
+/// Wrapper around possible errors which occur only during
+/// advertisement construction from a builder.
+#[derive(Debug)]
+pub enum BoxedAdvConstructionError {
+    /// An error originating from a problem with LDT
+    /// encryption of the advertisement contents.
+    Ldt(LdtPostprocessError),
+}
+
+impl<C: CryptoProvider> BoxedAdvBuilder<C> {
+    /// Returns true if this wraps an adv builder which
+    /// leverages some encrypted identity.
+    pub fn is_encrypted(&self) -> bool {
+        match self {
+            BoxedAdvBuilder::Public(_) => false,
+            BoxedAdvBuilder::Ldt(_) => true,
+        }
+    }
+    /// Constructs a new BoxedAdvBuilder from the given BoxedIdentity
+    pub fn new(identity: BoxedIdentity<C>) -> Self {
+        match identity {
+            BoxedIdentity::Public(identity) => BoxedAdvBuilder::Public(AdvBuilder::new(identity)),
+            BoxedIdentity::LdtIdentity(identity) => BoxedAdvBuilder::Ldt(AdvBuilder::new(identity)),
+        }
+    }
+    /// Attempts to add a data element to the advertisement
+    /// being built by this BoxedAdvBuilder. Returns
+    /// nothing if successful, and a BoxedAddDataElementError
+    /// if something went wrong in the attempt to add the DE.
+    pub fn add_data_element(
+        &mut self,
+        data_element: ToBoxedDataElementBundle,
+    ) -> Result<(), BoxedAddDataElementError> {
+        match self {
+            BoxedAdvBuilder::Public(public_builder) => {
+                //Verify that we can get the data element as plaintext
+                let maybe_plaintext_data_element = data_element.to_plaintext();
+                match maybe_plaintext_data_element {
+                    Some(plaintext_data_element) => public_builder
+                        .add_data_element(plaintext_data_element)
+                        .map_err(|e| e.into()),
+                    None => Err(BoxedAddDataElementError::FlavorMismatchError),
+                }
+            }
+            BoxedAdvBuilder::Ldt(private_builder) => {
+                //Verify that we can get the data element as ciphertext
+                let maybe_ciphertext_data_element = data_element.to_ciphertext();
+                match maybe_ciphertext_data_element {
+                    Some(ciphertext_data_element) => private_builder
+                        .add_data_element(ciphertext_data_element)
+                        .map_err(|e| e.into()),
+                    None => Err(BoxedAddDataElementError::FlavorMismatchError),
+                }
+            }
+        }
+    }
+    /// Consume this BoxedAdvBuilder and attempt to create
+    /// a serialized advertisement including the added DEs.
+    pub fn into_advertisement(
+        self,
+    ) -> Result<ArrayView<u8, BLE_ADV_SVC_CONTENT_LEN>, BoxedAdvConstructionError> {
+        match self {
+            BoxedAdvBuilder::Public(x) => {
+                match x.into_advertisement() {
+                    Ok(x) => Ok(x),
+                    Err(x) => match x {}, //Infallible
+                }
+            }
+            BoxedAdvBuilder::Ldt(x) => {
+                x.into_advertisement().map_err(BoxedAdvConstructionError::Ldt)
+            }
+        }
+    }
+}
+
+/// Possible errors generated when trying to add a DE to a
+/// BoxedAdvBuilder.
+#[derive(Debug, Error)]
+pub enum BoxedAddDataElementError {
+    /// Some kind of error in adding the data element which
+    /// is not an issue with trying to add a DE of the incorrect
+    /// packet flavoring.
+    #[error("{0:?}")]
+    UnderlyingError(AddDataElementError),
+    #[error(
+        "Expected packet flavoring for added DEs does not match the actual packet flavor of the DE"
+    )]
+    /// Error when attempting to add a DE which requires one
+    /// of an (encrypted/plaintext) advertisement, but the
+    /// advertisement builder doesn't match this requirement.
+    FlavorMismatchError,
+}
+
+impl From<AddDataElementError> for BoxedAddDataElementError {
+    fn from(add_data_element_error: AddDataElementError) -> Self {
+        BoxedAddDataElementError::UnderlyingError(add_data_element_error)
+    }
+}
+
+/// Trait object reference to a `ToDataElementBundle<I>` with lifetime `'a`.
+/// Implements `ToDataElementBundle<I>` by deferring to the wrapped trait object.
+pub struct DynamicToDataElementBundle<'a, I: PacketFlavor> {
+    wrapped: &'a dyn ToDataElementBundle<I>,
+}
+
+impl<'a, I: PacketFlavor> From<&'a dyn ToDataElementBundle<I>>
+    for DynamicToDataElementBundle<'a, I>
+{
+    fn from(wrapped: &'a dyn ToDataElementBundle<I>) -> Self {
+        DynamicToDataElementBundle { wrapped }
+    }
+}
+
+impl<'a, I: PacketFlavor> ToDataElementBundle<I> for DynamicToDataElementBundle<'a, I> {
+    fn to_de_bundle(&self) -> DataElementBundle<I> {
+        self.wrapped.to_de_bundle()
+    }
+}
+
+/// Trait for types which can provide trait object
+/// references to either plaintext or ciphertext [ToDataElementBundle]
+pub trait ToMultiFlavorElementBundle {
+    /// Gets the associated trait object reference to a `ToDataElementBundle<Plaintext>`
+    /// with the same lifetime as a reference to the implementor.
+    fn to_plaintext(&self) -> DynamicToDataElementBundle<Plaintext>;
+
+    /// Gets the associated trait object reference to a `ToDataElementBundle<Ciphertext>`
+    /// with the same lifetime as a reference to the implementor.
+    fn to_ciphertext(&self) -> DynamicToDataElementBundle<Ciphertext>;
+}
+
+/// Blanket impl of [ToMultiFlavorElementBundle] for implementors of [ToDataElementBundle]
+/// for both [Plaintext] and [Ciphertext] packet flavors.
+impl<T: ToDataElementBundle<Plaintext> + ToDataElementBundle<Ciphertext>> ToMultiFlavorElementBundle
+    for T
+{
+    fn to_plaintext(&self) -> DynamicToDataElementBundle<Plaintext> {
+        let reference: &dyn ToDataElementBundle<Plaintext> = self;
+        reference.into()
+    }
+    fn to_ciphertext(&self) -> DynamicToDataElementBundle<Ciphertext> {
+        let reference: &dyn ToDataElementBundle<Ciphertext> = self;
+        reference.into()
+    }
+}
+
+/// Boxed trait object version of [ToDataElementBundle] which incorporates
+/// all possible variants on generatable packet flavoring
+/// (`Plaintext`, `Ciphertext`, or both, as a [ToMultiFlavorElementBundle])
+pub enum ToBoxedDataElementBundle {
+    /// The underlying DE is plaintext-only.
+    Plaintext(Box<dyn ToDataElementBundle<Plaintext>>),
+    /// The underlying DE is ciphertext-only.
+    Ciphertext(Box<dyn ToDataElementBundle<Ciphertext>>),
+    /// The underlying DE may exist in plaintext or
+    /// in ciphertext advertisements.
+    Both(Box<dyn ToMultiFlavorElementBundle>),
+}
+
+impl ToBoxedDataElementBundle {
+    /// If this [ToBoxedDataElementBundle] can generate plaintext, returns
+    /// a trait object reference to a `ToDataElementBundle<Plaintext>`
+    pub fn to_plaintext(&self) -> Option<DynamicToDataElementBundle<Plaintext>> {
+        match &self {
+            ToBoxedDataElementBundle::Plaintext(x) => Some(x.as_ref().into()),
+            ToBoxedDataElementBundle::Ciphertext(_) => None,
+            ToBoxedDataElementBundle::Both(x) => Some(x.as_ref().to_plaintext()),
+        }
+    }
+    /// If this [ToBoxedDataElementBundle] can generate ciphertext, returns
+    /// a trait object reference to a `ToDataElementBundle<Ciphertext>`
+    pub fn to_ciphertext(&self) -> Option<DynamicToDataElementBundle<Ciphertext>> {
+        match &self {
+            ToBoxedDataElementBundle::Plaintext(_) => None,
+            ToBoxedDataElementBundle::Ciphertext(x) => Some(x.as_ref().into()),
+            ToBoxedDataElementBundle::Both(x) => Some(x.as_ref().to_ciphertext()),
+        }
+    }
+}
+
+/// Boxed version of implementors of the Identity trait.
+/// A is the underlying Aes algorithm leveraged by ciphertext-based identities.
+pub enum BoxedIdentity<C: CryptoProvider> {
+    /// Public Identity.
+    Public(PublicIdentity),
+    /// An encrypted identity, using LDT encryption.
+    LdtIdentity(LdtIdentity<C>),
+}
+
+impl<C: CryptoProvider> From<PublicIdentity> for BoxedIdentity<C> {
+    fn from(public_identity: PublicIdentity) -> BoxedIdentity<C> {
+        BoxedIdentity::Public(public_identity)
+    }
+}
+
+impl<C: CryptoProvider> From<LdtIdentity<C>> for BoxedIdentity<C> {
+    fn from(ldt_identity: LdtIdentity<C>) -> BoxedIdentity<C> {
+        BoxedIdentity::LdtIdentity(ldt_identity)
+    }
+}
+
+impl From<TxPower> for ToBoxedDataElementBundle {
+    fn from(data: TxPower) -> Self {
+        ToBoxedDataElementBundle::Both(Box::new(TxPowerDataElement::from(data)))
+    }
+}
+
+impl From<BoxedActionBits> for ToBoxedDataElementBundle {
+    fn from(action_bits: BoxedActionBits) -> Self {
+        match action_bits {
+            BoxedActionBits::Plaintext(action_bits) => {
+                ToBoxedDataElementBundle::Plaintext(Box::new(ActionsDataElement::from(action_bits)))
+            }
+            BoxedActionBits::Ciphertext(action_bits) => ToBoxedDataElementBundle::Ciphertext(
+                Box::new(ActionsDataElement::from(action_bits)),
+            ),
+        }
+    }
+}
+
+/// Boxed version of `ToActionElement` which allows abstracting over
+/// what packet flavors are supported by a given action.
+pub enum ToBoxedActionElement {
+    /// A context-sync sequence number.
+    ContextSyncSeqNum(ContextSyncSeqNum),
+    /// Action bit for active unlock.
+    ActiveUnlock(bool),
+    /// Action bit for nearby share.
+    NearbyShare(bool),
+    /// Action bit for instant tethering.
+    InstantTethering(bool),
+    /// Action bit for PhoneHub.
+    PhoneHub(bool),
+    /// Action bit for Finder.
+    Finder(bool),
+    /// Action bit for Fast Pair/SASS
+    FastPairSass(bool),
+    /// Action bit for Presence Manager.
+    PresenceManager(bool),
+}
+
+/// [`ActionBits`] with runtime-determined packet flavoring
+pub enum BoxedActionBits {
+    /// Action-bits for a plaintext advertisement.
+    Plaintext(ActionBits<Plaintext>),
+    /// Action-bits for a ciphertext advertisement.
+    Ciphertext(ActionBits<Ciphertext>),
+}
+
+/// Error which is raised when the flavor of a [`BoxedActionBits`]
+/// does not match the supported flavors of a [`ToBoxedActionElement`]
+/// upon attempting to add the action to the bit-field.
+#[derive(Debug)]
+pub struct BoxedSetActionFlavorError;
+
+impl BoxedActionBits {
+    /// Constructs the [`BoxedActionBits`] variant with the specified packet
+    /// flavor variant, and no bits set.
+    pub fn new(packet_flavor: PacketFlavorEnum) -> Self {
+        match packet_flavor {
+            PacketFlavorEnum::Plaintext => BoxedActionBits::Plaintext(ActionBits::default()),
+            PacketFlavorEnum::Ciphertext => BoxedActionBits::Ciphertext(ActionBits::default()),
+        }
+    }
+
+    fn set<F: PacketFlavor, E: ToActionElement<F>>(
+        action_bits: &mut ActionBits<F>,
+        to_element: E,
+    ) -> Result<(), BoxedSetActionFlavorError> {
+        action_bits.set_action(to_element);
+        Ok(())
+    }
+
+    /// Attempts to set the specified [`ToBoxedActionElement`], yielding
+    /// a [`BoxedSetActionFlavorError`] if the flavor of this
+    /// [`BoxedActionBits`] isn't supported by the passed [`ToBoxedActionElement`].
+    pub fn set_action(
+        &mut self,
+        to_element: ToBoxedActionElement,
+    ) -> Result<(), BoxedSetActionFlavorError> {
+        match self {
+            BoxedActionBits::Plaintext(action_bits) => match to_element {
+                ToBoxedActionElement::ContextSyncSeqNum(x) => Self::set(action_bits, x),
+                ToBoxedActionElement::NearbyShare(b) => {
+                    Self::set(action_bits, NearbyShare::from(b))
+                }
+                ToBoxedActionElement::Finder(b) => Self::set(action_bits, Finder::from(b)),
+                ToBoxedActionElement::FastPairSass(b) => {
+                    Self::set(action_bits, FastPairSass::from(b))
+                }
+                _ => Err(BoxedSetActionFlavorError),
+            },
+            BoxedActionBits::Ciphertext(action_bits) => match to_element {
+                ToBoxedActionElement::ContextSyncSeqNum(x) => Self::set(action_bits, x),
+                ToBoxedActionElement::ActiveUnlock(b) => {
+                    Self::set(action_bits, ActiveUnlock::from(b))
+                }
+                ToBoxedActionElement::NearbyShare(b) => {
+                    Self::set(action_bits, NearbyShare::from(b))
+                }
+                ToBoxedActionElement::InstantTethering(b) => {
+                    Self::set(action_bits, InstantTethering::from(b))
+                }
+                ToBoxedActionElement::PhoneHub(b) => Self::set(action_bits, PhoneHub::from(b)),
+                ToBoxedActionElement::PresenceManager(b) => {
+                    Self::set(action_bits, PresenceManager::from(b))
+                }
+                _ => Err(BoxedSetActionFlavorError),
+            },
+        }
+    }
+}
diff --git a/nearby/presence/np_adv_dynamic/src/lib.rs b/nearby/presence/np_adv_dynamic/src/lib.rs
new file mode 100644
index 0000000..4ba8f8b
--- /dev/null
+++ b/nearby/presence/np_adv_dynamic/src/lib.rs
@@ -0,0 +1,21 @@
+// 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.
+
+//! A no_std-friendly wrapper around `np_adv` to allow dynamic-style
+//! advertisement serialization, with correctness checked at run-time.
+
+/// Dynamic wrappers around extended adv serialization.
+pub mod extended;
+/// Dynamic wrappers around legacy adv serialization.
+pub mod legacy;
diff --git a/nearby/presence/np_c_ffi/Cargo.lock b/nearby/presence/np_c_ffi/Cargo.lock
index 796292b..836d35b 100644
--- a/nearby/presence/np_c_ffi/Cargo.lock
+++ b/nearby/presence/np_c_ffi/Cargo.lock
@@ -668,8 +668,10 @@
  "crypto_provider_default",
  "handle_map",
  "lazy_static",
+ "ldt_np_adv",
  "lock_adapter",
  "np_adv",
+ "np_hkdf",
 ]
 
 [[package]]
diff --git a/nearby/presence/np_c_ffi/include/c/np_c_ffi.h b/nearby/presence/np_c_ffi/include/c/np_c_ffi.h
index 03e5821..81914c9 100644
--- a/nearby/presence/np_c_ffi/include/c/np_c_ffi.h
+++ b/nearby/presence/np_c_ffi/include/c/np_c_ffi.h
@@ -33,6 +33,21 @@
 #include <stdlib.h>
 
 /**
+ * Result type for trying to add a credential to a credential-slab.
+ */
+enum np_ffi_AddCredentialToSlabResult {
+  /**
+   * We succeeded in adding the credential to the slab.
+   */
+  NP_FFI_ADD_CREDENTIAL_TO_SLAB_RESULT_SUCCESS = 0,
+  /**
+   * The handle to the slab was actually invalid.
+   */
+  NP_FFI_ADD_CREDENTIAL_TO_SLAB_RESULT_INVALID_HANDLE = 1,
+};
+typedef uint8_t np_ffi_AddCredentialToSlabResult;
+
+/**
  * The possible boolean action types which can be present in an Actions data element
  */
 enum np_ffi_BooleanActionType {
@@ -51,19 +66,41 @@
  */
 enum np_ffi_CreateCredentialBookResultKind {
   /**
-   * There was no space left to create a new credential book
-   */
-  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_NO_SPACE_LEFT = 0,
-  /**
    * We created a new credential book behind the given handle.
    * The associated payload may be obtained via
    * `CreateCredentialBookResult#into_success()`.
    */
-  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_SUCCESS = 1,
+  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_SUCCESS = 0,
+  /**
+   * There was no space left to create a new credential book
+   */
+  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_NO_SPACE_LEFT = 1,
+  /**
+   * The slab that we tried to create a credential-book from
+   * actually was an invalid handle.
+   */
+  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_INVALID_SLAB_HANDLE = 2,
 };
 typedef uint8_t np_ffi_CreateCredentialBookResultKind;
 
 /**
+ * Discriminant for `CreateCredentialSlabResult`
+ */
+enum np_ffi_CreateCredentialSlabResultKind {
+  /**
+   * There was no space left to create a new credential slab
+   */
+  NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_KIND_NO_SPACE_LEFT = 0,
+  /**
+   * We created a new credential slab behind the given handle.
+   * The associated payload may be obtained via
+   * `CreateCredentialSlabResult#into_success()`.
+   */
+  NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_KIND_SUCCESS = 1,
+};
+typedef uint8_t np_ffi_CreateCredentialSlabResultKind;
+
+/**
  * A result-type enum which tells the caller whether/not a deallocation
  * succeeded or failed due to the requested handle not being present.
  */
@@ -122,16 +159,8 @@
 typedef uint8_t np_ffi_DeserializedV0AdvertisementKind;
 
 /**
- * Represents deserialized information about the V0 identity utilized
- * by a deserialized V0 advertisement
- */
-typedef enum {
-  NP_FFI_DESERIALIZED_V0_IDENTITY_PLAINTEXT,
-  NP_FFI_DESERIALIZED_V0_IDENTITY_DECRYPTED,
-} np_ffi_DeserializedV0Identity;
-
-/**
- * Discriminant for `DeserializedV0Identity`.
+ * Discriminant for deserialized information about the V0
+ * identity utilized by a deserialized V0 advertisement.
  */
 enum np_ffi_DeserializedV0IdentityKind {
   /**
@@ -277,8 +306,9 @@
  * Result type for `create_credential_book`
  */
 enum np_ffi_CreateCredentialBookResult_Tag {
-  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_NO_SPACE_LEFT = 0,
-  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_SUCCESS = 1,
+  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_SUCCESS = 0,
+  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_NO_SPACE_LEFT = 1,
+  NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_INVALID_SLAB_HANDLE = 2,
 };
 typedef uint8_t np_ffi_CreateCredentialBookResult_Tag;
 
@@ -291,6 +321,79 @@
 } np_ffi_CreateCredentialBookResult;
 
 /**
+ *A `#[repr(C)]` handle to a value of type `super::CredentialSlabInternals`.
+ */
+typedef struct {
+  uint64_t handle_id;
+} np_ffi_CredentialSlab;
+
+/**
+ * Result type for `create_credential_slab`
+ */
+typedef enum {
+  NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_NO_SPACE_LEFT,
+  NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_SUCCESS,
+} np_ffi_CreateCredentialSlabResult_Tag;
+
+typedef struct {
+  np_ffi_CreateCredentialSlabResult_Tag tag;
+  union {
+    struct {
+      np_ffi_CredentialSlab success;
+    };
+  };
+} np_ffi_CreateCredentialSlabResult;
+
+/**
+ * Cryptographic information about a particular V0 discovery credential
+ * necessary to match and decrypt encrypted V0 advertisements.
+ */
+typedef struct {
+  uint8_t key_seed[32];
+  uint8_t legacy_metadata_key_hmac[32];
+} np_ffi_V0DiscoveryCredential;
+
+/**
+ * A representation of a MatchedCredential which is passable across the FFI boundary
+ */
+typedef struct {
+  uint32_t cred_id;
+  const uint8_t *encrypted_metadata_bytes_buffer;
+  uintptr_t encrypted_metadata_bytes_len;
+} np_ffi_FfiMatchedCredential;
+
+/**
+ * Representation of a V0 credential that contains additional data to provide back to caller once it
+ * is matched. The credential_id can be used by the caller to correlate it back to the full
+ * credentials details.
+ */
+typedef struct {
+  np_ffi_V0DiscoveryCredential discovery_cred;
+  np_ffi_FfiMatchedCredential matched_cred;
+} np_ffi_V0MatchableCredential;
+
+/**
+ * Cryptographic information about a particular V1 discovery credential
+ * necessary to match and decrypt encrypted V1 advertisement sections.
+ */
+typedef struct {
+  uint8_t key_seed[32];
+  uint8_t expected_unsigned_metadata_key_hmac[32];
+  uint8_t expected_signed_metadata_key_hmac[32];
+  uint8_t pub_key[32];
+} np_ffi_V1DiscoveryCredential;
+
+/**
+ * Representation of a V1 credential that contains additional data to provide back to caller once it
+ * is matched. The credential_id can be used by the caller to correlate it back to the full
+ * credentials details.
+ */
+typedef struct {
+  np_ffi_V1DiscoveryCredential discovery_cred;
+  np_ffi_FfiMatchedCredential matched_cred;
+} np_ffi_V1MatchableCredential;
+
+/**
  *A `#[repr(C)]` handle to a value of type `super::V0PayloadInternals`.
  */
 typedef struct {
@@ -303,7 +406,7 @@
 typedef struct {
   uint8_t num_des;
   np_ffi_V0Payload payload;
-  np_ffi_DeserializedV0Identity identity;
+  np_ffi_DeserializedV0IdentityKind identity_kind;
 } np_ffi_LegibleDeserializedV0Advertisement;
 
 /**
@@ -527,6 +630,10 @@
  */
 typedef struct {
   /**
+   * The offset of this generic data-element.
+   */
+  uint8_t offset;
+  /**
    * The DE type code of this generic data-element.
    */
   np_ffi_V1DEType de_type;
@@ -616,6 +723,22 @@
 void np_ffi_global_config_set_num_shards(uint8_t num_shards);
 
 /**
+ * Sets the maximum number of active handles to credential slabs
+ * which may be active at any one time.
+ * Default value: Max value.
+ * Max value: `u32::MAX - 1`.
+ *
+ * Useful for bounding the maximum memory used by the client application
+ * on credential slabs in constrained-memory environments.
+ *
+ * Setting this value will have no effect if the handle-maps for the
+ * API have already begun being used by the client code, and any
+ * values set will take effect upon the first usage of any API
+ * call utilizing credential slabs.
+ */
+void np_ffi_global_config_set_max_num_credential_slabs(uint32_t max_num_credential_slabs);
+
+/**
  * Sets the maximum number of active handles to credential books
  * which may be active at any one time.
  * Default value: Max value.
@@ -666,9 +789,10 @@
 void np_ffi_global_config_set_max_num_deserialized_v1_advertisements(uint32_t max_num_deserialized_v1_advertisements);
 
 /**
- * Allocates a new credential-book, returning a handle to the created object
+ * Allocates a new credential-book from the given slab, returning a handle
+ * to the created object. The slab will be deallocated by this call.
  */
-np_ffi_CreateCredentialBookResult np_ffi_create_credential_book(void);
+np_ffi_CreateCredentialBookResult np_ffi_create_credential_book_from_slab(np_ffi_CredentialSlab slab);
 
 /**
  * Gets the tag of a `CreateCredentialBookResult` tagged enum.
@@ -682,11 +806,54 @@
 np_ffi_CredentialBook np_ffi_CreateCredentialBookResult_into_SUCCESS(np_ffi_CreateCredentialBookResult result);
 
 /**
+ * Deallocates a credential-slab by its handle.
+ */
+np_ffi_DeallocateResult np_ffi_deallocate_credential_slab(np_ffi_CredentialSlab credential_slab);
+
+/**
  * Deallocates a credential-book by its handle
  */
 np_ffi_DeallocateResult np_ffi_deallocate_credential_book(np_ffi_CredentialBook credential_book);
 
 /**
+ * Allocates a new credential-slab, returning a handle to the created object
+ */
+np_ffi_CreateCredentialSlabResult np_ffi_create_credential_slab(void);
+
+/**
+ * Gets the tag of a `CreateCredentialSlabResult` tagged enum.
+ */
+np_ffi_CreateCredentialSlabResultKind np_ffi_CreateCredentialSlabResult_kind(np_ffi_CreateCredentialSlabResult result);
+
+/**
+ * Casts a `CreateCredentialSlabResult` to the `SUCCESS` variant, panicking in the
+ * case where the passed value is of a different enum variant.
+ */
+np_ffi_CredentialSlab np_ffi_CreateCredentialSlabResult_into_SUCCESS(np_ffi_CreateCredentialSlabResult result);
+
+/**
+ * Adds the given V0 discovery credential with some associated
+ * match-data to this credential slab.
+ *
+ * Safety: this is safe if the provided pointer points to a valid memory address
+ * which contains the correct len amount of bytes. The copy from the memory address isn't atomic,
+ * so concurrent modification of the array from another thread would cause undefined behavior.
+ */
+np_ffi_AddCredentialToSlabResult np_ffi_CredentialSlab_add_v0_credential(np_ffi_CredentialSlab credential_slab,
+                                                                         np_ffi_V0MatchableCredential v0_cred);
+
+/**
+ * Adds the given V1 discovery credential with some associated
+ * match-data to this credential slab.
+ *
+ * Safety: this is safe if the provided pointer points to a valid memory address
+ * which contains the correct len amount of bytes. The copy from the memory address isn't atomic,
+ * so concurrent modification of the array from another thread would cause undefined behavior.
+ */
+np_ffi_AddCredentialToSlabResult np_ffi_CredentialSlab_add_v1_credential(np_ffi_CredentialSlab credential_slab,
+                                                                         np_ffi_V1MatchableCredential v1_cred);
+
+/**
  * Attempts to deserialize an advertisement with the given service-data
  * payload (presumed to be under the NP service UUID) using credentials
  * pulled from the given credential-book.
@@ -751,9 +918,9 @@
 np_ffi_V0Payload np_ffi_LegibleDeserializedV0Advertisement_into_payload(np_ffi_LegibleDeserializedV0Advertisement adv);
 
 /**
- * Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`.
+ * Gets just the identity kind associated with a `LegibleDeserializedV0Advertisement`.
  */
-np_ffi_DeserializedV0Identity np_ffi_LegibleDeserializedV0Advertisement_into_identity(np_ffi_LegibleDeserializedV0Advertisement adv);
+np_ffi_DeserializedV0IdentityKind np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind(np_ffi_LegibleDeserializedV0Advertisement adv);
 
 /**
  * Deallocates any internal data of a `LegibleDeserializedV0Advertisement`
@@ -761,11 +928,6 @@
 np_ffi_DeallocateResult np_ffi_deallocate_legible_v0_advertisement(np_ffi_LegibleDeserializedV0Advertisement adv);
 
 /**
- * Gets the tag of the `DeserializedV0Identity` tagged-union.
- */
-np_ffi_DeserializedV0IdentityKind np_ffi_DeserializedV0Identity_kind(np_ffi_DeserializedV0Identity identity);
-
-/**
  * Attempts to get the data-element with the given index in the passed v0 adv payload
  */
 np_ffi_GetV0DEResult np_ffi_V0Payload_get_de(np_ffi_V0Payload payload, uint8_t index);
diff --git a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h
index 3361c5d..ad3ec24 100644
--- a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h
+++ b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h
@@ -75,6 +75,20 @@
 /// API call.
 void np_ffi_global_config_set_num_shards(uint8_t num_shards);
 
+/// Sets the maximum number of active handles to credential slabs
+/// which may be active at any one time.
+/// Default value: Max value.
+/// Max value: `u32::MAX - 1`.
+///
+/// Useful for bounding the maximum memory used by the client application
+/// on credential slabs in constrained-memory environments.
+///
+/// Setting this value will have no effect if the handle-maps for the
+/// API have already begun being used by the client code, and any
+/// values set will take effect upon the first usage of any API
+/// call utilizing credential slabs.
+void np_ffi_global_config_set_max_num_credential_slabs(uint32_t max_num_credential_slabs);
+
 /// Sets the maximum number of active handles to credential books
 /// which may be active at any one time.
 /// Default value: Max value.
@@ -119,8 +133,9 @@
 /// call which references or returns a deserialized V1 advertisement.
 void np_ffi_global_config_set_max_num_deserialized_v1_advertisements(uint32_t max_num_deserialized_v1_advertisements);
 
-/// Allocates a new credential-book, returning a handle to the created object
-CreateCredentialBookResult np_ffi_create_credential_book();
+/// Allocates a new credential-book from the given slab, returning a handle
+/// to the created object. The slab will be deallocated by this call.
+CreateCredentialBookResult np_ffi_create_credential_book_from_slab(CredentialSlab slab);
 
 /// Gets the tag of a `CreateCredentialBookResult` tagged enum.
 CreateCredentialBookResultKind np_ffi_CreateCredentialBookResult_kind(CreateCredentialBookResult result);
@@ -129,9 +144,40 @@
 /// case where the passed value is of a different enum variant.
 CredentialBook np_ffi_CreateCredentialBookResult_into_SUCCESS(CreateCredentialBookResult result);
 
+/// Deallocates a credential-slab by its handle.
+DeallocateResult np_ffi_deallocate_credential_slab(CredentialSlab credential_slab);
+
 /// Deallocates a credential-book by its handle
 DeallocateResult np_ffi_deallocate_credential_book(CredentialBook credential_book);
 
+/// Allocates a new credential-slab, returning a handle to the created object
+CreateCredentialSlabResult np_ffi_create_credential_slab();
+
+/// Gets the tag of a `CreateCredentialSlabResult` tagged enum.
+CreateCredentialSlabResultKind np_ffi_CreateCredentialSlabResult_kind(CreateCredentialSlabResult result);
+
+/// Casts a `CreateCredentialSlabResult` to the `SUCCESS` variant, panicking in the
+/// case where the passed value is of a different enum variant.
+CredentialSlab np_ffi_CreateCredentialSlabResult_into_SUCCESS(CreateCredentialSlabResult result);
+
+/// Adds the given V0 discovery credential with some associated
+/// match-data to this credential slab.
+///
+/// Safety: this is safe if the provided pointer points to a valid memory address
+/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic,
+/// so concurrent modification of the array from another thread would cause undefined behavior.
+AddCredentialToSlabResult np_ffi_CredentialSlab_add_v0_credential(CredentialSlab credential_slab,
+                                                                  V0MatchableCredential v0_cred);
+
+/// Adds the given V1 discovery credential with some associated
+/// match-data to this credential slab.
+///
+/// Safety: this is safe if the provided pointer points to a valid memory address
+/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic,
+/// so concurrent modification of the array from another thread would cause undefined behavior.
+AddCredentialToSlabResult np_ffi_CredentialSlab_add_v1_credential(CredentialSlab credential_slab,
+                                                                  V1MatchableCredential v1_cred);
+
 /// Attempts to deserialize an advertisement with the given service-data
 /// payload (presumed to be under the NP service UUID) using credentials
 /// pulled from the given credential-book.
@@ -174,15 +220,12 @@
 /// Gets just the data-element payload of a `LegibleDeserializedV0Advertisement`.
 V0Payload np_ffi_LegibleDeserializedV0Advertisement_into_payload(LegibleDeserializedV0Advertisement adv);
 
-/// Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`.
-DeserializedV0Identity np_ffi_LegibleDeserializedV0Advertisement_into_identity(LegibleDeserializedV0Advertisement adv);
+/// Gets just the identity kind associated with a `LegibleDeserializedV0Advertisement`.
+DeserializedV0IdentityKind np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind(LegibleDeserializedV0Advertisement adv);
 
 /// Deallocates any internal data of a `LegibleDeserializedV0Advertisement`
 DeallocateResult np_ffi_deallocate_legible_v0_advertisement(LegibleDeserializedV0Advertisement adv);
 
-/// Gets the tag of the `DeserializedV0Identity` tagged-union.
-DeserializedV0IdentityKind np_ffi_DeserializedV0Identity_kind(DeserializedV0Identity identity);
-
 /// Attempts to get the data-element with the given index in the passed v0 adv payload
 GetV0DEResult np_ffi_V0Payload_get_de(V0Payload payload, uint8_t index);
 
diff --git a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h
index 4d66308..d39bb70 100644
--- a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h
+++ b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h
@@ -36,6 +36,14 @@
 namespace np_ffi {
 namespace internal {
 
+/// Result type for trying to add a credential to a credential-slab.
+enum class AddCredentialToSlabResult : uint8_t {
+  /// We succeeded in adding the credential to the slab.
+  Success = 0,
+  /// The handle to the slab was actually invalid.
+  InvalidHandle = 1,
+};
+
 /// The possible boolean action types which can be present in an Actions data element
 enum class BooleanActionType : uint8_t {
   ActiveUnlock = 8,
@@ -49,11 +57,24 @@
 
 /// Discriminant for `CreateCredentialBookResult`
 enum class CreateCredentialBookResultKind : uint8_t {
-  /// There was no space left to create a new credential book
-  NoSpaceLeft = 0,
   /// We created a new credential book behind the given handle.
   /// The associated payload may be obtained via
   /// `CreateCredentialBookResult#into_success()`.
+  Success = 0,
+  /// There was no space left to create a new credential book
+  NoSpaceLeft = 1,
+  /// The slab that we tried to create a credential-book from
+  /// actually was an invalid handle.
+  InvalidSlabHandle = 2,
+};
+
+/// Discriminant for `CreateCredentialSlabResult`
+enum class CreateCredentialSlabResultKind : uint8_t {
+  /// There was no space left to create a new credential slab
+  NoSpaceLeft = 0,
+  /// We created a new credential slab behind the given handle.
+  /// The associated payload may be obtained via
+  /// `CreateCredentialSlabResult#into_success()`.
   Success = 1,
 };
 
@@ -93,14 +114,8 @@
   NoMatchingCredentials = 1,
 };
 
-/// Represents deserialized information about the V0 identity utilized
-/// by a deserialized V0 advertisement
-enum class DeserializedV0Identity {
-  Plaintext,
-  Decrypted,
-};
-
-/// Discriminant for `DeserializedV0Identity`.
+/// Discriminant for deserialized information about the V0
+/// identity utilized by a deserialized V0 advertisement.
 enum class DeserializedV0IdentityKind : uint8_t {
   /// The deserialized identity was a plaintext identity.
   Plaintext = 0,
@@ -193,8 +208,9 @@
 /// Result type for `create_credential_book`
 union CreateCredentialBookResult {
   enum class Tag : uint8_t {
-    NoSpaceLeft = 0,
-    Success = 1,
+    Success = 0,
+    NoSpaceLeft = 1,
+    InvalidSlabHandle = 2,
   };
 
   struct Success_Body {
@@ -208,6 +224,67 @@
   Success_Body success;
 };
 
+///A `#[repr(C)]` handle to a value of type `super::CredentialSlabInternals`.
+struct CredentialSlab {
+  uint64_t handle_id;
+};
+
+/// Result type for `create_credential_slab`
+struct CreateCredentialSlabResult {
+  enum class Tag {
+    NoSpaceLeft,
+    Success,
+  };
+
+  struct Success_Body {
+    CredentialSlab _0;
+  };
+
+  Tag tag;
+  union {
+    Success_Body success;
+  };
+};
+
+/// Cryptographic information about a particular V0 discovery credential
+/// necessary to match and decrypt encrypted V0 advertisements.
+struct V0DiscoveryCredential {
+  uint8_t key_seed[32];
+  uint8_t legacy_metadata_key_hmac[32];
+};
+
+/// A representation of a MatchedCredential which is passable across the FFI boundary
+struct FfiMatchedCredential {
+  uint32_t cred_id;
+  const uint8_t *encrypted_metadata_bytes_buffer;
+  uintptr_t encrypted_metadata_bytes_len;
+};
+
+/// Representation of a V0 credential that contains additional data to provide back to caller once it
+/// is matched. The credential_id can be used by the caller to correlate it back to the full
+/// credentials details.
+struct V0MatchableCredential {
+  V0DiscoveryCredential discovery_cred;
+  FfiMatchedCredential matched_cred;
+};
+
+/// Cryptographic information about a particular V1 discovery credential
+/// necessary to match and decrypt encrypted V1 advertisement sections.
+struct V1DiscoveryCredential {
+  uint8_t key_seed[32];
+  uint8_t expected_unsigned_metadata_key_hmac[32];
+  uint8_t expected_signed_metadata_key_hmac[32];
+  uint8_t pub_key[32];
+};
+
+/// Representation of a V1 credential that contains additional data to provide back to caller once it
+/// is matched. The credential_id can be used by the caller to correlate it back to the full
+/// credentials details.
+struct V1MatchableCredential {
+  V1DiscoveryCredential discovery_cred;
+  FfiMatchedCredential matched_cred;
+};
+
 ///A `#[repr(C)]` handle to a value of type `super::V0PayloadInternals`.
 struct V0Payload {
   uint64_t handle_id;
@@ -217,7 +294,7 @@
 struct LegibleDeserializedV0Advertisement {
   uint8_t num_des;
   V0Payload payload;
-  DeserializedV0Identity identity;
+  DeserializedV0IdentityKind identity_kind;
 };
 
 /// Represents a deserialized V0 advertisement
@@ -409,6 +486,8 @@
 /// This representation is stable, and so you may directly
 /// reference this struct's fields if you wish.
 struct GenericV1DataElement {
+  /// The offset of this generic data-element.
+  uint8_t offset;
   /// The DE type code of this generic data-element.
   V1DEType de_type;
   /// The raw data-element byte payload, up to
diff --git a/nearby/presence/np_c_ffi/src/credentials.rs b/nearby/presence/np_c_ffi/src/credentials.rs
index 6b60545..70949a6 100644
--- a/nearby/presence/np_c_ffi/src/credentials.rs
+++ b/nearby/presence/np_c_ffi/src/credentials.rs
@@ -14,15 +14,20 @@
 //! Credential-related data-types and functions
 
 use crate::{unwrap, PanicReason};
+use core::slice;
 use np_ffi_core::common::*;
 use np_ffi_core::credentials::credential_book::CredentialBook;
+use np_ffi_core::credentials::credential_slab::CredentialSlab;
 use np_ffi_core::credentials::*;
 use np_ffi_core::utils::FfiEnum;
 
-/// Allocates a new credential-book, returning a handle to the created object
+/// Allocates a new credential-book from the given slab, returning a handle
+/// to the created object. The slab will be deallocated by this call.
 #[no_mangle]
-pub extern "C" fn np_ffi_create_credential_book() -> CreateCredentialBookResult {
-    create_credential_book()
+pub extern "C" fn np_ffi_create_credential_book_from_slab(
+    slab: CredentialSlab,
+) -> CreateCredentialBookResult {
+    create_credential_book_from_slab(slab)
 }
 
 /// Gets the tag of a `CreateCredentialBookResult` tagged enum.
@@ -42,6 +47,14 @@
     unwrap(result.into_success(), PanicReason::EnumCastFailed)
 }
 
+/// Deallocates a credential-slab by its handle.
+#[no_mangle]
+pub extern "C" fn np_ffi_deallocate_credential_slab(
+    credential_slab: CredentialSlab,
+) -> DeallocateResult {
+    deallocate_credential_slab(credential_slab)
+}
+
 /// Deallocates a credential-book by its handle
 #[no_mangle]
 pub extern "C" fn np_ffi_deallocate_credential_book(
@@ -49,3 +62,98 @@
 ) -> DeallocateResult {
     deallocate_credential_book(credential_book)
 }
+
+/// Allocates a new credential-slab, returning a handle to the created object
+#[no_mangle]
+pub extern "C" fn np_ffi_create_credential_slab() -> CreateCredentialSlabResult {
+    create_credential_slab()
+}
+
+/// Gets the tag of a `CreateCredentialSlabResult` tagged enum.
+#[no_mangle]
+pub extern "C" fn np_ffi_CreateCredentialSlabResult_kind(
+    result: CreateCredentialSlabResult,
+) -> CreateCredentialSlabResultKind {
+    result.kind()
+}
+
+/// Casts a `CreateCredentialSlabResult` to the `SUCCESS` variant, panicking in the
+/// case where the passed value is of a different enum variant.
+#[no_mangle]
+pub extern "C" fn np_ffi_CreateCredentialSlabResult_into_SUCCESS(
+    result: CreateCredentialSlabResult,
+) -> CredentialSlab {
+    unwrap(result.into_success(), PanicReason::EnumCastFailed)
+}
+
+/// Representation of a V0 credential that contains additional data to provide back to caller once it
+/// is matched. The credential_id can be used by the caller to correlate it back to the full
+/// credentials details.
+#[repr(C)]
+pub struct V0MatchableCredential {
+    discovery_cred: V0DiscoveryCredential,
+    matched_cred: FfiMatchedCredential,
+}
+
+/// Representation of a V1 credential that contains additional data to provide back to caller once it
+/// is matched. The credential_id can be used by the caller to correlate it back to the full
+/// credentials details.
+#[repr(C)]
+pub struct V1MatchableCredential {
+    discovery_cred: V1DiscoveryCredential,
+    matched_cred: FfiMatchedCredential,
+}
+
+/// A representation of a MatchedCredential which is passable across the FFI boundary
+#[repr(C)]
+pub struct FfiMatchedCredential {
+    cred_id: u32,
+    encrypted_metadata_bytes_buffer: *const u8,
+    encrypted_metadata_bytes_len: usize,
+}
+
+/// Adds the given V0 discovery credential with some associated
+/// match-data to this credential slab.
+///
+/// Safety: this is safe if the provided pointer points to a valid memory address
+/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic,
+/// so concurrent modification of the array from another thread would cause undefined behavior.
+#[no_mangle]
+pub extern "C" fn np_ffi_CredentialSlab_add_v0_credential(
+    credential_slab: CredentialSlab,
+    v0_cred: V0MatchableCredential,
+) -> AddCredentialToSlabResult {
+    #[allow(unsafe_code)]
+    let metadata_slice = unsafe {
+        slice::from_raw_parts(
+            v0_cred.matched_cred.encrypted_metadata_bytes_buffer,
+            v0_cred.matched_cred.encrypted_metadata_bytes_len,
+        )
+    };
+
+    let matched_credential = MatchedCredential::new(v0_cred.matched_cred.cred_id, metadata_slice);
+    credential_slab.add_v0(v0_cred.discovery_cred, matched_credential)
+}
+
+/// Adds the given V1 discovery credential with some associated
+/// match-data to this credential slab.
+///
+/// Safety: this is safe if the provided pointer points to a valid memory address
+/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic,
+/// so concurrent modification of the array from another thread would cause undefined behavior.
+#[no_mangle]
+pub extern "C" fn np_ffi_CredentialSlab_add_v1_credential(
+    credential_slab: CredentialSlab,
+    v1_cred: V1MatchableCredential,
+) -> AddCredentialToSlabResult {
+    #[allow(unsafe_code)]
+    let metadata_slice = unsafe {
+        slice::from_raw_parts(
+            v1_cred.matched_cred.encrypted_metadata_bytes_buffer,
+            v1_cred.matched_cred.encrypted_metadata_bytes_len,
+        )
+    };
+
+    let matched_credential = MatchedCredential::new(v1_cred.matched_cred.cred_id, metadata_slice);
+    credential_slab.add_v1(v1_cred.discovery_cred, matched_credential)
+}
diff --git a/nearby/presence/np_c_ffi/src/deserialize/v0.rs b/nearby/presence/np_c_ffi/src/deserialize/v0.rs
index 24c4c2a..28bec8a 100644
--- a/nearby/presence/np_c_ffi/src/deserialize/v0.rs
+++ b/nearby/presence/np_c_ffi/src/deserialize/v0.rs
@@ -52,12 +52,12 @@
     adv.payload()
 }
 
-/// Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`.
+/// Gets just the identity kind associated with a `LegibleDeserializedV0Advertisement`.
 #[no_mangle]
-pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_into_identity(
+pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind(
     adv: LegibleDeserializedV0Advertisement,
-) -> DeserializedV0Identity {
-    adv.identity()
+) -> DeserializedV0IdentityKind {
+    adv.identity_kind()
 }
 
 /// Deallocates any internal data of a `LegibleDeserializedV0Advertisement`
@@ -68,14 +68,6 @@
     adv.deallocate()
 }
 
-/// Gets the tag of the `DeserializedV0Identity` tagged-union.
-#[no_mangle]
-pub extern "C" fn np_ffi_DeserializedV0Identity_kind(
-    identity: DeserializedV0Identity,
-) -> DeserializedV0IdentityKind {
-    identity.kind()
-}
-
 /// Attempts to get the data-element with the given index in the passed v0 adv payload
 #[no_mangle]
 pub extern "C" fn np_ffi_V0Payload_get_de(payload: V0Payload, index: u8) -> GetV0DEResult {
diff --git a/nearby/presence/np_c_ffi/src/lib.rs b/nearby/presence/np_c_ffi/src/lib.rs
index 0dd6d33..84e9859 100644
--- a/nearby/presence/np_c_ffi/src/lib.rs
+++ b/nearby/presence/np_c_ffi/src/lib.rs
@@ -147,6 +147,23 @@
     np_ffi_core::common::global_config_set_num_shards(num_shards)
 }
 
+/// Sets the maximum number of active handles to credential slabs
+/// which may be active at any one time.
+/// Default value: Max value.
+/// Max value: `u32::MAX - 1`.
+///
+/// Useful for bounding the maximum memory used by the client application
+/// on credential slabs in constrained-memory environments.
+///
+/// Setting this value will have no effect if the handle-maps for the
+/// API have already begun being used by the client code, and any
+/// values set will take effect upon the first usage of any API
+/// call utilizing credential slabs.
+#[no_mangle]
+pub extern "C" fn np_ffi_global_config_set_max_num_credential_slabs(max_num_credential_slabs: u32) {
+    np_ffi_core::common::global_config_set_max_num_credential_slabs(max_num_credential_slabs)
+}
+
 /// Sets the maximum number of active handles to credential books
 /// which may be active at any one time.
 /// Default value: Max value.
diff --git a/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc b/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc
index 72a3f91..164bab3 100644
--- a/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc
+++ b/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc
@@ -40,7 +40,9 @@
 
 BENCHMARK_DEFINE_F(NpCppBenchmark, V0PlaintextAdvertisement)
 (benchmark::State &state) {
-  auto cred_book = nearby_protocol::CredentialBook::TryCreate();
+  auto cred_slab = nearby_protocol::CredentialSlab::TryCreate();
+  assert(cred_slab.ok());
+  auto cred_book = nearby_protocol::CredentialBook::TryCreateFromSlab(cred_slab.value());
   assert(cred_book.ok());
   auto num_ciphers = state.range(0);
 
@@ -68,7 +70,14 @@
 BENCHMARK_DEFINE_F(NpCBenchmark, V0PlaintextAdvertisement)
 (benchmark::State &state) {
   auto num_ciphers = state.range(0);
-  auto book_result = np_ffi::internal::np_ffi_create_credential_book();
+  auto slab_result = np_ffi::internal::np_ffi_create_credential_slab();
+  assert(
+      np_ffi::internal::np_ffi_CreateCredentialSlabResult_kind(slab_result) ==
+      np_ffi::internal::CreateCredentialSlabResultKind::Success);
+  auto slab = np_ffi::internal::np_ffi_CreateCredentialSlabResult_into_SUCCESS(
+      slab_result);
+
+  auto book_result = np_ffi::internal::np_ffi_create_credential_book_from_slab(slab);
   assert(
       np_ffi::internal::np_ffi_CreateCredentialBookResult_kind(book_result) ==
       np_ffi::internal::CreateCredentialBookResultKind::Success);
@@ -99,4 +108,4 @@
     ->Range(1, 1000)
     ->Unit(benchmark::kMicrosecond);
 
-BENCHMARK_MAIN();
\ No newline at end of file
+BENCHMARK_MAIN();
diff --git a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc
index 84d7247..3c409e7 100644
--- a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc
+++ b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc
@@ -34,7 +34,13 @@
   nearby_protocol::RawAdvertisementPayload payload(
       (nearby_protocol::ByteBuffer<255>(raw_bytes)));
 
-  auto credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  if (!credential_slab.ok()) {
+    printf("Error: create Credential slab failed\n");
+    __builtin_trap();
+  }
+
+  auto credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(credential_slab.value());
   if (!credential_book.ok()) {
     printf("Error: create Credential book failed\n");
     __builtin_trap();
@@ -45,4 +51,4 @@
           payload, credential_book.value());
 
   return 0;
-}
\ No newline at end of file
+}
diff --git a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc
index c08b6cd..2475b5e 100644
--- a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc
+++ b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc
@@ -32,7 +32,13 @@
   nearby_protocol::RawAdvertisementPayload payload(
       (nearby_protocol::ByteBuffer<255>(raw_bytes)));
 
-  auto credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  if (!credential_slab.ok()) {
+    printf("Error: create Credential slab failed\n");
+    __builtin_trap();
+  }
+
+  auto credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(credential_slab.value());
   if (!credential_book.ok()) {
     printf("Error: create Credential book failed\n");
     __builtin_trap();
@@ -51,4 +57,4 @@
       payload, credential_book.value());
 
   return 0;
-}
\ No newline at end of file
+}
diff --git a/nearby/presence/np_cpp_ffi/include/nearby_protocol.h b/nearby/presence/np_cpp_ffi/include/nearby_protocol.h
index e7e46e7..1e16eef 100644
--- a/nearby/presence/np_cpp_ffi/include/nearby_protocol.h
+++ b/nearby/presence/np_cpp_ffi/include/nearby_protocol.h
@@ -51,6 +51,7 @@
 
 // Re-exporting cbindgen generated types which are used in the public API
 using np_ffi::internal::BooleanActionType;
+using np_ffi::internal::CreateCredentialSlabResultKind;
 using np_ffi::internal::CreateCredentialBookResultKind;
 using np_ffi::internal::DeserializeAdvertisementResultKind;
 using np_ffi::internal::DeserializedV0AdvertisementKind;
@@ -71,7 +72,6 @@
 
 // V0 Classes
 class DeserializedV0Advertisement;
-class DeserializedV0Identity;
 class LegibleDeserializedV0Advertisement;
 class V0DataElement;
 class V0Payload;
@@ -109,6 +109,12 @@
   // np_ffi_global_config_set_num_shards in np_cpp_ffi_functions.h for more info
   static void SetNumShards(uint8_t num_shards);
 
+  // Sets the maximum number of active handles to credential slabs which may be
+  // active at any one time. See
+  // np_ffi_global_config_set_max_num_credential_slabs in np_cpp_ffi_functions.h
+  // for more info
+  static void SetMaxNumCredentialSlabs(uint32_t max_num_credential_slabs);
+
   // Sets the maximum number of active handles to credential books which may be
   // active at any one time. See
   // np_ffi_global_config_set_max_num_credential_books in np_cpp_ffi_functions.h
@@ -128,6 +134,37 @@
       uint32_t max_num_deserialized_v1_advertisements);
 };
 
+// Holds the credentials used in the construction of a credential book
+// using CredentialBook::TryCreateFromSlab()
+class CredentialSlab {
+public:
+  // Don't allow copy constructor or copy assignment, since that would result in
+  // the underlying handle being freed multiple times
+  CredentialSlab(const CredentialSlab &other) = delete;
+  CredentialSlab &operator=(const CredentialSlab &other) = delete;
+
+  // Move constructor and move assignment are needed in order to wrap this class
+  // in absl::StatusOr
+  CredentialSlab(CredentialSlab &&other) noexcept;
+  CredentialSlab &operator=(CredentialSlab &&other) noexcept;
+
+  // The destructor for a CredentialSlab, this will be called when a
+  // CredentialSlab instance goes out of scope and will free the underlying
+  // resources
+  ~CredentialSlab();
+
+  // Creates a new instance of a CredentialSlab, returns the CredentialSlab on
+  // success or a Status code on failure
+  [[nodiscard]] static absl::StatusOr<CredentialSlab> TryCreate();
+private:
+  friend class CredentialBook;
+  explicit CredentialSlab(np_ffi::internal::CredentialSlab credential_slab)
+      : credential_slab_(credential_slab), moved_(false) {}
+
+  np_ffi::internal::CredentialSlab credential_slab_;
+  bool moved_;
+};
+
 // Holds the credentials used when decrypting data of an advertisement.
 // This needs to be passed to Deserializer::DeserializeAdvertisement() when
 // attempting to deserialize a payload
@@ -148,9 +185,11 @@
   // resources
   ~CredentialBook();
 
-  // Creates a new instance of a CredentialBook, returns the CredentialBook on
-  // success or a Status code on failure
-  [[nodiscard]] static absl::StatusOr<CredentialBook> TryCreate();
+  // Creates a new instance of a CredentialBook from a CredentialSlab,
+  // returning the CredentialBook on success or a Status code on failure.
+  // The passed credential-slab will be deallocated if this operation
+  // is successful.
+  [[nodiscard]] static absl::StatusOr<CredentialBook> TryCreateFromSlab(CredentialSlab &slab);
 
 private:
   friend class Deserializer;
@@ -339,8 +378,9 @@
   // and will free the underlying parent handle.
   ~LegibleDeserializedV0Advertisement();
 
-  // Returns just the identity information associated with the advertisement
-  [[nodiscard]] DeserializedV0Identity GetIdentity();
+  // Returns just the kind of identity (public/encrypted)
+  // associated with the advertisement
+  [[nodiscard]] DeserializedV0IdentityKind GetIdentityKind();
   // Returns the number of data elements in the advertisement
   [[nodiscard]] uint8_t GetNumberOfDataElements();
   // Returns just the data-element payload of the advertisement
@@ -358,20 +398,6 @@
   bool moved_;
 };
 
-// A V0 identity of an advertisement
-class DeserializedV0Identity {
-public:
-  // Returns the DeserializedV0IdentityKind of the advertisement
-  [[nodiscard]] DeserializedV0IdentityKind GetKind();
-
-private:
-  friend class LegibleDeserializedV0Advertisement;
-  explicit DeserializedV0Identity(
-      np_ffi::internal::DeserializedV0Identity v0_identity)
-      : v0_identity_(v0_identity) {}
-  np_ffi::internal::DeserializedV0Identity v0_identity_;
-};
-
 // A data element payload of a Deserialized V0 Advertisement.
 class V0Payload {
 public:
diff --git a/nearby/presence/np_cpp_ffi/nearby_protocol.cc b/nearby/presence/np_cpp_ffi/nearby_protocol.cc
index 3ee5c63..106eb77 100644
--- a/nearby/presence/np_cpp_ffi/nearby_protocol.cc
+++ b/nearby/presence/np_cpp_ffi/nearby_protocol.cc
@@ -67,6 +67,11 @@
   np_ffi::internal::np_ffi_global_config_set_num_shards(num_shards);
 }
 
+void GlobalConfig::SetMaxNumCredentialSlabs(uint32_t max_num_credential_slabs) {
+  np_ffi::internal::np_ffi_global_config_set_max_num_credential_slabs(
+      max_num_credential_slabs);
+}
+
 void GlobalConfig::SetMaxNumCredentialBooks(uint32_t max_num_credential_books) {
   np_ffi::internal::np_ffi_global_config_set_max_num_credential_books(
       max_num_credential_books);
@@ -86,21 +91,73 @@
           max_num_deserialized_v1_advertisements);
 }
 
-absl::StatusOr<CredentialBook> CredentialBook::TryCreate() {
-  auto result = np_ffi::internal::np_ffi_create_credential_book();
-  auto kind = np_ffi::internal::np_ffi_CreateCredentialBookResult_kind(result);
+absl::StatusOr<CredentialSlab> CredentialSlab::TryCreate() {
+  auto result = np_ffi::internal::np_ffi_create_credential_slab();
+  auto kind = np_ffi::internal::np_ffi_CreateCredentialSlabResult_kind(result);
 
   switch (kind) {
-  case CreateCredentialBookResultKind::Success: {
-    auto book = CredentialBook(
-        np_ffi::internal::np_ffi_CreateCredentialBookResult_into_SUCCESS(
+  case CreateCredentialSlabResultKind::Success: {
+    auto slab = CredentialSlab(
+        np_ffi::internal::np_ffi_CreateCredentialSlabResult_into_SUCCESS(
             result));
-    return book;
+    return slab;
+  }
+  case CreateCredentialSlabResultKind::NoSpaceLeft: {
+    return absl::ResourceExhaustedError(
+        "No space left to create credential slab");
+  }
+  }
+}
+
+CredentialSlab::~CredentialSlab() {
+  if (!this->moved_) {
+    auto result =
+        np_ffi::internal::np_ffi_deallocate_credential_slab(credential_slab_);
+    assert_panic(result == np_ffi::internal::DeallocateResult::Success);
+  }
+}
+
+CredentialSlab::CredentialSlab(CredentialSlab &&other) noexcept
+    : credential_slab_(other.credential_slab_), moved_(other.moved_) {
+  other.credential_slab_ = {};
+  other.moved_ = true;
+}
+
+CredentialSlab &CredentialSlab::operator=(CredentialSlab &&other) noexcept {
+  if (this != &other) {
+    if (!this->moved_) {
+      auto result = np_ffi::internal::np_ffi_deallocate_credential_slab(
+          this->credential_slab_);
+      assert_panic(result == np_ffi::internal::DeallocateResult::Success);
+    }
+
+    this->credential_slab_ = other.credential_slab_;
+    this->moved_ = other.moved_;
+
+    other.credential_slab_ = {};
+    other.moved_ = true;
+  }
+  return *this;
+}
+
+absl::StatusOr<CredentialBook> CredentialBook::TryCreateFromSlab(CredentialSlab &slab) {
+  assert_panic(!slab.moved_);
+  auto result = np_ffi::internal::np_ffi_create_credential_book_from_slab(slab.credential_slab_);
+  auto kind = np_ffi::internal::np_ffi_CreateCredentialBookResult_kind(result);
+  switch (kind) {
+  case CreateCredentialBookResultKind::Success: {
+    auto book = np_ffi::internal::np_ffi_CreateCredentialBookResult_into_SUCCESS(result);
+    slab.moved_ = true;
+    return CredentialBook(book);
   }
   case CreateCredentialBookResultKind::NoSpaceLeft: {
     return absl::ResourceExhaustedError(
         "No space left to create credential book");
   }
+  case CreateCredentialBookResultKind::InvalidSlabHandle: {
+    return absl::NotFoundError(
+        "The slab referenced by the given handle was not found.");
+  }
   }
 }
 
@@ -286,12 +343,12 @@
   }
 }
 
-DeserializedV0Identity LegibleDeserializedV0Advertisement::GetIdentity() {
+DeserializedV0IdentityKind LegibleDeserializedV0Advertisement::GetIdentityKind() {
   assert_panic(!this->moved_);
   auto result =
-      np_ffi::internal::np_ffi_LegibleDeserializedV0Advertisement_into_identity(
+      np_ffi::internal::np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind(
           legible_v0_advertisement_);
-  return DeserializedV0Identity(result);
+  return result;
 }
 
 uint8_t LegibleDeserializedV0Advertisement::GetNumberOfDataElements() {
@@ -309,10 +366,6 @@
   return V0Payload(result);
 }
 
-np_ffi::internal::DeserializedV0IdentityKind DeserializedV0Identity::GetKind() {
-  return np_ffi::internal::np_ffi_DeserializedV0Identity_kind(v0_identity_);
-}
-
 V0Payload::V0Payload(V0Payload &&other) noexcept
     : v0_payload_(other.v0_payload_), moved_(other.moved_) {
   other.v0_payload_ = {};
diff --git a/nearby/presence/np_cpp_ffi/sample/main.cc b/nearby/presence/np_cpp_ffi/sample/main.cc
index a18b996..a02a523 100644
--- a/nearby/presence/np_cpp_ffi/sample/main.cc
+++ b/nearby/presence/np_cpp_ffi/sample/main.cc
@@ -25,7 +25,7 @@
 
 void HandleV0Adv(nearby_protocol::DeserializedV0Advertisement);
 void HandleLegibleV0Adv(nearby_protocol::LegibleDeserializedV0Advertisement);
-void HandleV0Identity(nearby_protocol::DeserializedV0Identity);
+void HandleV0IdentityKind(nearby_protocol::DeserializedV0IdentityKind);
 void HandleDataElement(nearby_protocol::V0DataElement);
 
 void HandleV1Adv(nearby_protocol::DeserializedV1Advertisement);
@@ -46,8 +46,15 @@
   std::cout << "\n========= Example V0 Adv ==========\n";
   std::cout << "Hex bytes: 00031503260046\n\n";
 
-  // Create an empty credential book and verify that is is successful
-  auto cred_book_result = nearby_protocol::CredentialBook::TryCreate();
+  // Create an empty credential slab and verify that it is successful
+  auto cred_slab_result = nearby_protocol::CredentialSlab::TryCreate();
+  if (!cred_slab_result.ok()) {
+    std::cout << cred_slab_result.status().ToString();
+    return -1;
+  }
+
+  // Create an empty credential book from the empty slab, and verify success.
+  auto cred_book_result = nearby_protocol::CredentialBook::TryCreateFromSlab(cred_slab_result.value());
   if (!cred_book_result.ok()) {
     std::cout << cred_book_result.status().ToString();
     return -1;
@@ -171,7 +178,7 @@
 
 void HandleLegibleV0Adv(
     nearby_protocol::LegibleDeserializedV0Advertisement legible_adv) {
-  HandleV0Identity(legible_adv.GetIdentity());
+  HandleV0IdentityKind(legible_adv.GetIdentityKind());
 
   auto num_des = legible_adv.GetNumberOfDataElements();
   std::cout << "\t\tAdv contains " << unsigned(num_des) << " data elements \n";
@@ -187,8 +194,8 @@
   }
 }
 
-void HandleV0Identity(nearby_protocol::DeserializedV0Identity identity) {
-  switch (identity.GetKind()) {
+void HandleV0IdentityKind(nearby_protocol::DeserializedV0IdentityKind identity) {
+  switch (identity) {
   case np_ffi::internal::DeserializedV0IdentityKind::Plaintext: {
     std::cout << "\t\tIdentity is Plaintext\n";
     break;
diff --git a/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt b/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt
index 7ca9eb0..b5ff838 100644
--- a/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt
+++ b/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt
@@ -17,6 +17,7 @@
         deserialize_result_tests.cc
         deserialize_v0_tests.cc
         deserialize_v1_tests.cc
+	credential_slab_tests.cc
         credential_book_tests.cc
         byte_buffer_tests.cc
         np_cpp_test.h
diff --git a/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc b/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc
index 4bca8ec..15f262e 100644
--- a/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc
@@ -70,9 +70,10 @@
 
   nearby_protocol::RawAdvertisementPayload adv(buffer.value());
 
-  auto credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto credential_slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(credential_slab).value();
   auto str = nearby_protocol::Deserializer::DeserializeAdvertisement(
-                 adv, credential_book.value())
+                 adv, credential_book)
                  .IntoV1()
                  .TryGetSection(0)
                  .value()
diff --git a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
index 3a32904..dcaf421 100644
--- a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
@@ -19,19 +19,27 @@
 #include "gtest/gtest.h"
 
 TEST_F(NpCppTest, TestSetMaxCredBooks) {
-  auto book1_result = nearby_protocol::CredentialBook::TryCreate();
+  auto slab1_result = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(slab1_result.ok());
+  auto book1_result = nearby_protocol::CredentialBook::TryCreateFromSlab(slab1_result.value());
   ASSERT_TRUE(book1_result.ok());
 
-  auto book2_result = nearby_protocol::CredentialBook::TryCreate();
+  auto slab2_result = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(slab2_result.ok());
+  auto book2_result = nearby_protocol::CredentialBook::TryCreateFromSlab(slab2_result.value());
   ASSERT_TRUE(book2_result.ok());
 
-  auto book3_result = nearby_protocol::CredentialBook::TryCreate();
+  auto slab3_result = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(slab3_result.ok());
+  auto book3_result = nearby_protocol::CredentialBook::TryCreateFromSlab(slab3_result.value());
+
   ASSERT_FALSE(book3_result.ok());
   ASSERT_TRUE(absl::IsResourceExhausted(book3_result.status()));
 }
 
 TEST_F(NpCppTest, TestMoveConstructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
                                                               book);
@@ -62,7 +70,8 @@
 }
 
 TEST_F(NpCppTest, TestMoveAssignment) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
                                                               book);
@@ -70,7 +79,8 @@
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
   // create a second empty credential book
-  auto other_book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto other_slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto other_book = nearby_protocol::CredentialBook::TryCreateFromSlab(other_slab).value();
   other_book = std::move(book);
 
   // new credential book should still be successful
@@ -92,4 +102,4 @@
                    nearby_protocol::Deserializer::DeserializeAdvertisement(
                        V0AdvSimple, another_moved_book),
                "");
-}
\ No newline at end of file
+}
diff --git a/nearby/presence/np_cpp_ffi/tests/credential_slab_tests.cc b/nearby/presence/np_cpp_ffi/tests/credential_slab_tests.cc
new file mode 100644
index 0000000..f715361
--- /dev/null
+++ b/nearby/presence/np_cpp_ffi/tests/credential_slab_tests.cc
@@ -0,0 +1,56 @@
+// 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.
+
+#include "nearby_protocol.h"
+#include "shared_test_util.h"
+#include "np_cpp_test.h"
+
+#include "gtest/gtest.h"
+
+TEST_F(NpCppTest, TestSetMaxCredSlabs) {
+  auto slab1_result = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(slab1_result.ok());
+
+  auto slab2_result = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(slab2_result.ok());
+
+  auto slab3_result = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(slab3_result.ok());
+
+  auto slab4_result = nearby_protocol::CredentialSlab::TryCreate();
+
+  ASSERT_FALSE(slab4_result.ok());
+  ASSERT_TRUE(absl::IsResourceExhausted(slab4_result.status()));
+}
+
+TEST_F(NpCppTest, TestSlabMoveConstructor) {
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  // It should be possible to move the slab into a new object
+  // and use the moved version to successfully construct a
+  // credential-book.
+  nearby_protocol::CredentialSlab next_slab(std::move(slab));
+
+  auto maybe_book = nearby_protocol::CredentialBook::TryCreateFromSlab(next_slab);
+  ASSERT_TRUE(maybe_book.ok());
+
+  // Now, both slabs should be moved-out-of, since `TryCreateFromSlab` takes
+  // ownership. Verify that this is the case, and attempts to re-use the slabs
+  // result in an assert failure.
+  ASSERT_DEATH([[maybe_unused]] auto failure =
+                nearby_protocol::CredentialBook::TryCreateFromSlab(slab), //NOLINT(bugprone-use-after-move)
+               "");
+  ASSERT_DEATH([[maybe_unused]] auto failure =
+                nearby_protocol::CredentialBook::TryCreateFromSlab(next_slab),
+               "");
+}
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
index 737fd18..0469933 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
@@ -20,7 +20,8 @@
 #include "gtest/gtest.h"
 
 TEST_F(NpCppTest, TestResultMoveConstructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -57,8 +58,12 @@
 
   nearby_protocol::RawAdvertisementPayload adv(buffer.value());
 
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
+
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
           adv, maybe_credential_book.value());
@@ -70,8 +75,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
@@ -88,7 +93,8 @@
 }
 
 TEST_F(NpCppTest, TestResultMoveAssignment) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -124,8 +130,13 @@
   // An invalid header result should result in error
   nearby_protocol::RawAdvertisementPayload InvalidHeaderPayload(
       nearby_protocol::ByteBuffer<255>({1, {0xFF}}));
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
+
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
           InvalidHeaderPayload, maybe_credential_book.value());
@@ -140,7 +151,12 @@
 }
 
 TEST_F(NpCppTest, TestInvalidV0Cast) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
+
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
           V1AdvSimple, maybe_credential_book.value());
@@ -153,8 +169,13 @@
 
 TEST_F(NpCppTest, TestInvalidV1Cast) {
   // Create an empty credential book and verify that is is successful
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
+
+
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
           V0AdvSimple, maybe_credential_book.value());
@@ -166,12 +187,15 @@
 }
 
 TEST_F(NpCppTest, V0UseResultTwice) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V0AdvSimple, book_result.value());
+          V0AdvSimple, maybe_credential_book.value());
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
@@ -184,12 +208,15 @@
 }
 
 TEST_F(NpCppTest, V1UseResultTwice) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V1AdvSimple, book_result.value());
+          V1AdvSimple, maybe_credential_book.value());
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V1);
 
@@ -202,12 +229,15 @@
 }
 
 TEST_F(NpCppTest, IntoV0AfterOutOfScope) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V0AdvSimple, book_result.value());
+          V0AdvSimple, maybe_credential_book.value());
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
@@ -221,12 +251,15 @@
 }
 
 TEST_F(NpCppTest, IntoV1AfterOutOfScope) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V1AdvSimple, book_result.value());
+          V1AdvSimple, maybe_credential_book.value());
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V1);
 
@@ -240,12 +273,15 @@
 }
 
 TEST_F(NpCppTest, V0ResultKindAfterOutOfScope) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V0AdvSimple, book_result.value());
+          V0AdvSimple, maybe_credential_book.value());
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
@@ -259,12 +295,15 @@
 }
 
 TEST_F(NpCppTest, V1ResultKindAfterOutOfScope) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
+  ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V1AdvSimple, book_result.value());
+          V1AdvSimple, maybe_credential_book.value());
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V1);
 
@@ -275,4 +314,4 @@
   // in a crash.
   ASSERT_DEATH(
       { [[maybe_unused]] auto failure = deserialize_result.GetKind(); }, "");
-}
\ No newline at end of file
+}
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
index e2a10a0..54b17ab 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
@@ -19,7 +19,8 @@
 #include "gtest/gtest.h"
 
 TEST_F(NpCppTest, InvalidCast) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
   ASSERT_EQ(deserialize_result.GetKind(),
@@ -40,7 +41,8 @@
            0x15, 0x03} // Length 1 Tx Power DE with value 3
       }));
 
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab);
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -53,8 +55,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
@@ -79,7 +81,9 @@
            0x16, 0x00} // Length 1 Actions DE
       }));
 
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -92,8 +96,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
@@ -118,7 +122,9 @@
            0x26, 0xD0, 0x46} // Length 2 Actions DE
       }));
 
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -131,8 +137,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
@@ -174,7 +180,9 @@
            0x26, 0x00, 0x46, // Length 2 Actions
        }}));
 
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -187,8 +195,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
@@ -214,7 +222,9 @@
 }
 
 TEST_F(NpCppTest, V0EmptyPayload) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -225,7 +235,8 @@
 }
 
 TEST_F(NpCppTest, TestV0AdvMoveConstructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -252,7 +263,8 @@
 }
 
 TEST_F(NpCppTest, TestV0AdvMoveAssignment) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -292,7 +304,9 @@
 }
 
 TEST_F(NpCppTest, V0AdvDestructor) {
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto book_result = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(book_result.ok());
   {
     auto deserialize_result = CreateAdv(book_result.value());
@@ -326,7 +340,9 @@
 }
 
 TEST_F(NpCppTest, V0AdvUseAfterMove) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -346,7 +362,8 @@
 }
 
 TEST_F(NpCppTest, TestLegibleAdvMoveConstructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -356,13 +373,13 @@
   // Now move the adv into a new value, and make sure its still valid
   nearby_protocol::LegibleDeserializedV0Advertisement moved(std::move(legible));
   ASSERT_EQ(moved.GetNumberOfDataElements(), 1);
-  ASSERT_EQ(moved.GetIdentity().GetKind(),
+  ASSERT_EQ(moved.GetIdentityKind(),
             np_ffi::internal::DeserializedV0IdentityKind::Plaintext);
 
   // trying to use the moved object should result in a use after free which
   // triggers an abort
   ASSERT_DEATH([[maybe_unused]] auto failure =
-                   legible.GetIdentity(), // NOLINT(bugprone-use-after-move
+                   legible.GetIdentityKind(), // NOLINT(bugprone-use-after-move
                "");
   ASSERT_DEATH(
       [[maybe_unused]] auto failure = legible.GetNumberOfDataElements(), "");
@@ -372,7 +389,7 @@
   // abort
   nearby_protocol::LegibleDeserializedV0Advertisement moved_again(
       std::move(legible));
-  ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentity(), "");
+  ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentityKind(), "");
   ASSERT_DEATH([[maybe_unused]] auto failure =
                    moved_again.GetNumberOfDataElements(),
                "");
@@ -380,7 +397,8 @@
 }
 
 TEST_F(NpCppTest, TestLegibleAdvMoveAssignment) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -396,13 +414,13 @@
 
   // move adv2 into adv, the original should be deallocated by assignment
   legible2 = std::move(legible);
-  ASSERT_EQ(legible2.GetIdentity().GetKind(),
+  ASSERT_EQ(legible2.GetIdentityKind(),
             np_ffi::internal::DeserializedV0IdentityKind::Plaintext);
 
   // original result should now be invalid, using it will trigger a use after
   // free abort.
   ASSERT_DEATH([[maybe_unused]] auto failure =
-                   legible.GetIdentity(), // NOLINT(bugprone-use-after-move)
+                   legible.GetIdentityKind(), // NOLINT(bugprone-use-after-move)
                "");
   ASSERT_DEATH(
       [[maybe_unused]] auto failure = legible.GetNumberOfDataElements(), "");
@@ -411,7 +429,7 @@
   // moving again should still lead to an error
   auto moved_again = std::move(legible);
   ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.IntoPayload(), "");
-  ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentity(), "");
+  ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentityKind(), "");
   ASSERT_DEATH([[maybe_unused]] auto failure =
                    moved_again.GetNumberOfDataElements(),
                "");
@@ -426,19 +444,20 @@
 }
 
 TEST_F(NpCppTest, V0LegibleAdvUseAfterMove) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto legible_adv = CreateLegibleAdv(book);
 
   // Should be able to use the valid legible adv even though its original parent
   // is now out of scope.
-  ASSERT_EQ(legible_adv.GetIdentity().GetKind(),
+  ASSERT_EQ(legible_adv.GetIdentityKind(),
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
   ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1);
   [[maybe_unused]] auto payload = legible_adv.IntoPayload();
 
   // now that the legible adv has moved into the payload it should no longer be
   // valid
-  ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.GetIdentity(), "");
+  ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.GetIdentityKind(), "");
   ASSERT_DEATH([[maybe_unused]] auto failure =
                    legible_adv.GetNumberOfDataElements(),
                "");
@@ -446,16 +465,17 @@
 }
 
 TEST_F(NpCppTest, LegibleAdvDestructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   {
     auto legible_adv = CreateLegibleAdv(book);
     auto legible_adv2 = CreateLegibleAdv(book);
 
     // check that legible advs are valid.
-    ASSERT_EQ(legible_adv.GetIdentity().GetKind(),
+    ASSERT_EQ(legible_adv.GetIdentityKind(),
               nearby_protocol::DeserializedV0IdentityKind::Plaintext);
     ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1);
-    ASSERT_EQ(legible_adv2.GetIdentity().GetKind(),
+    ASSERT_EQ(legible_adv2.GetIdentityKind(),
               nearby_protocol::DeserializedV0IdentityKind::Plaintext);
     ASSERT_EQ(legible_adv2.GetNumberOfDataElements(), 1);
 
@@ -480,7 +500,8 @@
 }
 
 TEST_F(NpCppTest, V0PayloadDestructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   {
     auto payload = CreatePayload(book);
     auto payload2 = CreatePayload(book);
@@ -505,7 +526,8 @@
 }
 
 TEST_F(NpCppTest, TestV0PayloadMoveConstructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -531,7 +553,8 @@
 }
 
 TEST_F(NpCppTest, TestV0PayloadMoveAssignment) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -562,7 +585,9 @@
 }
 
 TEST_F(NpCppTest, InvalidDataElementCast) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -574,8 +599,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
@@ -591,7 +616,9 @@
 }
 
 TEST_F(NpCppTest, InvalidDataElementIndex) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -604,8 +631,8 @@
   ASSERT_EQ(v0_adv.GetKind(),
             nearby_protocol::DeserializedV0AdvertisementKind::Legible);
   auto legible_adv = v0_adv.IntoLegible();
-  auto identity = legible_adv.GetIdentity();
-  ASSERT_EQ(identity.GetKind(),
+  auto identity = legible_adv.GetIdentityKind();
+  ASSERT_EQ(identity,
             nearby_protocol::DeserializedV0IdentityKind::Plaintext);
 
   auto num_des = legible_adv.GetNumberOfDataElements();
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
index f46457e..aa32f7b 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
@@ -19,7 +19,9 @@
 #include "gtest/gtest.h"
 
 TEST_F(NpCppTest, V1SimpleTestCase) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
 
   auto deserialize_result =
@@ -57,7 +59,8 @@
 }
 
 TEST_F(NpCppTest, TestV1AdvMoveConstructor) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V1AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -93,7 +96,8 @@
 }
 
 TEST_F(NpCppTest, TestV1AdvMoveAssignment) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto slab = nearby_protocol::CredentialSlab::TryCreate().value();
+  auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V1AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
@@ -153,7 +157,9 @@
 }
 
 TEST_F(NpCppTest, TestSectionOwnership) {
-  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
+  auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate();
+  ASSERT_TRUE(maybe_credential_slab.ok());
+  auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value());
   ASSERT_TRUE(maybe_credential_book.ok());
 
   {
@@ -223,4 +229,4 @@
             nearby_protocol::DeserializedV1IdentityKind::Plaintext);
   ASSERT_EQ(section2.value().NumberOfDataElements(), 1);
 }
-*/
\ No newline at end of file
+*/
diff --git a/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h
index 4fc20e1..03eb03a 100644
--- a/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h
+++ b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h
@@ -29,6 +29,7 @@
       panic_handler_set = true;
       nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(2);
       nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(2);
+      nearby_protocol::GlobalConfig::SetMaxNumCredentialSlabs(3);
       nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(2);
     } else {
       ASSERT_FALSE(
diff --git a/nearby/presence/np_ed25519/Cargo.toml b/nearby/presence/np_ed25519/Cargo.toml
index 601fc82..bd42c65 100644
--- a/nearby/presence/np_ed25519/Cargo.toml
+++ b/nearby/presence/np_ed25519/Cargo.toml
@@ -4,9 +4,12 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 array_view.workspace = true
-crypto_provider = {workspace = true, features = ["raw_private_key_permit"]}
+crypto_provider = { workspace = true, features = ["raw_private_key_permit"] }
 sink.workspace = true
 tinyvec.workspace = true
 
diff --git a/nearby/presence/np_ed25519/src/lib.rs b/nearby/presence/np_ed25519/src/lib.rs
index 81a1a10..fe2ab5a 100644
--- a/nearby/presence/np_ed25519/src/lib.rs
+++ b/nearby/presence/np_ed25519/src/lib.rs
@@ -19,10 +19,6 @@
 //! or verified. These "context" bytes allow for usage of the
 //! same base key-pair for different purposes in the protocol.
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(missing_docs, clippy::indexing_slicing)]
-
-extern crate core;
 
 use array_view::ArrayView;
 use crypto_provider::ed25519::{
@@ -191,7 +187,8 @@
 
 impl<C: CryptoProvider> Clone for PublicKey<C> {
     fn clone(&self) -> Self {
-        Self::from_bytes(&self.to_bytes()).unwrap()
+        #[allow(clippy::expect_used)]
+        Self::from_bytes(&self.to_bytes()).expect("This should always succeed since self will always contain valid public key bytes, which is verified on creation")
     }
 }
 
@@ -250,6 +247,7 @@
     /// [`SignatureContext#write_length_prefixed`].
     fn create_signature_buffer(&self) -> impl Sink<u8> + AsRef<[u8]> {
         let mut buffer = ArrayVec::<[u8; MAX_SIGNATURE_BUFFER_LEN]>::new();
+        #[allow(clippy::expect_used)]
         self.write_length_prefixed(&mut buffer).expect("Context should always fit into sig buffer");
         buffer
     }
diff --git a/nearby/presence/np_ffi_core/Cargo.toml b/nearby/presence/np_ffi_core/Cargo.toml
index 126302f..0c14d1c 100644
--- a/nearby/presence/np_ffi_core/Cargo.toml
+++ b/nearby/presence/np_ffi_core/Cargo.toml
@@ -4,12 +4,17 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 array_view.workspace = true
+ldt_np_adv.workspace = true
 np_adv = { workspace = true, features = ["alloc"] }
+np_hkdf.workspace = true
 handle_map.workspace = true
 crypto_provider.workspace = true
-crypto_provider_default = {workspace = true, default-features = false}
+crypto_provider_default = { workspace = true, default-features = false }
 lock_adapter.workspace = true
 lazy_static.workspace = true
 
diff --git a/nearby/presence/np_ffi_core/src/common.rs b/nearby/presence/np_ffi_core/src/common.rs
index 1683b68..6333db5 100644
--- a/nearby/presence/np_ffi_core/src/common.rs
+++ b/nearby/presence/np_ffi_core/src/common.rs
@@ -40,6 +40,11 @@
     /// - In all other cases, 16 shards will be used by default.
     num_shards: u8,
 
+    /// The maximum number of credential slabs which may be active
+    /// at any one time. By default, this value will be set to
+    /// `u32::MAX - 1`, which is the upper-bound on this value.
+    max_num_credential_slabs: u32,
+
     /// The maximum number of credential books which may be active
     /// at any one time. By default, this value will be set to
     /// `u32::MAX - 1`, which is the upper-bound on this value.
@@ -68,6 +73,7 @@
     pub(crate) const fn new() -> Self {
         Self {
             num_shards: 0,
+            max_num_credential_slabs: DEFAULT_MAX_HANDLES,
             max_num_credential_books: DEFAULT_MAX_HANDLES,
             max_num_deserialized_v0_advertisements: DEFAULT_MAX_HANDLES,
             max_num_deserialized_v1_advertisements: DEFAULT_MAX_HANDLES,
@@ -92,6 +98,9 @@
             self.num_shards
         }
     }
+    pub(crate) fn max_num_credential_slabs(&self) -> u32 {
+        self.max_num_credential_slabs
+    }
     pub(crate) fn max_num_credential_books(&self) -> u32 {
         self.max_num_credential_books
     }
@@ -112,6 +121,13 @@
         self.max_num_credential_books = DEFAULT_MAX_HANDLES.min(max_num_credential_books)
     }
 
+    /// Sets the maximum number of active handles to credential-slabs
+    /// which may be active at any one time.
+    /// Max value: `u32::MAX - 1`.
+    pub fn set_max_num_credential_slabs(&mut self, max_num_credential_slabs: u32) {
+        self.max_num_credential_slabs = DEFAULT_MAX_HANDLES.min(max_num_credential_slabs)
+    }
+
     /// Sets the maximum number of active handles to deserialized v0
     /// advertisements which may be active at any one time.
     /// Max value: `u32::MAX - 1`.
@@ -140,6 +156,9 @@
 pub(crate) fn global_num_shards() -> u8 {
     COMMON_CONFIG.read().num_shards()
 }
+pub(crate) fn global_max_num_credential_slabs() -> u32 {
+    COMMON_CONFIG.read().max_num_credential_slabs()
+}
 pub(crate) fn global_max_num_credential_books() -> u32 {
     COMMON_CONFIG.read().max_num_credential_books()
 }
@@ -169,6 +188,17 @@
     config.set_num_shards(num_shards);
 }
 
+/// Sets the maximum number of active handles to credential slabs
+/// which may be active at any one time. Max value: `u32::MAX - 1`.
+///
+/// Setting this value will have no effect if the handle-maps for the
+/// API have already begun being used by the client code, and any
+/// values set will take effect upon the first usage of any API
+/// call utilizing credential slabs.
+pub fn global_config_set_max_num_credential_slabs(max_num_credential_slabs: u32) {
+    let mut config = COMMON_CONFIG.write();
+    config.set_max_num_credential_slabs(max_num_credential_slabs);
+}
 /// Sets the maximum number of active handles to credential books
 /// which may be active at any one time. Max value: `u32::MAX - 1`.
 ///
@@ -275,6 +305,32 @@
     }
 }
 
+/// The DE type for an encrypted identity
+#[derive(Clone, Copy)]
+#[repr(u8)]
+pub enum EncryptedIdentityType {
+    /// Identity for broadcasts to nearby devices with the same
+    /// logged-in-account (for some account).
+    Private = 1,
+    /// Identity for broadcasts to nearby devices which this
+    /// device has declared to trust.
+    Trusted = 2,
+    /// Identity for broadcasts to devices which have been provisioned
+    /// offline with this device.
+    Provisioned = 4,
+}
+
+impl From<np_adv::de_type::EncryptedIdentityDataElementType> for EncryptedIdentityType {
+    fn from(value: np_adv::de_type::EncryptedIdentityDataElementType) -> Self {
+        use np_adv::de_type::EncryptedIdentityDataElementType;
+        match value {
+            EncryptedIdentityDataElementType::Private => Self::Private,
+            EncryptedIdentityDataElementType::Trusted => Self::Trusted,
+            EncryptedIdentityDataElementType::Provisioned => Self::Provisioned,
+        }
+    }
+}
+
 /// Error raised when attempting to cast an enum to
 /// one of its variants, but the value is actually
 /// of a different variant than the requested one.
diff --git a/nearby/presence/np_ffi_core/src/credentials.rs b/nearby/presence/np_ffi_core/src/credentials.rs
index 5392417..0972d45 100644
--- a/nearby/presence/np_ffi_core/src/credentials.rs
+++ b/nearby/presence/np_ffi_core/src/credentials.rs
@@ -14,13 +14,282 @@
 //! Credential-related data-types and functions
 
 use crate::common::*;
-use crate::utils::FfiEnum;
-use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError};
+use crate::utils::{FfiEnum, LocksLongerThan};
+use crypto_provider_default::CryptoProviderImpl;
+use handle_map::{
+    declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError,
+    HandleMapTryAllocateError,
+};
+use std::sync::Arc;
+
+/// Cryptographic information about a particular V0 discovery credential
+/// necessary to match and decrypt encrypted V0 advertisements.
+#[repr(C)]
+pub struct V0DiscoveryCredential {
+    key_seed: [u8; 32],
+    legacy_metadata_key_hmac: [u8; 32],
+}
+
+impl V0DiscoveryCredential {
+    /// Constructs a new V0 discovery credential with the given 32-byte key-seed
+    /// and the given 32-byte HMAC for the (14-byte) legacy metadata key.
+    pub fn new(key_seed: [u8; 32], legacy_metadata_key_hmac: [u8; 32]) -> Self {
+        Self { key_seed, legacy_metadata_key_hmac }
+    }
+    fn into_internal(self) -> np_adv::credential::v0::V0DiscoveryCredential {
+        np_adv::credential::v0::V0DiscoveryCredential::new(
+            self.key_seed,
+            self.legacy_metadata_key_hmac,
+        )
+    }
+}
+
+/// Cryptographic information about a particular V1 discovery credential
+/// necessary to match and decrypt encrypted V1 advertisement sections.
+#[repr(C)]
+pub struct V1DiscoveryCredential {
+    key_seed: [u8; 32],
+    expected_unsigned_metadata_key_hmac: [u8; 32],
+    expected_signed_metadata_key_hmac: [u8; 32],
+    pub_key: [u8; 32],
+}
+
+impl V1DiscoveryCredential {
+    /// Constructs a new V1 discovery credential with the given 32-byte key-seed,
+    /// unsigned-variant HMAC of the metadata key, the signed-variant HMAC of
+    /// the metadata key, and the given public key for signature verification.
+    pub fn new(
+        key_seed: [u8; 32],
+        expected_unsigned_metadata_key_hmac: [u8; 32],
+        expected_signed_metadata_key_hmac: [u8; 32],
+        pub_key: [u8; 32],
+    ) -> Self {
+        Self {
+            key_seed,
+            expected_unsigned_metadata_key_hmac,
+            expected_signed_metadata_key_hmac,
+            pub_key,
+        }
+    }
+    fn into_internal(self) -> np_adv::credential::v1::V1DiscoveryCredential {
+        np_adv::credential::v1::V1DiscoveryCredential::new(
+            self.key_seed,
+            self.expected_unsigned_metadata_key_hmac,
+            self.expected_signed_metadata_key_hmac,
+            self.pub_key,
+        )
+    }
+}
+
+/// A [`MatchedCredential`] implementation for the purpose of
+/// capturing match-data details across the FFI boundary.
+/// Since we can't know what plaintext match-data the client
+/// wants to keep around, we just expose an ID for them to do
+/// their own look-up.
+///
+/// For the encrypted metadata, we need a slightly richer
+/// representation, since we need to be able to decrypt
+/// the metadata as part of an API call. Internally, we
+/// keep this as an atomic-reference-counted pointer to
+/// a byte array, and never expose this raw pointer across
+/// the FFI boundary.
+#[derive(Debug, Clone)]
+pub struct MatchedCredential {
+    cred_id: u32,
+    encrypted_metadata_bytes: Arc<[u8]>,
+}
+
+impl MatchedCredential {
+    /// Constructs a new matched credential from the given match-id
+    /// (some arbitrary `u32` identifier) and encrypted metadata bytes,
+    /// copied from the given slice.
+    pub fn new(cred_id: u32, encrypted_metadata_bytes: &[u8]) -> Self {
+        let encrypted_metadata_bytes = encrypted_metadata_bytes.to_vec().into_boxed_slice();
+        let encrypted_metadata_bytes = Arc::from(encrypted_metadata_bytes);
+        Self { cred_id, encrypted_metadata_bytes }
+    }
+    /// Gets the pre-specified numerical identifier for this matched-credential.
+    pub(crate) fn id(&self) -> u32 {
+        self.cred_id
+    }
+}
+
+impl PartialEq<MatchedCredential> for MatchedCredential {
+    fn eq(&self, other: &Self) -> bool {
+        self.id() == other.id()
+    }
+}
+
+impl Eq for MatchedCredential {}
+
+impl np_adv::credential::MatchedCredential for MatchedCredential {
+    type EncryptedMetadata = Arc<[u8]>;
+    type EncryptedMetadataFetchError = core::convert::Infallible;
+    fn fetch_encrypted_metadata(&self) -> Result<Arc<[u8]>, core::convert::Infallible> {
+        Ok(self.encrypted_metadata_bytes.clone())
+    }
+}
+
+/// Internals of a credential slab,
+/// an intermediate used in the construction
+/// of a credential-book.
+pub struct CredentialSlabInternals {
+    v0_creds:
+        Vec<np_adv::credential::MatchableCredential<np_adv::credential::v0::V0, MatchedCredential>>,
+    v1_creds:
+        Vec<np_adv::credential::MatchableCredential<np_adv::credential::v1::V1, MatchedCredential>>,
+}
+
+impl CredentialSlabInternals {
+    pub(crate) fn new() -> Self {
+        Self { v0_creds: Vec::new(), v1_creds: Vec::new() }
+    }
+    /// Adds the given V0 discovery credential with the given
+    /// identity match-data onto the end of the V0 credentials
+    /// currently stored in this slab.
+    pub(crate) fn add_v0(
+        &mut self,
+        discovery_credential: V0DiscoveryCredential,
+        match_data: MatchedCredential,
+    ) {
+        let discovery_credential = discovery_credential.into_internal();
+        let matchable_credential =
+            np_adv::credential::MatchableCredential { discovery_credential, match_data };
+        self.v0_creds.push(matchable_credential);
+    }
+    /// Adds the given V1 discovery credential with the given
+    /// identity match-data onto the end of the V1 credentials
+    /// currently stored in this slab.
+    pub(crate) fn add_v1(
+        &mut self,
+        discovery_credential: V1DiscoveryCredential,
+        match_data: MatchedCredential,
+    ) {
+        let discovery_credential = discovery_credential.into_internal();
+        let matchable_credential =
+            np_adv::credential::MatchableCredential { discovery_credential, match_data };
+        self.v1_creds.push(matchable_credential);
+    }
+}
+
+/// Discriminant for `CreateCredentialSlabResult`
+#[repr(u8)]
+pub enum CreateCredentialSlabResultKind {
+    /// There was no space left to create a new credential slab
+    NoSpaceLeft = 0,
+    /// We created a new credential slab behind the given handle.
+    /// The associated payload may be obtained via
+    /// `CreateCredentialSlabResult#into_success()`.
+    Success = 1,
+}
+
+/// Result type for `create_credential_slab`
+#[repr(C)]
+#[allow(missing_docs)]
+pub enum CreateCredentialSlabResult {
+    NoSpaceLeft,
+    Success(CredentialSlab),
+}
+
+impl From<Result<CredentialSlab, HandleMapFullError>> for CreateCredentialSlabResult {
+    fn from(result: Result<CredentialSlab, HandleMapFullError>) -> Self {
+        match result {
+            Ok(slab) => CreateCredentialSlabResult::Success(slab),
+            Err(_) => CreateCredentialSlabResult::NoSpaceLeft,
+        }
+    }
+}
+
+/// Result type for trying to add a credential to a credential-slab.
+#[repr(u8)]
+pub enum AddCredentialToSlabResult {
+    /// We succeeded in adding the credential to the slab.
+    Success = 0,
+    /// The handle to the slab was actually invalid.
+    InvalidHandle = 1,
+}
+
+declare_handle_map! {
+    mod credential_slab {
+        #[dimensions = super::get_credential_slab_handle_map_dimensions()]
+        type CredentialSlab: HandleLike<Object = super::CredentialSlabInternals>;
+    }
+}
+use credential_slab::CredentialSlab;
+
+fn get_credential_slab_handle_map_dimensions() -> HandleMapDimensions {
+    HandleMapDimensions {
+        num_shards: global_num_shards(),
+        max_active_handles: global_max_num_credential_slabs(),
+    }
+}
+
+impl CredentialSlab {
+    /// Adds the given V0 discovery credential with some associated
+    /// match-data to this credential slab.
+    pub fn add_v0(
+        &self,
+        discovery_credential: V0DiscoveryCredential,
+        match_data: MatchedCredential,
+    ) -> AddCredentialToSlabResult {
+        match self.get_mut() {
+            Ok(mut write_guard) => {
+                write_guard.add_v0(discovery_credential, match_data);
+                AddCredentialToSlabResult::Success
+            }
+            Err(_) => AddCredentialToSlabResult::InvalidHandle,
+        }
+    }
+    /// Adds the given V1 discovery credential with some associated
+    /// match-data to this credential slab.
+    pub fn add_v1(
+        &self,
+        discovery_credential: V1DiscoveryCredential,
+        match_data: MatchedCredential,
+    ) -> AddCredentialToSlabResult {
+        match self.get_mut() {
+            Ok(mut write_guard) => {
+                write_guard.add_v1(discovery_credential, match_data);
+                AddCredentialToSlabResult::Success
+            }
+            Err(_) => AddCredentialToSlabResult::InvalidHandle,
+        }
+    }
+}
+
+/// Allocates a new credential-slab, returning a handle to the created object
+pub fn create_credential_slab() -> CreateCredentialSlabResult {
+    CredentialSlab::allocate(CredentialSlabInternals::new).into()
+}
+
+impl FfiEnum for CreateCredentialSlabResult {
+    type Kind = CreateCredentialSlabResultKind;
+    fn kind(&self) -> Self::Kind {
+        match self {
+            CreateCredentialSlabResult::NoSpaceLeft => CreateCredentialSlabResultKind::NoSpaceLeft,
+            CreateCredentialSlabResult::Success(_) => CreateCredentialSlabResultKind::Success,
+        }
+    }
+}
+
+impl CreateCredentialSlabResult {
+    declare_enum_cast! {into_success, Success, CredentialSlab }
+}
 
 /// Internal, Rust-side implementation of a credential-book.
 /// See [`CredentialBook`] for the FFI-side handles.
-// TODO: Give this a real definition!
-pub struct CredentialBookInternals;
+pub struct CredentialBookInternals {
+    pub(crate) book: np_adv::credential::book::PrecalculatedOwnedCredentialBook<MatchedCredential>,
+}
+
+impl CredentialBookInternals {
+    fn create_from_slab(credential_slab: CredentialSlabInternals) -> Self {
+        let book = np_adv::credential::book::CredentialBookBuilder::build_precalculated_owned_book::<
+            CryptoProviderImpl,
+        >(credential_slab.v0_creds, credential_slab.v1_creds);
+        Self { book }
+    }
+}
 
 fn get_credential_book_handle_map_dimensions() -> HandleMapDimensions {
     HandleMapDimensions {
@@ -40,42 +309,60 @@
 /// Discriminant for `CreateCredentialBookResult`
 #[repr(u8)]
 pub enum CreateCredentialBookResultKind {
-    /// There was no space left to create a new credential book
-    NoSpaceLeft = 0,
     /// We created a new credential book behind the given handle.
     /// The associated payload may be obtained via
     /// `CreateCredentialBookResult#into_success()`.
-    Success = 1,
+    Success = 0,
+    /// There was no space left to create a new credential book
+    NoSpaceLeft = 1,
+    /// The slab that we tried to create a credential-book from
+    /// actually was an invalid handle.
+    InvalidSlabHandle = 2,
 }
 
 /// Result type for `create_credential_book`
 #[repr(u8)]
 #[allow(missing_docs)]
 pub enum CreateCredentialBookResult {
-    NoSpaceLeft = 0,
-    Success(CredentialBook) = 1,
+    Success(CredentialBook) = 0,
+    NoSpaceLeft = 1,
+    InvalidSlabHandle = 2,
 }
 
-impl From<Result<CredentialBook, HandleMapFullError>> for CreateCredentialBookResult {
-    fn from(result: Result<CredentialBook, HandleMapFullError>) -> Self {
-        match result {
-            Ok(book) => CreateCredentialBookResult::Success(book),
-            Err(_) => CreateCredentialBookResult::NoSpaceLeft,
-        }
-    }
-}
+impl LocksLongerThan<CredentialSlab> for CredentialBook {}
 
 /// Allocates a new credential-book, returning a handle to the created object
-pub fn create_credential_book() -> CreateCredentialBookResult {
-    CredentialBook::allocate(|| CredentialBookInternals).into()
+pub fn create_credential_book_from_slab(
+    credential_slab: CredentialSlab,
+) -> CreateCredentialBookResult {
+    // The credential-book allocation is on the outside, since we should ensure
+    // that we have a slot available for construction before we try to deallocate
+    // the credential-slab which was passed in.
+    let op_result = CredentialBook::try_allocate(|| {
+        credential_slab.deallocate().map(CredentialBookInternals::create_from_slab)
+    });
+    match op_result {
+        Ok(book) => CreateCredentialBookResult::Success(book),
+        Err(HandleMapTryAllocateError::ValueProviderFailed(_)) => {
+            // Unable to deallocate the referenced credential-slab
+            CreateCredentialBookResult::InvalidSlabHandle
+        }
+        Err(HandleMapTryAllocateError::HandleMapFull) => {
+            // Unable to allocate space for a new credential-book
+            CreateCredentialBookResult::NoSpaceLeft
+        }
+    }
 }
 
 impl FfiEnum for CreateCredentialBookResult {
     type Kind = CreateCredentialBookResultKind;
     fn kind(&self) -> Self::Kind {
         match self {
-            CreateCredentialBookResult::NoSpaceLeft => CreateCredentialBookResultKind::NoSpaceLeft,
             CreateCredentialBookResult::Success(_) => CreateCredentialBookResultKind::Success,
+            CreateCredentialBookResult::NoSpaceLeft => CreateCredentialBookResultKind::NoSpaceLeft,
+            CreateCredentialBookResult::InvalidSlabHandle => {
+                CreateCredentialBookResultKind::InvalidSlabHandle
+            }
         }
     }
 }
@@ -89,5 +376,7 @@
     credential_book.deallocate().map(|_| ()).into()
 }
 
-/// A handle on a particular v0 shared credential stored within a credential book
-pub struct V0SharedCredential;
+/// Deallocates a credential-slab by its handle
+pub fn deallocate_credential_slab(credential_slab: CredentialSlab) -> DeallocateResult {
+    credential_slab.deallocate().map(|_| ()).into()
+}
diff --git a/nearby/presence/np_ffi_core/src/deserialize/mod.rs b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
index 38f59ba..44510f6 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/mod.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
@@ -112,16 +112,13 @@
     credential_book: CredentialBook,
 ) -> Result<DeserializeAdvertisementSuccess, DeserializeAdvertisementError> {
     // Deadlock Safety: Credential-book locks always live longer than deserialized advs.
-    let _credential_book_read_guard = credential_book.get()?;
+    let credential_book_read_guard = credential_book.get()?;
 
-    //TODO: Use an actual credential source
-    let cred_book = np_adv::credential::book::CredentialBookBuilder::<
-        np_adv::credential::EmptyMatchedCredential,
-    >::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &[]);
+    let cred_book = &credential_book_read_guard.book;
 
     let arena = deserialization_arena!();
     let deserialized_advertisement =
-        np_adv::deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv_payload, &cred_book)?;
+        np_adv::deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv_payload, cred_book)?;
     match deserialized_advertisement {
         np_adv::DeserializedAdvertisement::V0(adv_contents) => {
             let adv_handle = DeserializedV0Advertisement::allocate_with_contents(adv_contents)?;
@@ -156,3 +153,13 @@
 ) -> DeserializeAdvertisementResult {
     deserialize_advertisement_from_slice(adv_payload.as_slice(), credential_book)
 }
+
+/// Errors returned from [`crate::deserialize::v0::v0_payload::V0Payload#decrypt_metadata`].
+pub enum DecryptMetadataError {
+    /// The advertisement payload handle was either deallocated
+    /// or corresponds to a public advertisement, and so we
+    /// don't have any metadata to decrypt.
+    EncryptedMetadataNotAvailable,
+    /// Decryption of the raw metadata bytes failed.
+    DecryptionFailed,
+}
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
index d8c008a..a07b9d8 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/v0.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
@@ -15,10 +15,14 @@
 
 use crate::common::*;
 use crate::credentials::credential_book::CredentialBook;
+use crate::credentials::MatchedCredential;
+use crate::deserialize::DecryptMetadataError;
 use crate::utils::{FfiEnum, LocksLongerThan};
+use crypto_provider_default::CryptoProviderImpl;
 use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError};
 use np_adv::legacy::actions::ActionsDataElement;
 use np_adv::legacy::{data_elements as np_adv_de, Ciphertext, PacketFlavorEnum, Plaintext};
+use np_adv::HasIdentityMatch;
 use std::vec::Vec;
 
 /// Discriminant for possible results of V0 advertisement deserialization
@@ -66,8 +70,10 @@
         }
     }
 
-    pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
-        contents: np_adv::V0AdvertisementContents<M>,
+    pub(crate) fn allocate_with_contents(
+        contents: np_adv::V0AdvertisementContents<
+            np_adv::credential::ReferencedMatchedCredential<MatchedCredential>,
+        >,
     ) -> Result<Self, DeserializeAdvertisementError> {
         match contents {
             np_adv::V0AdvertisementContents::Plaintext(plaintext_contents) => {
@@ -76,8 +82,12 @@
                 )?;
                 Ok(Self::Legible(adv))
             }
-            np_adv::V0AdvertisementContents::Decrypted(_) => {
-                unimplemented!();
+            np_adv::V0AdvertisementContents::Decrypted(decrypted_contents) => {
+                let decrypted_contents = decrypted_contents.clone_match_data();
+                let adv = LegibleDeserializedV0Advertisement::allocate_with_decrypted_contents(
+                    decrypted_contents,
+                )?;
+                Ok(Self::Legible(adv))
             }
             np_adv::V0AdvertisementContents::NoMatchingCredentials => {
                 Ok(Self::NoMatchingCredentials)
@@ -93,7 +103,7 @@
 pub struct LegibleDeserializedV0Advertisement {
     num_des: u8,
     payload: V0Payload,
-    identity: DeserializedV0Identity,
+    identity_kind: DeserializedV0IdentityKind,
 }
 
 impl LegibleDeserializedV0Advertisement {
@@ -105,8 +115,38 @@
             .collect::<Result<Vec<_>, _>>()
             .map_err(|_| DeserializeAdvertisementError)?;
         let num_des = data_elements.len() as u8;
-        let payload = V0Payload::allocate_with_data_elements(data_elements)?;
-        Ok(Self { num_des, payload, identity: DeserializedV0Identity::Plaintext })
+        let payload = V0Payload::allocate_with_plaintext_data_elements(data_elements)?;
+        Ok(Self { num_des, payload, identity_kind: DeserializedV0IdentityKind::Plaintext })
+    }
+    pub(crate) fn allocate_with_decrypted_contents(
+        contents: np_adv::WithMatchedCredential<
+            MatchedCredential,
+            np_adv::legacy::deserialize::DecryptedAdvContents,
+        >,
+    ) -> Result<Self, DeserializeAdvertisementError> {
+        let data_elements = contents
+            .contents()
+            .data_elements()
+            .collect::<Result<Vec<_>, _>>()
+            .map_err(|_| DeserializeAdvertisementError)?;
+        let num_des = data_elements.len() as u8;
+
+        let salt = contents.contents().salt();
+        let identity_type = contents.contents().identity_type();
+
+        // Reduce the information contained in the contents to just
+        // the metadata key, since we're done copying over the DEs
+        // and other data into an FFI-friendly form.
+        let match_data = contents.map(|x| x.metadata_key());
+
+        let payload = V0Payload::allocate_with_decrypted_contents(
+            identity_type,
+            salt,
+            match_data,
+            data_elements,
+        )?;
+
+        Ok(Self { num_des, payload, identity_kind: DeserializedV0IdentityKind::Decrypted })
     }
     /// Gets the number of data-elements in this adv's payload
     /// Suitable as an iteration bound for `Self.into_payload().get_de(...)`.
@@ -117,9 +157,10 @@
     pub fn payload(&self) -> V0Payload {
         self.payload
     }
-    /// Destructures this legible advertisement into just the identity information
-    pub fn identity(&self) -> DeserializedV0Identity {
-        self.identity
+    /// Destructures this legible advertisement into just the discriminant
+    /// for the kind of identity (plaintext/encrypted) used for its contents.
+    pub fn identity_kind(&self) -> DeserializedV0IdentityKind {
+        self.identity_kind
     }
     /// Deallocates the underlying handle of the payload
     pub fn deallocate(self) -> DeallocateResult {
@@ -127,7 +168,8 @@
     }
 }
 
-/// Discriminant for `DeserializedV0Identity`.
+/// Discriminant for deserialized information about the V0
+/// identity utilized by a deserialized V0 advertisement.
 #[derive(Clone, Copy)]
 #[repr(u8)]
 pub enum DeserializedV0IdentityKind {
@@ -137,30 +179,136 @@
     Decrypted = 1,
 }
 
-/// Represents deserialized information about the V0 identity utilized
-/// by a deserialized V0 advertisement
+/// Information about the identity which matched a
+/// decrypted V0 advertisement.
+#[derive(Clone, Copy)]
 #[repr(C)]
-#[allow(missing_docs)]
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub enum DeserializedV0Identity {
-    Plaintext,
-    // TODO: This gets a payload once we support creds
-    Decrypted,
+pub struct DeserializedV0IdentityDetails {
+    /// The identity type (private/provisioned/trusted)
+    identity_type: EncryptedIdentityType,
+    /// The ID of the credential which
+    /// matched the deserialized adv
+    cred_id: u32,
+    /// The 14-byte legacy metadata key
+    metadata_key: [u8; 14],
+    /// The 2-byte advertisement salt
+    salt: [u8; 2],
 }
 
-impl FfiEnum for DeserializedV0Identity {
-    type Kind = DeserializedV0IdentityKind;
+impl DeserializedV0IdentityDetails {
+    pub(crate) fn new(
+        cred_id: u32,
+        identity_type: np_adv::de_type::EncryptedIdentityDataElementType,
+        salt: ldt_np_adv::LegacySalt,
+        metadata_key: np_adv::legacy::ShortMetadataKey,
+    ) -> Self {
+        let metadata_key = metadata_key.0;
+        let salt = *salt.bytes();
+        let identity_type = identity_type.into();
+        Self { identity_type, cred_id, salt, metadata_key }
+    }
+    /// Returns the ID of the credential which
+    /// matched the deserialized adv
+    pub fn cred_id(&self) -> u32 {
+        self.cred_id
+    }
+    /// Returns the identity type (private/provisioned/trusted)
+    pub fn identity_type(&self) -> EncryptedIdentityType {
+        self.identity_type
+    }
+    /// Returns the 14-byte legacy metadata key
+    pub fn metadata_key(&self) -> [u8; 14] {
+        self.metadata_key
+    }
+    /// Returns the 2-byte advertisement salt
+    pub fn salt(&self) -> [u8; 2] {
+        self.salt
+    }
+}
+
+/// Discriminant for `GetV0IdentityDetailsResult`
+#[derive(Clone, Copy)]
+#[repr(u8)]
+pub enum GetV0IdentityDetailsResultKind {
+    /// The attempt to get the identity details
+    /// for the advertisement failed, possibly
+    /// due to the advertisement being a public
+    /// advertisement, or the underlying
+    /// advertisement has already been deallocated.
+    Error = 0,
+    /// The attempt to get the identity details succeeded.
+    /// The wrapped identity details may be obtained via
+    /// `GetV0IdentityDetailsResult#into_success`.
+    Success = 1,
+}
+
+/// The result of attempting to get the identity details
+/// for a V0 advertisement via
+/// `DeserializedV0Advertisement#get_identity_details`.
+#[repr(C)]
+#[allow(missing_docs)]
+pub enum GetV0IdentityDetailsResult {
+    Error,
+    Success(DeserializedV0IdentityDetails),
+}
+
+impl FfiEnum for GetV0IdentityDetailsResult {
+    type Kind = GetV0IdentityDetailsResultKind;
     fn kind(&self) -> Self::Kind {
         match self {
-            DeserializedV0Identity::Plaintext => DeserializedV0IdentityKind::Plaintext,
-            DeserializedV0Identity::Decrypted => DeserializedV0IdentityKind::Decrypted,
+            GetV0IdentityDetailsResult::Error => GetV0IdentityDetailsResultKind::Error,
+            GetV0IdentityDetailsResult::Success(_) => GetV0IdentityDetailsResultKind::Success,
         }
     }
 }
 
+impl GetV0IdentityDetailsResult {
+    declare_enum_cast! {into_success, Success, DeserializedV0IdentityDetails}
+}
+
+/// Internal implementation of a deserialized V0 identity.
+pub(crate) struct DeserializedV0IdentityInternals {
+    /// The details about the identity, suitable
+    /// for direct communication over FFI
+    details: DeserializedV0IdentityDetails,
+    /// The metadata key, together with the matched
+    /// credential and enough information to decrypt
+    /// the credential metadata, if desired.
+    match_data: np_adv::WithMatchedCredential<MatchedCredential, np_adv::legacy::ShortMetadataKey>,
+}
+
+impl DeserializedV0IdentityInternals {
+    pub(crate) fn new(
+        identity_type: np_adv::de_type::EncryptedIdentityDataElementType,
+        salt: ldt_np_adv::LegacySalt,
+        match_data: np_adv::WithMatchedCredential<
+            MatchedCredential,
+            np_adv::legacy::ShortMetadataKey,
+        >,
+    ) -> Self {
+        let cred_id = match_data.matched_credential().id();
+        let metadata_key = match_data.contents();
+        let details =
+            DeserializedV0IdentityDetails::new(cred_id, identity_type, salt, *metadata_key);
+        Self { details, match_data }
+    }
+    /// Gets the directly-transmissible details about
+    /// this deserialized V0 identity. Does not include
+    /// decrypted metadata bytes.
+    pub(crate) fn details(&self) -> DeserializedV0IdentityDetails {
+        self.details
+    }
+    /// Attempts to decrypt the metadata associated
+    /// with this identity.
+    pub(crate) fn decrypt_metadata(&self) -> Option<Vec<u8>> {
+        self.match_data.decrypt_metadata::<CryptoProviderImpl>().ok()
+    }
+}
+
 /// The internal data-structure used for storing
 /// the payload of a deserialized V0 advertisement.
 pub struct V0PayloadInternals {
+    identity: Option<DeserializedV0IdentityInternals>,
     des: Vec<V0DataElement>,
 }
 
@@ -173,6 +321,24 @@
             None => GetV0DEResult::Error,
         }
     }
+    /// Gets the identity details for this V0 payload,
+    /// if this payload was associated with an identity.
+    fn get_identity_details(&self) -> GetV0IdentityDetailsResult {
+        match &self.identity {
+            Some(x) => GetV0IdentityDetailsResult::Success(x.details()),
+            None => GetV0IdentityDetailsResult::Error,
+        }
+    }
+    /// Attempts to decrypt the metadata for the matched
+    /// credential for this V0 payload (if any)
+    fn decrypt_metadata(&self) -> Result<Vec<u8>, DecryptMetadataError> {
+        match &self.identity {
+            None => Err(DecryptMetadataError::EncryptedMetadataNotAvailable),
+            Some(identity) => {
+                identity.decrypt_metadata().ok_or(DecryptMetadataError::DecryptionFailed)
+            }
+        }
+    }
 }
 
 fn get_v0_payload_handle_map_dimensions() -> HandleMapDimensions {
@@ -195,12 +361,33 @@
 impl LocksLongerThan<V0Payload> for CredentialBook {}
 
 impl V0Payload {
-    pub(crate) fn allocate_with_data_elements<F: np_adv::legacy::PacketFlavor>(
-        data_elements: Vec<np_adv::legacy::deserialize::PlainDataElement<F>>,
+    pub(crate) fn allocate_with_plaintext_data_elements(
+        data_elements: Vec<
+            np_adv::legacy::deserialize::PlainDataElement<np_adv::legacy::Plaintext>,
+        >,
     ) -> Result<Self, HandleMapFullError> {
         Self::allocate(move || {
             let des = data_elements.into_iter().map(V0DataElement::from).collect();
-            V0PayloadInternals { des }
+            let identity = None;
+            V0PayloadInternals { des, identity }
+        })
+    }
+    pub(crate) fn allocate_with_decrypted_contents(
+        identity_type: np_adv::de_type::EncryptedIdentityDataElementType,
+        salt: ldt_np_adv::LegacySalt,
+        match_data: np_adv::WithMatchedCredential<
+            MatchedCredential,
+            np_adv::legacy::ShortMetadataKey,
+        >,
+        data_elements: Vec<
+            np_adv::legacy::deserialize::PlainDataElement<np_adv::legacy::Ciphertext>,
+        >,
+    ) -> Result<Self, HandleMapFullError> {
+        Self::allocate(move || {
+            let des = data_elements.into_iter().map(V0DataElement::from).collect();
+            let identity =
+                Some(DeserializedV0IdentityInternals::new(identity_type, salt, match_data));
+            V0PayloadInternals { des, identity }
         })
     }
     /// Gets the data-element with the given index in this v0 adv payload
@@ -211,6 +398,32 @@
         }
     }
 
+    /// Gets the identity details for this V0 payload,
+    /// if this payload was associted with an identity
+    /// (i.e: non-public advertisements).
+    pub fn get_identity_details(&self) -> GetV0IdentityDetailsResult {
+        match self.get() {
+            Ok(read_guard) => read_guard.get_identity_details(),
+            Err(_) => GetV0IdentityDetailsResult::Error,
+        }
+    }
+
+    /// Attempts to decrypt the metadata for the matched
+    /// credential for this V0 payload (if any)
+    ///
+    /// Note that while this method is publicly exposed
+    /// from `np_ffi_core`, since it involves the (FFI-layer-unexpressed)
+    /// type `Vec<u8>`, a direct wrapper will not suffice,
+    /// and instead a language-specific binding will need to
+    /// be generated for this method which respects the
+    /// expected memory-management semantics of the target language.
+    pub fn decrypt_metadata(&self) -> Result<Vec<u8>, DecryptMetadataError> {
+        match self.get() {
+            Ok(read_guard) => read_guard.decrypt_metadata(),
+            Err(_) => Err(DecryptMetadataError::EncryptedMetadataNotAvailable),
+        }
+    }
+
     /// Deallocates any underlying data held by a V0Payload
     pub fn deallocate_payload(&self) -> DeallocateResult {
         self.deallocate().map(|_| ()).into()
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v1.rs b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
index 819bb8c..4684f55 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/v1.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
@@ -16,11 +16,15 @@
 use super::DeserializeAdvertisementError;
 use crate::common::*;
 use crate::credentials::credential_book::CredentialBook;
+use crate::credentials::MatchedCredential;
+use crate::deserialize::DecryptMetadataError;
 use crate::utils::*;
 use array_view::ArrayView;
+use crypto_provider_default::CryptoProviderImpl;
 use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions};
 use legible_v1_sections::LegibleV1Sections;
 use np_adv::extended::deserialize::DataElementParseError;
+use np_adv::HasIdentityMatch;
 use std::vec::Vec;
 
 /// Representation of a deserialized V1 advertisement
@@ -59,8 +63,10 @@
         self.legible_sections.deallocate().map(|_| ()).into()
     }
 
-    pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
-        contents: np_adv::V1AdvertisementContents<M>,
+    pub(crate) fn allocate_with_contents(
+        contents: np_adv::V1AdvertisementContents<
+            np_adv::credential::ReferencedMatchedCredential<MatchedCredential>,
+        >,
     ) -> Result<Self, DeserializeAdvertisementError> {
         // 16-section limit enforced by np_adv
         let num_undecryptable_sections = contents.invalid_sections_count() as u8;
@@ -109,13 +115,25 @@
     }
 }
 
-impl<'adv, M: np_adv::credential::MatchedCredential>
-    TryFrom<Vec<np_adv::V1DeserializedSection<'adv, M>>> for LegibleV1SectionsInternals
+impl<'adv>
+    TryFrom<
+        Vec<
+            np_adv::V1DeserializedSection<
+                'adv,
+                np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>,
+            >,
+        >,
+    > for LegibleV1SectionsInternals
 {
     type Error = DataElementParseError;
 
     fn try_from(
-        contents: Vec<np_adv::V1DeserializedSection<'adv, M>>,
+        contents: Vec<
+            np_adv::V1DeserializedSection<
+                'adv,
+                np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>,
+            >,
+        >,
     ) -> Result<Self, Self::Error> {
         let sections = contents
             .into_iter()
@@ -142,8 +160,12 @@
 impl LocksLongerThan<LegibleV1Sections> for CredentialBook {}
 
 impl LegibleV1Sections {
-    pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
-        contents: Vec<np_adv::V1DeserializedSection<M>>,
+    pub(crate) fn allocate_with_contents(
+        contents: Vec<
+            np_adv::V1DeserializedSection<
+                np_adv::credential::ReferencedMatchedCredential<MatchedCredential>,
+            >,
+        >,
     ) -> Result<Self, DeserializeAdvertisementError> {
         let section = LegibleV1SectionsInternals::try_from(contents)
             .map_err(|_| DeserializeAdvertisementError)?;
@@ -190,10 +212,45 @@
     declare_enum_cast! {into_success, Success, DeserializedV1Section}
 }
 
+/// Discriminant for `GetV1DE16ByteSaltResult`.
+#[derive(Clone, Copy)]
+#[repr(u8)]
+pub enum GetV1DE16ByteSaltResultKind {
+    /// The attempt to get the derived salt failed, possibly
+    /// because the passed DE offset was invalid (==255),
+    /// or because there was no salt included for the
+    /// referenced advertisement section (i.e: it was
+    /// a public advertisement section, or it was deallocated.)
+    Error = 0,
+    /// A 16-byte salt for the given DE offset was successfully
+    /// derived.
+    Success = 1,
+}
+
+/// The result of attempting to get a derived 16-byte salt
+/// for a given DE within a section.
+#[derive(Copy, Clone)]
+#[repr(C)]
+#[allow(missing_docs)]
+pub enum GetV1DE16ByteSaltResult {
+    Error,
+    Success([u8; 16]),
+}
+
+impl FfiEnum for GetV1DE16ByteSaltResult {
+    type Kind = GetV1DE16ByteSaltResultKind;
+    fn kind(&self) -> Self::Kind {
+        match self {
+            GetV1DE16ByteSaltResult::Error => GetV1DE16ByteSaltResultKind::Error,
+            GetV1DE16ByteSaltResult::Success(_) => GetV1DE16ByteSaltResultKind::Success,
+        }
+    }
+}
+
 /// The internal FFI-friendly representation of a deserialized v1 section
 pub struct DeserializedV1SectionInternals {
     des: Vec<V1DataElement>,
-    identity: DeserializedV1Identity,
+    identity: Option<DeserializedV1IdentityInternals>,
 }
 
 impl DeserializedV1SectionInternals {
@@ -203,7 +260,11 @@
     }
     /// Gets the enum tag of the identity used for this section.
     fn identity_kind(&self) -> DeserializedV1IdentityKind {
-        self.identity.kind()
+        if self.identity.is_some() {
+            DeserializedV1IdentityKind::Decrypted
+        } else {
+            DeserializedV1IdentityKind::Plaintext
+        }
     }
     /// Attempts to get the DE with the given index in this section.
     fn get_de(&self, index: u8) -> GetV1DEResult {
@@ -212,14 +273,53 @@
             None => GetV1DEResult::Error,
         }
     }
+    /// Attempts to get the directly-transmissible details about
+    /// the deserialized V1 identity for this section. Does
+    /// not include decrypted metadata bytes nor the section salt.
+    pub(crate) fn get_identity_details(&self) -> GetV1IdentityDetailsResult {
+        match &self.identity {
+            Some(identity) => GetV1IdentityDetailsResult::Success(identity.details()),
+            None => GetV1IdentityDetailsResult::Error,
+        }
+    }
+    /// Attempts to decrypt the metadata for the matched
+    /// credential for this V1 section (if any).
+    pub(crate) fn decrypt_metadata(&self) -> Result<Vec<u8>, DecryptMetadataError> {
+        match &self.identity {
+            None => Err(DecryptMetadataError::EncryptedMetadataNotAvailable),
+            Some(identity) => {
+                identity.decrypt_metadata().ok_or(DecryptMetadataError::DecryptionFailed)
+            }
+        }
+    }
+    /// Attempts to derive a 16-byte DE salt for a DE in this section
+    /// with the given DE offset. This operation may fail if the
+    /// passed offset is 255 (causes overflow) or if the section
+    /// is leveraging a public identity, and hence, doesn't have
+    /// an associated salt.
+    pub(crate) fn derive_16_byte_salt_for_offset(&self, de_offset: u8) -> GetV1DE16ByteSaltResult {
+        self.identity
+            .as_ref()
+            .and_then(|x| x.derive_16_byte_salt_for_offset(de_offset))
+            .map_or(GetV1DE16ByteSaltResult::Error, GetV1DE16ByteSaltResult::Success)
+    }
 }
 
-impl<'adv, M: np_adv::credential::MatchedCredential> TryFrom<np_adv::V1DeserializedSection<'adv, M>>
-    for DeserializedV1SectionInternals
+impl<'adv>
+    TryFrom<
+        np_adv::V1DeserializedSection<
+            'adv,
+            np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>,
+        >,
+    > for DeserializedV1SectionInternals
 {
     type Error = DataElementParseError;
 
-    fn try_from(section: np_adv::V1DeserializedSection<M>) -> Result<Self, Self::Error> {
+    fn try_from(
+        section: np_adv::V1DeserializedSection<
+            np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>,
+        >,
+    ) -> Result<Self, Self::Error> {
         use np_adv::extended::deserialize::Section;
         use np_adv::V1DeserializedSection;
         match section {
@@ -228,11 +328,30 @@
                     .iter_data_elements()
                     .map(|r| r.map(|de| V1DataElement::from(&de)))
                     .collect::<Result<Vec<_>, _>>()?;
-                let identity = DeserializedV1Identity::Plaintext;
+                let identity = None;
                 Ok(Self { des, identity })
             }
-            V1DeserializedSection::Decrypted(_) => {
-                unimplemented!();
+            V1DeserializedSection::Decrypted(with_matched) => {
+                let section = with_matched.contents();
+                let des = section
+                    .iter_data_elements()
+                    .map(|r| r.map(|de| V1DataElement::from(&de)))
+                    .collect::<Result<Vec<_>, _>>()?;
+
+                let identity_type = section.identity_type();
+                let verification_mode = section.verification_mode();
+                let salt = section.salt();
+
+                let match_data = with_matched.clone_match_data();
+                let match_data = match_data.map(|x| x.metadata_key());
+
+                let identity = Some(DeserializedV1IdentityInternals::new(
+                    identity_type,
+                    verification_mode,
+                    salt,
+                    match_data,
+                ));
+                Ok(Self { des, identity })
             }
         }
     }
@@ -248,27 +367,168 @@
     Decrypted = 1,
 }
 
-/// Deserialized information about the identity
-/// employed in a V1 adveritsement section.
-#[derive(Clone, Copy)]
-#[repr(C)]
-#[allow(missing_docs)]
-pub enum DeserializedV1Identity {
-    Plaintext,
-    // TODO: This gets a payload once we support creds
-    Decrypted,
+/// Internals for the representation of a decrypted
+/// V1 section identity.
+pub(crate) struct DeserializedV1IdentityInternals {
+    /// The details about the identity, suitable
+    /// for direct communication over FFI
+    details: DeserializedV1IdentityDetails,
+    /// The metadata key, together with the matched
+    /// credential and enough information to decrypt
+    /// the credential metadata, if desired.
+    match_data: np_adv::WithMatchedCredential<MatchedCredential, np_adv::MetadataKey>,
+    /// The 16-byte section salt
+    salt: np_adv::extended::deserialize::RawV1Salt,
 }
 
-impl FfiEnum for DeserializedV1Identity {
-    type Kind = DeserializedV1IdentityKind;
+impl DeserializedV1IdentityInternals {
+    pub(crate) fn new(
+        identity_type: np_adv::de_type::EncryptedIdentityDataElementType,
+        verification_mode: np_adv::extended::deserialize::VerificationMode,
+        salt: np_adv::extended::deserialize::RawV1Salt,
+        match_data: np_adv::WithMatchedCredential<MatchedCredential, np_adv::MetadataKey>,
+    ) -> Self {
+        let cred_id = match_data.matched_credential().id();
+        let metadata_key = match_data.contents();
+        let details = DeserializedV1IdentityDetails::new(
+            cred_id,
+            identity_type,
+            verification_mode,
+            *metadata_key,
+        );
+        Self { details, match_data, salt }
+    }
+    /// Gets the directly-transmissible details about
+    /// this deserialized V1 identity. Does not include
+    /// decrypted metadata bytes nor the section salt.
+    pub(crate) fn details(&self) -> DeserializedV1IdentityDetails {
+        self.details
+    }
+    /// Attempts to decrypt the metadata associated
+    /// with this identity.
+    pub(crate) fn decrypt_metadata(&self) -> Option<Vec<u8>> {
+        self.match_data.decrypt_metadata::<CryptoProviderImpl>().ok()
+    }
+    /// For a given data-element offset, derives a 16-byte DE salt
+    /// for a DE in that position within this section.
+    pub(crate) fn derive_16_byte_salt_for_offset(&self, de_offset: u8) -> Option<[u8; 16]> {
+        let section_salt = np_hkdf::v1_salt::V1Salt::<CryptoProviderImpl>::from(self.salt);
+        let de_offset = np_hkdf::v1_salt::DataElementOffset::from(de_offset);
+        section_salt.derive::<16>(Some(de_offset))
+    }
+}
+
+/// Information about the verification scheme used
+/// for verifying the integrity of the contents
+/// of a decrypted section.
+#[derive(Clone, Copy)]
+#[repr(u8)]
+pub enum V1VerificationMode {
+    /// Message integrity code verification.
+    Mic = 0,
+    /// Signature verification.
+    Signature = 1,
+}
+
+impl From<np_adv::extended::deserialize::VerificationMode> for V1VerificationMode {
+    fn from(verification_mode: np_adv::extended::deserialize::VerificationMode) -> Self {
+        use np_adv::extended::deserialize::VerificationMode;
+        match verification_mode {
+            VerificationMode::Mic => Self::Mic,
+            VerificationMode::Signature => Self::Signature,
+        }
+    }
+}
+
+/// Discriminant for `GetV1IdentityDetailsResult`
+#[derive(Clone, Copy)]
+#[repr(u8)]
+pub enum GetV1IdentityDetailsResultKind {
+    /// The attempt to get the identity details
+    /// for the section failed, possibly
+    /// due to the section being a public
+    /// section, or the underlying
+    /// advertisement has already been deallocated.
+    Error = 0,
+    /// The attempt to get the identity details succeeded.
+    /// The wrapped identity details may be obtained via
+    /// `GetV1IdentityDetailsResult#into_success`.
+    Success = 1,
+}
+
+/// The result of attempting to get the identity details
+/// for a V1 advertisement section via
+/// `DeserializedV1Advertisement#get_identity_details`.
+#[repr(C)]
+#[allow(missing_docs)]
+pub enum GetV1IdentityDetailsResult {
+    Error,
+    Success(DeserializedV1IdentityDetails),
+}
+
+impl FfiEnum for GetV1IdentityDetailsResult {
+    type Kind = GetV1IdentityDetailsResultKind;
     fn kind(&self) -> Self::Kind {
         match self {
-            DeserializedV1Identity::Plaintext => DeserializedV1IdentityKind::Plaintext,
-            DeserializedV1Identity::Decrypted => DeserializedV1IdentityKind::Decrypted,
+            GetV1IdentityDetailsResult::Error => GetV1IdentityDetailsResultKind::Error,
+            GetV1IdentityDetailsResult::Success(_) => GetV1IdentityDetailsResultKind::Success,
         }
     }
 }
 
+impl GetV1IdentityDetailsResult {
+    declare_enum_cast! {into_success, Success, DeserializedV1IdentityDetails}
+}
+
+/// Information about the identity which matched
+/// a decrypted V1 section.
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct DeserializedV1IdentityDetails {
+    /// The identity type (private/provisioned/trusted)
+    identity_type: EncryptedIdentityType,
+    /// The verification mode (MIC/Signature) which
+    /// was used to verify the decrypted adv contents.
+    verification_mode: V1VerificationMode,
+    /// The ID of the credential which
+    /// matched the deserialized section.
+    cred_id: u32,
+    /// The 16-byte metadata key.
+    metadata_key: [u8; 16],
+}
+
+impl DeserializedV1IdentityDetails {
+    pub(crate) fn new(
+        cred_id: u32,
+        identity_type: np_adv::de_type::EncryptedIdentityDataElementType,
+        verification_mode: np_adv::extended::deserialize::VerificationMode,
+        metadata_key: np_adv::MetadataKey,
+    ) -> Self {
+        let metadata_key = metadata_key.0;
+        let identity_type = identity_type.into();
+        let verification_mode = verification_mode.into();
+        Self { cred_id, identity_type, verification_mode, metadata_key }
+    }
+    /// Returns the ID of the credential which
+    /// matched the deserialized section.
+    pub fn cred_id(&self) -> u32 {
+        self.cred_id
+    }
+    /// Returns the identity type (private/provisioned/trusted)
+    pub fn identity_type(&self) -> EncryptedIdentityType {
+        self.identity_type
+    }
+    /// Returns the verification mode (MIC/Signature)
+    /// employed for the decrypted section.
+    pub fn verification_mode(&self) -> V1VerificationMode {
+        self.verification_mode
+    }
+    /// Returns the 16-byte section metadata key.
+    pub fn metadata_key(&self) -> [u8; 16] {
+        self.metadata_key
+    }
+}
+
 /// Handle to a deserialized V1 section
 #[repr(C)]
 pub struct DeserializedV1Section {
@@ -290,20 +550,60 @@
         self.identity_tag
     }
 
-    /// Gets the DE with the given index in this section.
-    pub fn get_de(&self, de_index: u8) -> GetV1DEResult {
+    fn apply_to_section_internals<R>(
+        &self,
+        func: impl FnOnce(&DeserializedV1SectionInternals) -> R,
+        lookup_failure_result: R,
+    ) -> R {
         // TODO: Once the `FromResidual` trait is stabilized, this can be simplified.
         match self.legible_sections_handle.get() {
             Ok(legible_sections_read_guard) => {
                 match legible_sections_read_guard.get_section_internals(self.legible_section_index)
                 {
-                    Some(section_ref) => section_ref.get_de(de_index),
-                    None => GetV1DEResult::Error,
+                    Some(section_ref) => func(section_ref),
+                    None => lookup_failure_result,
                 }
             }
-            Err(_) => GetV1DEResult::Error,
+            Err(_) => lookup_failure_result,
         }
     }
+    /// Gets the DE with the given index in this section.
+    pub fn get_de(&self, de_index: u8) -> GetV1DEResult {
+        self.apply_to_section_internals(
+            move |section_ref| section_ref.get_de(de_index),
+            GetV1DEResult::Error,
+        )
+    }
+    /// Attempts to get the details of the identity employed
+    /// for the section referenced by this handle. May fail
+    /// if the handle is invalid, or if the advertisement
+    /// section leverages a public identity.
+    pub fn get_identity_details(&self) -> GetV1IdentityDetailsResult {
+        self.apply_to_section_internals(
+            DeserializedV1SectionInternals::get_identity_details,
+            GetV1IdentityDetailsResult::Error,
+        )
+    }
+    /// Attempts to decrypt the metadata for the matched
+    /// credential for the V1 section referenced by
+    /// this handle (if any).
+    pub fn decrypt_metadata(&self) -> Result<Vec<u8>, DecryptMetadataError> {
+        self.apply_to_section_internals(
+            DeserializedV1SectionInternals::decrypt_metadata,
+            Err(DecryptMetadataError::EncryptedMetadataNotAvailable),
+        )
+    }
+    /// Attempts to derive a 16-byte DE salt for a DE in this section
+    /// with the given DE offset. This operation may fail if the
+    /// passed offset is 255 (causes overflow) or if the section
+    /// is leveraging a public identity, and hence, doesn't have
+    /// an associated salt.
+    pub fn derive_16_byte_salt_for_offset(&self, de_offset: u8) -> GetV1DE16ByteSaltResult {
+        self.apply_to_section_internals(
+            move |section_ref| section_ref.derive_16_byte_salt_for_offset(de_offset),
+            GetV1DE16ByteSaltResult::Error,
+        )
+    }
 }
 
 /// Discriminant for the `GetV1DEResult` enum.
@@ -365,13 +665,14 @@
 
 impl<'a> From<&'a np_adv::extended::deserialize::DataElement<'a>> for V1DataElement {
     fn from(de: &'a np_adv::extended::deserialize::DataElement<'a>) -> Self {
+        let offset = de.offset().as_u8();
         let de_type = V1DEType::from(de.de_type());
         let contents_as_slice = de.contents();
         //Guaranteed not to panic due DE size limit.
         #[allow(clippy::unwrap_used)]
         let array_view: ArrayView<u8, 127> = ArrayView::try_from_slice(contents_as_slice).unwrap();
         let payload = ByteBuffer::from_array_view(array_view);
-        Self::Generic(GenericV1DataElement { de_type, payload })
+        Self::Generic(GenericV1DataElement { de_type, offset, payload })
     }
 }
 
@@ -381,6 +682,8 @@
 #[derive(Clone)]
 #[repr(C)]
 pub struct GenericV1DataElement {
+    /// The offset of this generic data-element.
+    pub offset: u8,
     /// The DE type code of this generic data-element.
     pub de_type: V1DEType,
     /// The raw data-element byte payload, up to
@@ -389,6 +692,10 @@
 }
 
 impl GenericV1DataElement {
+    /// Gets the offset for this generic V1 data element.
+    pub fn offset(&self) -> u8 {
+        self.offset
+    }
     /// Gets the DE-type of this generic V1 data element.
     pub fn de_type(&self) -> V1DEType {
         self.de_type
diff --git a/nearby/presence/np_ffi_core/src/lib.rs b/nearby/presence/np_ffi_core/src/lib.rs
index bba33e2..0c25168 100644
--- a/nearby/presence/np_ffi_core/src/lib.rs
+++ b/nearby/presence/np_ffi_core/src/lib.rs
@@ -11,15 +11,8 @@
 // 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.
+
 //! Core functionality common to all NP Rust FFI layers
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 #[macro_use]
 extern crate lazy_static;
diff --git a/nearby/presence/np_hkdf/Cargo.toml b/nearby/presence/np_hkdf/Cargo.toml
index 885913f..45e40da 100644
--- a/nearby/presence/np_hkdf/Cargo.toml
+++ b/nearby/presence/np_hkdf/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
diff --git a/nearby/presence/np_hkdf/benches/np_hkdf.rs b/nearby/presence/np_hkdf/benches/np_hkdf.rs
index e3ce506..bec3b0e 100644
--- a/nearby/presence/np_hkdf/benches/np_hkdf.rs
+++ b/nearby/presence/np_hkdf/benches/np_hkdf.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(missing_docs, unused_results)]
+
 use criterion::{black_box, criterion_group, criterion_main, Criterion};
 use crypto_provider::{CryptoProvider, CryptoRng};
 use crypto_provider_default::CryptoProviderImpl;
diff --git a/nearby/presence/np_hkdf/src/lib.rs b/nearby/presence/np_hkdf/src/lib.rs
index f4f98eb..8201f38 100644
--- a/nearby/presence/np_hkdf/src/lib.rs
+++ b/nearby/presence/np_hkdf/src/lib.rs
@@ -16,17 +16,9 @@
 //!
 //! All HKDF calls should happen in this module and expose the correct result type for
 //! each derived key use case.
-#![no_std]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
-extern crate core;
+#![no_std]
+
 #[cfg(feature = "std")]
 extern crate std;
 
diff --git a/nearby/presence/np_hkdf/src/v1_salt.rs b/nearby/presence/np_hkdf/src/v1_salt.rs
index ae327db..94e6855 100644
--- a/nearby/presence/np_hkdf/src/v1_salt.rs
+++ b/nearby/presence/np_hkdf/src/v1_salt.rs
@@ -45,7 +45,7 @@
                 &[
                     b"V1 derived salt",
                     &de.and_then(|d| d.offset.checked_add(1))
-                        .and_then(|o| o.try_into().ok())
+                        .map(|o| o.into())
                         .unwrap_or(0_u32)
                         .to_be_bytes(),
                 ],
diff --git a/nearby/presence/np_hkdf/tests/test_vectors.rs b/nearby/presence/np_hkdf/tests/test_vectors.rs
index 380c107..175e73e 100644
--- a/nearby/presence/np_hkdf/tests/test_vectors.rs
+++ b/nearby/presence/np_hkdf/tests/test_vectors.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::indexing_slicing, clippy::unwrap_used)]
+
 use anyhow::anyhow;
 use crypto_provider::aes::AesKey;
 use crypto_provider_default::CryptoProviderImpl;
@@ -28,7 +30,7 @@
         test_helper::get_data_file("presence/np_hkdf/resources/test/hkdf-test-vectors.json");
     let mut file = fs::File::open(full_path)?;
     let mut data = String::new();
-    file.read_to_string(&mut data)?;
+    let _ = file.read_to_string(&mut data)?;
     let test_cases = match serde_json::de::from_str(&data)? {
         serde_json::Value::Array(a) => a,
         _ => return Err(anyhow!("bad json")),
diff --git a/nearby/presence/rand_ext/Cargo.toml b/nearby/presence/rand_ext/Cargo.toml
index bbdb5af..79a8ea8 100644
--- a/nearby/presence/rand_ext/Cargo.toml
+++ b/nearby/presence/rand_ext/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 crypto_provider.workspace = true
 log.workspace = true
diff --git a/nearby/presence/rand_ext/src/lib.rs b/nearby/presence/rand_ext/src/lib.rs
index 9819c6b..d767143 100644
--- a/nearby/presence/rand_ext/src/lib.rs
+++ b/nearby/presence/rand_ext/src/lib.rs
@@ -14,8 +14,6 @@
 
 //! Helper functions around `rand`'s offerings for convenient test usage.
 #![no_std]
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
 
 extern crate alloc;
 
diff --git a/nearby/presence/sink/Cargo.toml b/nearby/presence/sink/Cargo.toml
index a0322b6..a479205 100644
--- a/nearby/presence/sink/Cargo.toml
+++ b/nearby/presence/sink/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 tinyvec.workspace = true
 
diff --git a/nearby/presence/sink/src/lib.rs b/nearby/presence/sink/src/lib.rs
index 75c7c1d..fe6e5b6 100644
--- a/nearby/presence/sink/src/lib.rs
+++ b/nearby/presence/sink/src/lib.rs
@@ -14,15 +14,8 @@
 
 //! A no_std-friendly data-writing "sink" trait which allows for convenient expression
 //! of "write me into a limited-size buffer"-type methods on traits.
+
 #![cfg_attr(not(feature = "std"), no_std)]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::indexing_slicing,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used
-)]
 
 /// An append-only, limited-size collection.
 pub trait Sink<T> {
diff --git a/nearby/presence/test_helper/Cargo.toml b/nearby/presence/test_helper/Cargo.toml
index 488ea74..c52d6bf 100644
--- a/nearby/presence/test_helper/Cargo.toml
+++ b/nearby/presence/test_helper/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 hex.workspace = true
 serde_json.workspace = true
diff --git a/nearby/presence/test_helper/src/lib.rs b/nearby/presence/test_helper/src/lib.rs
index 7c0694f..8b42623 100644
--- a/nearby/presence/test_helper/src/lib.rs
+++ b/nearby/presence/test_helper/src/lib.rs
@@ -11,11 +11,11 @@
 // 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.
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
 
 //! Helper crate for common functions used in testing
 
+#![allow(clippy::unwrap_used, clippy::expect_used)]
+
 use std::fs;
 use std::io::Read;
 
@@ -33,7 +33,7 @@
     let full_path = get_data_file(file);
     let mut file = fs::File::open(full_path).expect("Should be able to open data file");
     let mut data = String::new();
-    file.read_to_string(&mut data).expect("should be able to read data file");
+    let _ = file.read_to_string(&mut data).expect("should be able to read data file");
     data
 }
 
@@ -45,12 +45,12 @@
 
 /// extract a string from a jsonvalue
 pub fn extract_key_str<'a>(value: &'a serde_json::Value, key: &str) -> &'a str {
-    value[key].as_str().unwrap()
+    value.get(key).unwrap().as_str().unwrap()
 }
 
 /// Decode a hex-encoded vec at `key`
 pub fn extract_key_vec(value: &serde_json::Value, key: &str) -> Vec<u8> {
-    hex::decode(value[key].as_str().unwrap()).unwrap()
+    hex::decode(value.get(key).unwrap().as_str().unwrap()).unwrap()
 }
 
 /// Decode a hex-encoded array at `key`
diff --git a/nearby/presence/xts_aes/Cargo.toml b/nearby/presence/xts_aes/Cargo.toml
index ba7aadb..86745c4 100644
--- a/nearby/presence/xts_aes/Cargo.toml
+++ b/nearby/presence/xts_aes/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [features]
 default = []
 std = []
@@ -14,7 +17,7 @@
 ldt_tbc.workspace = true
 
 [dev-dependencies]
-crypto_provider_default = {workspace = true, features = ["rustcrypto"]}
+crypto_provider_default = { workspace = true, features = ["rustcrypto"] }
 rand_ext.workspace = true
 test_helper.workspace = true
 wycheproof.workspace = true
diff --git a/nearby/presence/xts_aes/src/lib.rs b/nearby/presence/xts_aes/src/lib.rs
index ac2cf15..e8a80a1 100644
--- a/nearby/presence/xts_aes/src/lib.rs
+++ b/nearby/presence/xts_aes/src/lib.rs
@@ -12,21 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#![no_std]
-#![forbid(unsafe_code)]
-#![deny(
-    missing_docs,
-    clippy::unwrap_used,
-    clippy::panic,
-    clippy::expect_used,
-    clippy::indexing_slicing
-)]
-
 //! Implementation of the XTS-AES tweakable block cipher.
 //!
 //! See NIST docs [here](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf)
 //! and [here](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38e.pdf).
 
+#![no_std]
+
 #[cfg(feature = "std")]
 extern crate std;
 
diff --git a/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs b/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs
index 94ed066..abe1077 100644
--- a/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs
+++ b/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use aes::{cipher, cipher::KeyInit as _};
 use alloc::vec::Vec;
 use crypto_provider::aes::*;
diff --git a/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs b/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs
index 724e3bd..1f92f89 100644
--- a/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs
+++ b/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-extern crate core;
+#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
 
 use crypto_provider::CryptoProvider;
 use crypto_provider_default::CryptoProviderImpl;
diff --git a/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs b/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs
index 3d0cb50..6ceb8b9 100644
--- a/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs
+++ b/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-extern crate core;
+#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
 
 use anyhow::anyhow;
 use crypto_provider::CryptoProvider;
@@ -225,7 +225,7 @@
 
             // `key = value` in a test case chunk
             if let Some(captures) = regex::Regex::new("^(.*) = (.*)$").unwrap().captures(&line) {
-                map.insert(
+                let _ = map.insert(
                     captures.get(1).unwrap().as_str().to_owned(),
                     captures.get(2).unwrap().as_str().to_owned(),
                 );
diff --git a/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs b/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs
index 0e28dfa..924b665 100644
--- a/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs
+++ b/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#![allow(clippy::unwrap_used)]
+
 use alloc::vec::Vec;
 use crypto_provider::aes::*;
 use crypto_provider::CryptoProvider;
diff --git a/nearby/src/main.rs b/nearby/src/main.rs
index eaf3ed2..3d698e2 100644
--- a/nearby/src/main.rs
+++ b/nearby/src/main.rs
@@ -38,6 +38,7 @@
         Subcommand::CheckEverything { ref check_options } => {
             check_everything(&root_dir, check_options)?
         }
+        Subcommand::CleanEverything => clean_everything(&root_dir)?,
         Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?,
         Subcommand::FfiCheckEverything(ref options) => ffi::check_everything(&root_dir, options)?,
         Subcommand::BoringsslCheckEverything(ref options) => {
@@ -95,6 +96,8 @@
 
     Ok(())
 }
+
+/// Runs checks to ensure lints are passing and all targets are building
 pub fn check_everything(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
     license::check_license_headers(root)?;
     check_workspace(root, check_options)?;
@@ -110,6 +113,15 @@
     Ok(())
 }
 
+pub fn clean_everything(root: &path::Path) -> anyhow::Result<()> {
+    run_cmd_shell(root, "cargo clean")?;
+    run_cmd_shell(&root.join("presence/ldt_np_adv_ffi"), "cargo clean")?;
+    run_cmd_shell(&root.join("presence/np_c_ffi"), "cargo clean")?;
+    run_cmd_shell(&root.join("crypto/crypto_provider_boringssl"), "cargo clean")?;
+    run_cmd_shell(&root.join("connections/ukey2/ukey2_c_ffi"), "cargo clean")?;
+    Ok(())
+}
+
 #[derive(clap::Parser)]
 struct Cli {
     #[clap(subcommand)]
@@ -123,6 +135,9 @@
         #[command(flatten)]
         check_options: CheckOptions,
     },
+    /// Cleans the main workspace and all sub projects - useful if upgrading rust compiler version
+    /// and need dependencies to be compiled with the same version
+    CleanEverything,
     /// Checks everything included in the top level workspace
     CheckWorkspace(CheckOptions),
     /// Checks everything related to the boringssl version (equivalent of running check-boringssl
diff --git a/nearby/util/handle_map/Cargo.toml b/nearby/util/handle_map/Cargo.toml
index fbe3a20..13973d4 100644
--- a/nearby/util/handle_map/Cargo.toml
+++ b/nearby/util/handle_map/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 lock_adapter.workspace = true
 
diff --git a/nearby/util/handle_map/benches/benches.rs b/nearby/util/handle_map/benches/benches.rs
index f1ee427..f1988ba 100644
--- a/nearby/util/handle_map/benches/benches.rs
+++ b/nearby/util/handle_map/benches/benches.rs
@@ -11,6 +11,9 @@
 // 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.
+
+#![allow(missing_docs, unused_results, clippy::unwrap_used)]
+
 use criterion::{black_box, criterion_group, criterion_main, Criterion};
 use handle_map::*;
 use std::sync::Arc;
@@ -45,7 +48,7 @@
     let handle = handle_map.allocate(|| 0xFF).unwrap();
     let handle_map_ref = &handle_map;
     // Perform repeated reads
-    c.bench_function("single-threaded reads", |b| {
+    let _ = c.bench_function("single-threaded reads", |b| {
         b.iter(|| {
             let guard = handle_map_ref.get(black_box(handle)).unwrap();
             black_box(*guard)
diff --git a/nearby/util/handle_map/src/declare_handle_map.rs b/nearby/util/handle_map/src/declare_handle_map.rs
index dbdc77f..c19a012 100644
--- a/nearby/util/handle_map/src/declare_handle_map.rs
+++ b/nearby/util/handle_map/src/declare_handle_map.rs
@@ -131,6 +131,13 @@
             }
             impl $crate::HandleLike for $handle_type_name {
                 type Object = $wrapped_type;
+                fn try_allocate<E: core::fmt::Debug>(
+                    initial_value_provider: impl FnOnce() -> Result<$wrapped_type, E>,
+                ) -> Result<Self, $crate::HandleMapTryAllocateError<E>> {
+                    GLOBAL_HANDLE_MAP
+                        .try_allocate(initial_value_provider)
+                        .map(|derived_handle| Self { handle_id: derived_handle.get_id() })
+                }
                 fn allocate(
                     initial_value_provider: impl FnOnce() -> $wrapped_type,
                 ) -> Result<Self, $crate::HandleMapFullError> {
diff --git a/nearby/util/handle_map/src/guard.rs b/nearby/util/handle_map/src/guard.rs
index 4a96bb5..0c8c9d7 100644
--- a/nearby/util/handle_map/src/guard.rs
+++ b/nearby/util/handle_map/src/guard.rs
@@ -60,17 +60,13 @@
     type Ret = T;
 
     fn map<'b>(&self, arg: &'b Self::Arg) -> &'b Self::Ret {
-        // We know that the entry exists, since we've locked the
-        // shard and already checked that it exists prior to
-        // handing out this new, mapped read-lock.
-        arg.get(&self.handle).unwrap()
+        #[allow(clippy::expect_used)]
+        arg.get(&self.handle).expect("We know that the entry exists, since we've locked the shard and already checked that it exists prior to handing out this new, mapped read-lock.")
     }
 
     fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret {
-        // We know that the entry exists, since we've locked the
-        // shard and already checked that it exists prior to
-        // handing out this new, mapped read-lock.
-        arg.get_mut(&self.handle).unwrap()
+        #[allow(clippy::expect_used)]
+        arg.get_mut(&self.handle).expect("We know that the entry exists, since we've locked the shard and already checked that it exists prior to handing out this new, mapped read-lock.")
     }
 }
 
@@ -134,10 +130,12 @@
     type Ret = T;
 
     fn map<'b>(&self, arg: &'b Self::Arg) -> &'b Self::Ret {
-        arg.get(&self.handle).unwrap()
+        #[allow(clippy::expect_used)]
+        arg.get(&self.handle).expect("Caller must verify that provided hande exists")
     }
 
     fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret {
-        arg.get_mut(&self.handle).unwrap()
+        #[allow(clippy::expect_used)]
+        arg.get_mut(&self.handle).expect("Caller must verify that provided hande exists")
     }
 }
diff --git a/nearby/util/handle_map/src/lib.rs b/nearby/util/handle_map/src/lib.rs
index bb420e3..22009c1 100644
--- a/nearby/util/handle_map/src/lib.rs
+++ b/nearby/util/handle_map/src/lib.rs
@@ -15,9 +15,7 @@
 //! A thread-safe implementation of a map for managing object handles,
 //! a safer alternative to raw pointers for FFI interop.
 
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
-
+use core::fmt::Debug;
 use std::boxed::Box;
 use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
 use std::vec::Vec;
@@ -84,6 +82,17 @@
 #[derive(Debug)]
 pub struct HandleNotPresentError;
 
+/// Errors which may be raised while attempting to allocate
+/// a handle from contents given by a (fallible) value-provider.
+#[derive(Debug)]
+pub enum HandleMapTryAllocateError<E: Debug> {
+    /// The call to the value-provider for the allocation failed.
+    ValueProviderFailed(E),
+    /// We couldn't reserve a spot for the allocation, because
+    /// the handle-map was full.
+    HandleMapFull,
+}
+
 /// FFI-transmissible structure expressing the dimensions
 /// (max # of allocatable slots, number of shards) of a handle-map
 /// to be used upon initialization.
@@ -142,10 +151,35 @@
     /// may fail if attempting to allocate over the `dimensions.max_active_handles`
     /// limit imposed on the handle-map, in which case this method
     /// will return a `HandleMapFullError`.
+    ///
+    /// If you want the passed closure to be able to possibly fail, see
+    /// [`Self::try_allocate`] instead.
     pub fn allocate(
         &self,
         initial_value_provider: impl FnOnce() -> T,
     ) -> Result<Handle, HandleMapFullError> {
+        let wrapped_value_provider = move || Ok(initial_value_provider());
+        self.try_allocate::<core::convert::Infallible>(wrapped_value_provider).map_err(
+            |e| match e {
+                HandleMapTryAllocateError::ValueProviderFailed(never) => match never {},
+                HandleMapTryAllocateError::HandleMapFull => HandleMapFullError,
+            },
+        )
+    }
+
+    /// Attempts to allocate a new object within the given handle-map, returning
+    /// a handle to the location it was stored at. This operation
+    /// may fail if attempting to allocate over the `dimensions.max_active_handles`
+    /// limit imposed on the handle-map, in which case this method
+    /// will return a `HandleMapTryAllocateError::HandleMapFull`,
+    /// or if the passed initial-value provider fails, in which case this
+    /// will return the error wrapped in `HandleMapTryAllocateError::ValueProviderFailed`.
+    ///
+    /// If your initial-value provider is infallible, see [`Self::allocate`] instead.
+    pub fn try_allocate<E: Debug>(
+        &self,
+        initial_value_provider: impl FnOnce() -> Result<T, E>,
+    ) -> Result<Handle, HandleMapTryAllocateError<E>> {
         let mut initial_value_provider = initial_value_provider;
         loop {
             // Increment the new-handle-ID counter using relaxed memory ordering,
@@ -156,18 +190,26 @@
             let shard_index = new_handle.get_shard_index(self.dimensions.num_shards);
 
             // Now, check the shard to see if we can actually allocate into it.
-            let shard_allocate_result = self.handle_map_shards[shard_index].try_allocate(
-                new_handle,
-                initial_value_provider,
-                &self.outstanding_allocations_counter,
-                self.dimensions.max_active_handles,
-            );
+            #[allow(clippy::expect_used)]
+            let shard_allocate_result = self
+                .handle_map_shards
+                .get(shard_index)
+                .expect("Shard index is always within range")
+                .try_allocate(
+                    new_handle,
+                    initial_value_provider,
+                    &self.outstanding_allocations_counter,
+                    self.dimensions.max_active_handles,
+                );
             match shard_allocate_result {
                 Ok(_) => {
                     return Ok(new_handle);
                 }
+                Err(ShardAllocationError::ValueProviderFailed(e)) => {
+                    return Err(HandleMapTryAllocateError::ValueProviderFailed(e))
+                }
                 Err(ShardAllocationError::ExceedsAllocationLimit) => {
-                    return Err(HandleMapFullError);
+                    return Err(HandleMapTryAllocateError::HandleMapFull);
                 }
                 Err(ShardAllocationError::EntryOccupied(thrown_back_provider)) => {
                     // We need to do the whole thing again with a new ID
@@ -181,7 +223,11 @@
     /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`].
     pub fn get(&self, handle: Handle) -> Result<ObjectReadGuardImpl<T>, HandleNotPresentError> {
         let shard_index = handle.get_shard_index(self.dimensions.num_shards);
-        self.handle_map_shards[shard_index].get(handle)
+        #[allow(clippy::expect_used)]
+        self.handle_map_shards
+            .get(shard_index)
+            .expect("shard index is always within range")
+            .get(handle)
     }
 
     /// Gets a read+write reference to an object within the given handle-map,
@@ -191,7 +237,11 @@
         handle: Handle,
     ) -> Result<ObjectReadWriteGuardImpl<T>, HandleNotPresentError> {
         let shard_index = handle.get_shard_index(self.dimensions.num_shards);
-        self.handle_map_shards[shard_index].get_mut(handle)
+        #[allow(clippy::expect_used)]
+        self.handle_map_shards
+            .get(shard_index)
+            .expect("shard_index is always in range")
+            .get_mut(handle)
     }
 
     /// Removes the object pointed to by the given handle in
@@ -199,7 +249,10 @@
     /// exists. Otherwise, returns [`HandleNotPresentError`].
     pub fn deallocate(&self, handle: Handle) -> Result<T, HandleNotPresentError> {
         let shard_index = handle.get_shard_index(self.dimensions.num_shards);
-        self.handle_map_shards[shard_index]
+        #[allow(clippy::expect_used)]
+        self.handle_map_shards
+            .get(shard_index)
+            .expect("shard index is always in range")
             .deallocate(handle, &self.outstanding_allocations_counter)
     }
 
@@ -224,9 +277,16 @@
     /// The underlying object type pointed-to by this handle
     type Object: Send + Sync;
 
-    /// Tries to allocate a new handle using the given provider
-    /// to construct the underlying stored object as a new
-    /// entry into the global handle table for this type.
+    /// Tries to allocate a new handle using the given (fallible)
+    /// provider to construct the underlying stored object as
+    /// a new entry into the global handle table for this type.
+    fn try_allocate<E: Debug>(
+        initial_value_provider: impl FnOnce() -> Result<Self::Object, E>,
+    ) -> Result<Self, HandleMapTryAllocateError<E>>;
+
+    /// Tries to allocate a new handle using the given (infallible)
+    /// provider to construct the underlying stored object as
+    /// a new entry into the global handle table for this type.
     fn allocate(
         initial_value_provider: impl FnOnce() -> Self::Object,
     ) -> Result<Self, HandleMapFullError>;
diff --git a/nearby/util/handle_map/src/shard.rs b/nearby/util/handle_map/src/shard.rs
index da3bd57..e5aad2f 100644
--- a/nearby/util/handle_map/src/shard.rs
+++ b/nearby/util/handle_map/src/shard.rs
@@ -33,13 +33,15 @@
 type ShardReadWriteGuard<'a, T> = RwLockWriteGuard<'a, ShardMapType<T>>;
 
 /// Internal error enum for failed allocations into a given shard.
-pub(crate) enum ShardAllocationError<T, F: FnOnce() -> T> {
+pub(crate) enum ShardAllocationError<T, E, F: FnOnce() -> Result<T, E>> {
     /// Error for when the entry for the handle is occupied,
     /// in which case we spit out the object-provider to try again
     /// with a new handle-id.
     EntryOccupied(F),
     /// Error for when we would exceed the maximum number of allocations.
     ExceedsAllocationLimit,
+    /// Error for when the initial value-provider call failed.
+    ValueProviderFailed(E),
 }
 
 /// An individual handle-map shard, which is ultimately
@@ -116,7 +118,11 @@
         // outstanding upgradeable guard on the shard. See `spin` documentation.
         // Remove the pointed-to object from the map, and return it,
         // releasing the lock when the guard goes out of scope.
-        let removed_object = map_read_write_guard.deref_mut().remove(&handle).unwrap();
+        #[allow(clippy::expect_used)]
+        let removed_object = map_read_write_guard
+            .deref_mut()
+            .remove(&handle)
+            .expect("existence of handle is checked above");
         // Decrement the allocations counter. Release ordering because we want
         // to ensure that clearing the map entry never gets re-ordered to after when
         // this counter gets decremented.
@@ -124,15 +130,15 @@
         Ok(removed_object)
     }
 
-    pub fn try_allocate<F>(
+    pub fn try_allocate<E, F>(
         &self,
         handle: Handle,
         object_provider: F,
         outstanding_allocations_counter: &AtomicU32,
         max_active_handles: u32,
-    ) -> Result<(), ShardAllocationError<T, F>>
+    ) -> Result<(), ShardAllocationError<T, E, F>>
     where
-        F: FnOnce() -> T,
+        F: FnOnce() -> Result<T, E>,
     {
         let mut read_write_guard = self.data.write();
         match read_write_guard.entry(handle) {
@@ -161,10 +167,17 @@
                 );
                 match allocation_count_bump_result {
                     Ok(_) => {
-                        // We're good to actually allocate
-                        let object = object_provider();
-                        vacant_entry.insert(object);
-                        Ok(())
+                        // We're good to actually allocate,
+                        // so attempt to call the value-provider.
+                        match object_provider() {
+                            Ok(object) => {
+                                // Successfully obtained the initial value,
+                                // so insert it into the vacant entry.
+                                let _ = vacant_entry.insert(object);
+                                Ok(())
+                            }
+                            Err(e) => Err(ShardAllocationError::ValueProviderFailed(e)),
+                        }
                     }
                     Err(_) => {
                         // The allocation would cause us to exceed the allowed allocations,
diff --git a/nearby/util/handle_map/src/tests.rs b/nearby/util/handle_map/src/tests.rs
index a0774d6..90e773a 100644
--- a/nearby/util/handle_map/src/tests.rs
+++ b/nearby/util/handle_map/src/tests.rs
@@ -11,6 +11,9 @@
 // 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.
+
+#![allow(clippy::unwrap_used, clippy::expect_used)]
+
 use crate::*;
 
 use core::ops::{Deref, DerefMut};
@@ -111,7 +114,7 @@
     let test_fn = Arc::new(move || {
         let allocation_result = handle_map_function_ref.allocate(|| 0xFF);
         if let Ok(handle) = allocation_result {
-            handle_map_function_ref.deallocate(handle).unwrap();
+            let _ = handle_map_function_ref.deallocate(handle).unwrap();
         }
     });
     test_for_each_thread(test_fn, num_repetitions_per_thread);
@@ -123,7 +126,7 @@
     assert_eq!((MAX_ACTIVE_HANDLES - 1) as usize, actual_num_active_handles);
 
     //Verify that we still have space for one more entry after all that.
-    handle_map_post_function_ref.allocate(|| 0xEE).unwrap();
+    let _ = handle_map_post_function_ref.allocate(|| 0xEE).unwrap();
 }
 
 /// Tests the progress of allocate/read/write/read/deallocate
@@ -219,7 +222,7 @@
     let mut handle_map = build_handle_map::<u8>();
     for _ in 0..(num_repetitions_per_thread * NUM_ACTIVE_THREADS) {
         let handle = handle_map.allocate(|| 0xFF).expect("Initial allocations shouldn't fail");
-        all_handles.insert(handle);
+        let _ = all_handles.insert(handle);
     }
     // Reset the new-handle-id counter
     handle_map.set_new_handle_id_counter(0);
@@ -254,8 +257,9 @@
 fn test_id_wraparound() {
     let mut handle_map = build_handle_map::<u8>();
     handle_map.set_new_handle_id_counter(u64::MAX);
-    handle_map.allocate(|| 0xAB).expect("Counter wrap-around allocation should not fail");
-    handle_map.allocate(|| 0xCD).expect("Post-counter-wrap-around allocation should not fail");
+    let _ = handle_map.allocate(|| 0xAB).expect("Counter wrap-around allocation should not fail");
+    let _ =
+        handle_map.allocate(|| 0xCD).expect("Post-counter-wrap-around allocation should not fail");
 }
 
 #[test]
diff --git a/nearby/util/lock_adapter/Cargo.toml b/nearby/util/lock_adapter/Cargo.toml
index eae1fc9..6e3174b 100644
--- a/nearby/util/lock_adapter/Cargo.toml
+++ b/nearby/util/lock_adapter/Cargo.toml
@@ -4,6 +4,9 @@
 edition.workspace = true
 publish.workspace = true
 
+[lints]
+workspace = true
+
 [dependencies]
 spin = { workspace = true, optional = true }
 
diff --git a/nearby/util/lock_adapter/src/lib.rs b/nearby/util/lock_adapter/src/lib.rs
index 3d75c0d..6bf6f0f 100644
--- a/nearby/util/lock_adapter/src/lib.rs
+++ b/nearby/util/lock_adapter/src/lib.rs
@@ -15,8 +15,6 @@
 //! An abstraction layer for Rust synchronization primitives which provides both no_std and std library
 //! based implementations
 
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
 
 /// A Spinlock-based implementation of Mutex using the `spin` crate that can be used in `no_std`