Merge changes Idad0ce1e,I84f0ca0f into main

* changes:
  pvmfw: Validate config header entries when Config::new()
  pvmfw: Refactor to add config::Range
diff --git a/Android.bp b/Android.bp
index 2f6fc20..64d193a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -64,3 +64,9 @@
         },
     },
 }
+
+genrule_defaults {
+    name: "dts_to_dtb",
+    tools: ["dtc"],
+    cmd: "$(location dtc) -I dts -O dtb $(in) -o $(out)",
+}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index adf6309..1410534 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -31,6 +31,9 @@
     },
     {
       "name": "libdice_policy.test"
+    },
+    {
+      "name": "libapkzip.test"
     }
   ],
   "avf-postsubmit": [
@@ -81,6 +84,9 @@
       "path": "packages/modules/Virtualization/libs/devicemapper"
     },
     {
+      "path": "packages/modules/Virtualization/libs/dice"
+    },
+    {
       "path": "packages/modules/Virtualization/libs/libfdt"
     },
     {
@@ -99,6 +105,9 @@
       "path": "packages/modules/Virtualization/rialto"
     },
     {
+      "path": "packages/modules/Virtualization/service_vm/requests"
+    },
+    {
       "path": "packages/modules/Virtualization/vm"
     },
     {
diff --git a/apex/empty-payload-apk/Android.bp b/apex/empty-payload-apk/Android.bp
index 70e6754..8bd138f 100644
--- a/apex/empty-payload-apk/Android.bp
+++ b/apex/empty-payload-apk/Android.bp
@@ -9,8 +9,8 @@
     apex_available: ["com.android.virt"],
     sdk_version: "system_current",
     jni_uses_platform_apis: true,
-    min_sdk_version: "UpsideDownCake",
-    target_sdk_version: "UpsideDownCake",
+    min_sdk_version: "34",
+    target_sdk_version: "34",
     compile_multilib: "first",
     stl: "none",
 }
diff --git a/compos/Android.bp b/compos/Android.bp
index 19123dd..b840506 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -14,6 +14,7 @@
         "libanyhow",
         "libbinder_rs",
         "libcompos_common",
+        "libhex",
         "liblibc",
         "liblog_rust",
         "libminijail_rust",
diff --git a/compos/benchmark/Android.bp b/compos/benchmark/Android.bp
index dc0c01c..93927a2 100644
--- a/compos/benchmark/Android.bp
+++ b/compos/benchmark/Android.bp
@@ -13,7 +13,7 @@
         "androidx.test.ext.junit",
         "MicrodroidDeviceTestHelper",
         "MicrodroidTestHelper",
-        "truth-prebuilt",
+        "truth",
     ],
     sdk_version: "test_current",
     use_embedded_native_libs: true,
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index 76da00a..bfd886e 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -53,7 +53,7 @@
 
         let file = File::open(path).with_context(|| format!("Opening {}", path.display()))?;
         let digest = fsverity::measure(file.as_fd())?;
-        let digest = to_hex_string(&digest);
+        let digest = hex::encode(digest);
 
         self.file_digests.push((target_path.to_owned(), digest));
         Ok(())
@@ -82,7 +82,3 @@
         Ok(())
     }
 }
-
-fn to_hex_string(buf: &[u8]) -> String {
-    buf.iter().map(|b| format!("{:02x}", b)).collect()
-}
diff --git a/libs/apkmanifest/Android.bp b/libs/apkmanifest/Android.bp
new file mode 100644
index 0000000..e6fcbef
--- /dev/null
+++ b/libs/apkmanifest/Android.bp
@@ -0,0 +1,46 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libapkmanifest_native",
+    srcs: ["native/*.cpp"],
+    shared_libs: [
+        "libandroidfw",
+        "libbase",
+        "liblog",
+        "libutils",
+    ],
+}
+
+rust_bindgen {
+    name: "libapkmanifest_bindgen",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    wrapper_src: "native/apkmanifest.hpp",
+    crate_name: "apkmanifest_bindgen",
+    source_stem: "bindings",
+    bindgen_flags: [
+        "--default-enum-style rust",
+    ],
+}
+
+rust_library {
+    name: "libapkmanifest",
+    crate_name: "apkmanifest",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    srcs: ["src/apkmanifest.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libapkzip",
+        "libapkmanifest_bindgen",
+        "libscopeguard",
+    ],
+    shared_libs: ["libapkmanifest_native"],
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libs/apkmanifest/native/apkmanifest.cpp b/libs/apkmanifest/native/apkmanifest.cpp
new file mode 100644
index 0000000..ab0ba72
--- /dev/null
+++ b/libs/apkmanifest/native/apkmanifest.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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 "apkmanifest.hpp"
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <androidfw/AssetsProvider.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/StringPiece.h>
+#include <androidfw/Util.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <utils/Errors.h>
+
+#include <cstdlib>
+#include <limits>
+#include <string>
+#include <string_view>
+
+using android::Asset;
+using android::AssetsProvider;
+using android::OK;
+using android::Res_value;
+using android::ResXMLParser;
+using android::ResXMLTree;
+using android::statusToString;
+using android::StringPiece16;
+using android::base::Error;
+using android::base::Result;
+using android::util::Utf16ToUtf8;
+using std::u16string_view;
+using std::unique_ptr;
+
+struct ApkManifestInfo {
+    std::string package;
+    uint32_t version_code;
+    uint32_t version_code_major;
+};
+
+namespace {
+// See https://developer.android.com/guide/topics/manifest/manifest-element
+constexpr u16string_view MANIFEST_TAG_NAME{u"manifest"};
+constexpr u16string_view ANDROID_NAMESPACE_URL{u"http://schemas.android.com/apk/res/android"};
+constexpr u16string_view PACKAGE_ATTRIBUTE_NAME{u"package"};
+constexpr u16string_view VERSION_CODE_ATTRIBUTE_NAME{u"versionCode"};
+constexpr u16string_view VERSION_CODE_MAJOR_ATTRIBUTE_NAME{u"versionCodeMajor"};
+
+// Read through the XML parse tree up to the <manifest> element.
+Result<void> findManifestElement(ResXMLTree& tree) {
+    for (;;) {
+        ResXMLParser::event_code_t event = tree.next();
+        switch (event) {
+            case ResXMLParser::END_DOCUMENT:
+            case ResXMLParser::END_TAG:
+            case ResXMLParser::TEXT:
+            default:
+                return Error() << "Unexpected XML parsing event: " << event;
+            case ResXMLParser::BAD_DOCUMENT:
+                return Error() << "Failed to parse XML: " << statusToString(tree.getError());
+            case ResXMLParser::START_NAMESPACE:
+            case ResXMLParser::END_NAMESPACE:
+                // Not of interest, keep going.
+                break;
+            case ResXMLParser::START_TAG:
+                // The first tag in an AndroidManifest.xml should be <manifest> (no namespace).
+                // And that's actually the only tag we care about.
+                if (tree.getElementNamespaceID() >= 0) {
+                    return Error() << "Root element has unexpected namespace.";
+                }
+                size_t nameLength = 0;
+                const char16_t* nameChars = tree.getElementName(&nameLength);
+                if (!nameChars) {
+                    return Error() << "Missing tag name";
+                }
+                if (u16string_view(nameChars, nameLength) != MANIFEST_TAG_NAME) {
+                    return Error() << "Expected <manifest> as root element";
+                }
+                return {};
+        }
+    }
+}
+
+// Return an attribute encoded as a string, converted to UTF-8. Note that all
+// attributes are strings in the original XML, but the binary format encodes
+// some as binary numbers etc. This function does not handle converting those
+// encodings back to strings, so should only be used when it is known that a
+// numeric value is not allowed.
+Result<std::string> getStringOnlyAttribute(const ResXMLTree& tree, size_t index) {
+    size_t len;
+    const char16_t* value = tree.getAttributeStringValue(index, &len);
+    if (!value) {
+        return Error() << "Expected attribute to have string value";
+    }
+    return Utf16ToUtf8(StringPiece16(value, len));
+}
+
+// Return the u32 value of an attribute.
+Result<uint32_t> getU32Attribute(const ResXMLTree& tree, size_t index) {
+    auto type = tree.getAttributeDataType(index);
+    switch (type) {
+        case Res_value::TYPE_INT_DEC:
+        case Res_value::TYPE_INT_HEX:
+            // This is how we'd expect the version to be encoded - and we don't
+            // care what base it was originally in.
+            return tree.getAttributeData(index);
+        case Res_value::TYPE_STRING: {
+            // If the original string is encoded, then we need to convert it.
+            auto str = OR_RETURN(getStringOnlyAttribute(tree, index));
+            char* str_end = nullptr;
+            // Note that by specifying base 0 we allow for octal, hex, or
+            // decimal representations here.
+            unsigned long value = std::strtoul(str.c_str(), &str_end, 0);
+            if (str_end != str.c_str() + str.size() ||
+                value > std::numeric_limits<uint32_t>::max()) {
+                return Error() << "Invalid numeric value";
+            }
+            return static_cast<uint32_t>(value);
+        }
+        default:
+            return Error() << "Expected numeric value, got type " << type;
+    }
+}
+
+// Parse the binary manifest and extract the information we care about.
+// Everything we're interested in should be an attribute on the <manifest> tag.
+// We don't care what order they come in, absent attributes will be treated as
+// the default value, and any unknown attributes (including ones not in the
+// expected namespace) will be ignored.
+Result<unique_ptr<ApkManifestInfo>> parseManifest(const void* manifest, size_t size) {
+    ResXMLTree tree;
+    auto status = tree.setTo(manifest, size);
+    if (status != OK) {
+        return Error() << "Failed to create XML Tree: " << statusToString(status);
+    }
+
+    OR_RETURN(findManifestElement(tree));
+
+    unique_ptr<ApkManifestInfo> info{new ApkManifestInfo{}};
+
+    size_t count = tree.getAttributeCount();
+    for (size_t i = 0; i < count; ++i) {
+        size_t len;
+        const char16_t* chars;
+
+        chars = tree.getAttributeNamespace(i, &len);
+        auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
+
+        chars = tree.getAttributeName(i, &len);
+        auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
+
+        if (namespaceUrl.empty()) {
+            if (attributeName == PACKAGE_ATTRIBUTE_NAME) {
+                auto result = getStringOnlyAttribute(tree, i);
+                if (!result.ok()) return Error() << "Package name: " << result.error();
+                info->package = *result;
+            }
+        } else if (namespaceUrl == ANDROID_NAMESPACE_URL) {
+            if (attributeName == VERSION_CODE_ATTRIBUTE_NAME) {
+                auto result = getU32Attribute(tree, i);
+                if (!result.ok()) return Error() << "Version code: " << result.error();
+                info->version_code = *result;
+            } else if (attributeName == VERSION_CODE_MAJOR_ATTRIBUTE_NAME) {
+                auto result = getU32Attribute(tree, i);
+                if (!result.ok()) return Error() << "Version code major: " << result.error();
+                info->version_code_major = *result;
+            }
+        }
+    }
+
+    return info;
+}
+} // namespace
+
+const ApkManifestInfo* extractManifestInfo(const void* manifest, size_t size) {
+    auto result = parseManifest(manifest, size);
+    if (!result.ok()) {
+        LOG(ERROR) << "Failed to parse APK manifest:" << result.error().message();
+        return nullptr;
+    }
+    return result->release();
+}
+
+void freeManifestInfo(const ApkManifestInfo* info) {
+    delete info;
+}
+
+const char* getPackageName(const ApkManifestInfo* info) {
+    return info->package.c_str();
+}
+
+uint64_t getVersionCode(const ApkManifestInfo* info) {
+    return info->version_code | (static_cast<uint64_t>(info->version_code_major) << 32);
+}
diff --git a/libs/apkmanifest/native/apkmanifest.hpp b/libs/apkmanifest/native/apkmanifest.hpp
new file mode 100644
index 0000000..352912e
--- /dev/null
+++ b/libs/apkmanifest/native/apkmanifest.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Opaque structure holding information extracted from an APK manifest.
+struct ApkManifestInfo;
+
+extern "C" {
+
+// Parse a binary XML encoded APK manifest and extract relevant information.
+// The caller must free the returned pointer using freeManifestInfo.  Returns
+// null if any error occurs. Does not retain any pointer to the manifest
+// provided.
+const ApkManifestInfo* extractManifestInfo(const void* manifest, size_t size);
+
+// Frees an ApkManifestInfo allocated by extractManifestInfo; this invalidates
+// the pointer and it must not be used again.
+void freeManifestInfo(const ApkManifestInfo* info);
+
+// Given a valid ApkManifestInfo pointer, return the package name of the APK, as
+// a nul-terminated UTF-8 string. The pointer remains valid until the
+// ApkManifestInfo is freed.
+const char* getPackageName(const ApkManifestInfo* info);
+
+// Given a valid ApkManifestInfo pointer, return the version code of the APK.
+uint64_t getVersionCode(const ApkManifestInfo* info);
+}
diff --git a/libs/apkmanifest/src/apkmanifest.rs b/libs/apkmanifest/src/apkmanifest.rs
new file mode 100644
index 0000000..6766b21
--- /dev/null
+++ b/libs/apkmanifest/src/apkmanifest.rs
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//! Handle parsing of APK manifest files.
+//! The manifest file is written as XML text, but is stored in the APK
+//! as Android binary compressed XML. This library is a wrapper around
+//! a thin C++ wrapper around libandroidfw, which contains the same
+//! parsing code as used by package manager and aapt2 (amongst other
+//! things).
+
+use anyhow::{bail, Context, Result};
+use apkmanifest_bindgen::{extractManifestInfo, freeManifestInfo, getPackageName, getVersionCode};
+use std::ffi::CStr;
+use std::fs::File;
+use std::path::Path;
+
+/// Information extracted from the Android manifest inside an APK.
+#[derive(Debug)]
+pub struct ApkManifestInfo {
+    /// The package name of the app.
+    pub package: String,
+    /// The version code of the app.
+    pub version_code: u64,
+}
+
+const ANDROID_MANIFEST: &str = "AndroidManifest.xml";
+
+/// Find the manifest inside the given APK and return information from it.
+pub fn get_manifest_info<P: AsRef<Path>>(apk_path: P) -> Result<ApkManifestInfo> {
+    let apk = File::open(apk_path.as_ref())?;
+    let manifest = apkzip::read_file(apk, ANDROID_MANIFEST)?;
+
+    // Safety: The function only reads the memory range we specify and does not hold
+    // any reference to it.
+    let native_info = unsafe { extractManifestInfo(manifest.as_ptr() as _, manifest.len()) };
+    if native_info.is_null() {
+        bail!("Failed to parse manifest")
+    };
+
+    scopeguard::defer! {
+        // Safety: The value we pass is the result of calling extractManifestInfo as required.
+        // We must call this exactly once, after we have finished using it, which the scopeguard
+        // ensures.
+        unsafe { freeManifestInfo(native_info); }
+    }
+
+    // Safety: It is always safe to call this with a valid native_info, which we have,
+    // and it always returns a valid nul-terminated C string with the same lifetime as native_info.
+    // We immediately make a copy.
+    let package = unsafe { CStr::from_ptr(getPackageName(native_info)) };
+    let package = package.to_str().context("Invalid package name")?.to_string();
+
+    // Safety: It is always safe to call this with a valid native_info, which we have.
+    let version_code = unsafe { getVersionCode(native_info) };
+
+    Ok(ApkManifestInfo { package, version_code })
+}
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index d3aa7ee..1c18d2d 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -7,10 +7,10 @@
     crate_name: "apkverify",
     defaults: ["avf_build_flags_rust"],
     srcs: ["src/lib.rs"],
-    prefer_rlib: true,
     edition: "2021",
     rustlibs: [
         "libanyhow",
+        "libapkzip",
         "libbyteorder",
         "libbytes",
         "libhex",
@@ -18,7 +18,6 @@
         "libnum_traits",
         "libopenssl",
         "libserde",
-        "libzip",
     ],
     proc_macros: ["libnum_derive"],
 }
@@ -34,6 +33,7 @@
 rust_test {
     name: "libapkverify.test",
     defaults: ["libapkverify.defaults"],
+    prefer_rlib: true,
     test_suites: ["general-tests"],
     data: ["tests/data/*"],
 }
@@ -49,6 +49,8 @@
     rustlibs: [
         "libandroid_logger",
         "libapkverify",
+        "libapkzip",
+        "libbyteorder",
         "liblog_rust",
         "libzip",
     ],
diff --git a/libs/apkverify/src/lib.rs b/libs/apkverify/src/lib.rs
index f7cbb7e..6af8122 100644
--- a/libs/apkverify/src/lib.rs
+++ b/libs/apkverify/src/lib.rs
@@ -24,7 +24,6 @@
 pub mod testing;
 mod v3;
 mod v4;
-mod ziputil;
 
 pub use algorithms::{HashAlgorithm, SignatureAlgorithmID};
 pub use v3::{get_public_key_der, verify};
diff --git a/libs/apkverify/src/sigutil.rs b/libs/apkverify/src/sigutil.rs
index 395b493..7d03bb2 100644
--- a/libs/apkverify/src/sigutil.rs
+++ b/libs/apkverify/src/sigutil.rs
@@ -17,6 +17,7 @@
 //! Utilities for Signature Verification
 
 use anyhow::{anyhow, ensure, Error, Result};
+use apkzip::{set_central_directory_offset, zip_sections};
 use byteorder::{LittleEndian, ReadBytesExt};
 use bytes::{Buf, BufMut, Bytes, BytesMut};
 use openssl::hash::{DigestBytes, Hasher, MessageDigest};
@@ -24,7 +25,6 @@
 use std::io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom, Take};
 
 use crate::algorithms::SignatureAlgorithmID;
-use crate::ziputil::{set_central_directory_offset, zip_sections};
 
 const APK_SIG_BLOCK_MIN_SIZE: u32 = 32;
 const APK_SIG_BLOCK_MAGIC: u128 = 0x3234206b636f6c4220676953204b5041;
@@ -51,8 +51,8 @@
 }
 
 impl<R: Read + Seek> ApkSections<R> {
-    pub fn new(reader: R) -> Result<ApkSections<R>> {
-        let (mut reader, zip_sections) = zip_sections(reader)?;
+    pub fn new(mut reader: R) -> Result<ApkSections<R>> {
+        let zip_sections = zip_sections(&mut reader)?;
         let (signing_block_offset, signing_block_size) =
             find_signing_block(&mut reader, zip_sections.central_directory_offset)?;
         Ok(ApkSections {
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index 6082422..8a8ad73 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -29,7 +29,7 @@
 
 use crate::algorithms::SignatureAlgorithmID;
 use crate::bytes_ext::{BytesExt, LengthPrefixed, ReadFromBytes};
-use crate::sigutil::*;
+use crate::sigutil::ApkSections;
 
 pub const APK_SIGNATURE_SCHEME_V3_BLOCK_ID: u32 = 0xf05368c0;
 
@@ -161,7 +161,8 @@
         // 1. Choose the strongest supported signature algorithm ID from signatures.
         let strongest = self.strongest_signature()?;
 
-        // 2. Verify the corresponding signature from signatures against signed data using public key.
+        // 2. Verify the corresponding signature from signatures against signed data using public
+        // key.
         let verified_signed_data = self.verify_signature(strongest)?;
 
         // 3. Verify the min and max SDK versions in the signed data match those specified for the
@@ -196,8 +197,8 @@
             hex::encode(digest.digest.as_ref()),
         );
 
-        // 7. Verify that public key of the first certificate of certificates is identical
-        //    to public key.
+        // 7. Verify that public key of the first certificate of certificates is identical to public
+        //    key.
         let cert = verified_signed_data.certificates.first().context("No certificates listed")?;
         let cert = X509::from_der(cert.as_ref())?;
         ensure!(
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index 52e1da4..680c81e 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -17,7 +17,10 @@
 use apkverify::{
     get_apk_digest, get_public_key_der, testing::assert_contains, verify, SignatureAlgorithmID,
 };
+use apkzip::zip_sections;
+use byteorder::{LittleEndian, ReadBytesExt};
 use log::info;
+use std::io::{Seek, SeekFrom};
 use std::{fs, matches, path::Path};
 
 const KEY_NAMES_DSA: &[&str] = &["1024", "2048", "3072"];
@@ -37,6 +40,28 @@
 }
 
 #[test]
+fn test_zip_sections_with_apk() {
+    let mut reader = fs::File::open("tests/data/v3-only-with-stamp.apk").unwrap();
+    let sections = zip_sections(&mut reader).unwrap();
+
+    // Checks Central directory.
+    assert_eq!(
+        sections.central_directory_offset + sections.central_directory_size,
+        sections.eocd_offset
+    );
+
+    // Checks EOCD.
+    const EOCD_SIGNATURE: u32 = 0x06054b50;
+
+    reader.seek(SeekFrom::Start(sections.eocd_offset as u64)).unwrap();
+    assert_eq!(reader.read_u32::<LittleEndian>().unwrap(), EOCD_SIGNATURE);
+    assert_eq!(
+        reader.metadata().unwrap().len(),
+        (sections.eocd_offset + sections.eocd_size) as u64
+    );
+}
+
+#[test]
 fn test_verify_truncated_cd() {
     setup();
     use zip::result::ZipError;
@@ -284,7 +309,7 @@
     let apk = fs::File::open(&apk_path).expect("Unabled to open apk file");
 
     let (verified_algorithm_id, verified_digest) =
-        get_apk_digest(&apk, SDK_INT, /*verify=*/ true)
+        get_apk_digest(&apk, SDK_INT, /* verify= */ true)
             .expect("Error when extracting apk digest with verification.");
 
     assert_eq!(expected_algorithm_id, verified_algorithm_id);
@@ -292,7 +317,7 @@
     assert_bytes_eq_to_data_in_file(&verified_digest, expected_digest_path);
 
     let (unverified_algorithm_id, unverified_digest) =
-        get_apk_digest(&apk, SDK_INT, /*verify=*/ false)
+        get_apk_digest(&apk, SDK_INT, /* verify= */ false)
             .expect("Error when extracting apk digest without verification.");
     assert_eq!(expected_algorithm_id, unverified_algorithm_id);
     assert_eq!(verified_digest, unverified_digest);
diff --git a/libs/apkzip/Android.bp b/libs/apkzip/Android.bp
new file mode 100644
index 0000000..dc35b5e
--- /dev/null
+++ b/libs/apkzip/Android.bp
@@ -0,0 +1,35 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libapkzip.defaults",
+    crate_name: "apkzip",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    srcs: ["src/ziputil.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libbytes",
+        "liblog_rust",
+        "libanyhow",
+        "libzip",
+    ],
+}
+
+rust_library {
+    name: "libapkzip",
+    defaults: ["libapkzip.defaults"],
+    host_supported: true,
+    apex_available: ["com.android.virt"],
+}
+
+rust_test {
+    name: "libapkzip.test",
+    defaults: ["libapkzip.defaults"],
+    prefer_rlib: true,
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libbyteorder",
+    ],
+}
diff --git a/libs/apkverify/src/ziputil.rs b/libs/apkzip/src/ziputil.rs
similarity index 74%
rename from libs/apkverify/src/ziputil.rs
rename to libs/apkzip/src/ziputil.rs
index 5e513a7..708bbcc 100644
--- a/libs/apkverify/src/ziputil.rs
+++ b/libs/apkzip/src/ziputil.rs
@@ -21,9 +21,6 @@
 use std::io::{Read, Seek};
 use zip::ZipArchive;
 
-#[cfg(test)]
-use std::io::SeekFrom;
-
 const EOCD_SIZE_WITHOUT_COMMENT: usize = 22;
 const EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET: usize = 12;
 const EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET: usize = 16;
@@ -31,16 +28,21 @@
 const EOCD_SIGNATURE: u32 = 0x06054b50;
 const ZIP64_MARK: u32 = 0xffffffff;
 
+/// Information about the layout of a zip file.
 #[derive(Debug, PartialEq, Eq)]
 pub struct ZipSections {
+    /// Offset within the file of the central directory.
     pub central_directory_offset: u32,
+    /// Size of the central directory.
     pub central_directory_size: u32,
+    /// Offset within the file of end of central directory marker.
     pub eocd_offset: u32,
+    /// Size of the end of central directory marker.
     pub eocd_size: u32,
 }
 
 /// Discover the layout of a zip file.
-pub fn zip_sections<R: Read + Seek>(mut reader: R) -> Result<(R, ZipSections)> {
+pub fn zip_sections<R: Read + Seek>(mut reader: R) -> Result<ZipSections> {
     // open a zip to parse EOCD
     let archive = ZipArchive::new(reader)?;
     let eocd_size = archive.comment().len() + EOCD_SIZE_WITHOUT_COMMENT;
@@ -65,15 +67,12 @@
         "Invalid ZIP: EOCD should follow CD with no extra data or overlap."
     );
 
-    Ok((
-        reader,
-        ZipSections {
-            central_directory_offset,
-            central_directory_size,
-            eocd_offset,
-            eocd_size: eocd_size as u32,
-        },
-    ))
+    Ok(ZipSections {
+        central_directory_offset,
+        central_directory_size,
+        eocd_offset,
+        eocd_size: eocd_size as u32,
+    })
 }
 
 fn get_central_directory(buf: &[u8]) -> Result<(u32, u32)> {
@@ -91,25 +90,39 @@
     Ok(())
 }
 
+/// Read an entire file from a .zip file into memory and return it.
+pub fn read_file<R: Read + Seek>(reader: R, file_name: &str) -> Result<Vec<u8>> {
+    let mut archive = ZipArchive::new(reader)?;
+    let mut file = archive.by_name(file_name)?;
+    let mut bytes = Vec::with_capacity(file.size() as usize);
+    file.read_to_end(&mut bytes)?;
+    Ok(bytes)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::testing::assert_contains;
-    use byteorder::{LittleEndian, ReadBytesExt};
-    use std::fs::File;
     use std::io::{Cursor, Write};
     use zip::{write::FileOptions, ZipWriter};
 
+    const FILE_CONTENT: &[u8] = b"testcontent";
+    const FILE_NAME: &str = "testfile";
+
     fn create_test_zip() -> Cursor<Vec<u8>> {
         let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
-        writer.start_file("testfile", FileOptions::default()).unwrap();
-        writer.write_all(b"testcontent").unwrap();
+        writer.start_file(FILE_NAME, FileOptions::default()).unwrap();
+        writer.write_all(FILE_CONTENT).unwrap();
         writer.finish().unwrap()
     }
 
+    fn assert_contains(haystack: &str, needle: &str) {
+        assert!(haystack.contains(needle), "{} is not found in {}", needle, haystack);
+    }
+
     #[test]
     fn test_zip_sections() {
-        let (cursor, sections) = zip_sections(create_test_zip()).unwrap();
+        let mut cursor = create_test_zip();
+        let sections = zip_sections(&mut cursor).unwrap();
         assert_eq!(
             sections.eocd_offset,
             (cursor.get_ref().len() - EOCD_SIZE_WITHOUT_COMMENT) as u32
@@ -117,6 +130,12 @@
     }
 
     #[test]
+    fn test_read_file() {
+        let file = read_file(create_test_zip(), FILE_NAME).unwrap();
+        assert_eq!(file.as_slice(), FILE_CONTENT);
+    }
+
+    #[test]
     fn test_reject_if_extra_data_between_cd_and_eocd() {
         // prepare normal zip
         let buf = create_test_zip().into_inner();
@@ -133,24 +152,4 @@
         assert!(res.is_err());
         assert_contains(&res.err().unwrap().to_string(), "Invalid ZIP: offset should be 0");
     }
-
-    #[test]
-    fn test_zip_sections_with_apk() {
-        let apk = File::open("tests/data/v3-only-with-stamp.apk").unwrap();
-        let (mut reader, sections) = zip_sections(apk).unwrap();
-
-        // Checks Central directory.
-        assert_eq!(
-            sections.central_directory_offset + sections.central_directory_size,
-            sections.eocd_offset
-        );
-
-        // Checks EOCD.
-        reader.seek(SeekFrom::Start(sections.eocd_offset as u64)).unwrap();
-        assert_eq!(reader.read_u32::<LittleEndian>().unwrap(), EOCD_SIGNATURE);
-        assert_eq!(
-            reader.metadata().unwrap().len(),
-            (sections.eocd_offset + sections.eocd_size) as u64
-        );
-    }
 }
diff --git a/libs/bssl/error/src/code.rs b/libs/bssl/error/src/code.rs
index 7fb36c4..9b661e9 100644
--- a/libs/bssl/error/src/code.rs
+++ b/libs/bssl/error/src/code.rs
@@ -91,6 +91,12 @@
     InvalidNonce,
 }
 
+impl From<CipherError> for ReasonCode {
+    fn from(e: CipherError) -> ReasonCode {
+        ReasonCode::Cipher(e)
+    }
+}
+
 impl fmt::Display for CipherError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "An error occurred in a Cipher function: {self:?}")
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 80398c0..3766c41 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -68,4 +68,5 @@
     EVP_AEAD_CTX_seal,
     HKDF,
     HMAC,
+    RAND_bytes,
 }
diff --git a/libs/bssl/src/aead.rs b/libs/bssl/src/aead.rs
index a7d03b9..e0c9fbb 100644
--- a/libs/bssl/src/aead.rs
+++ b/libs/bssl/src/aead.rs
@@ -23,8 +23,13 @@
 };
 use core::ptr::NonNull;
 
+/// BoringSSL spec recommends to use 12-byte nonces.
+///
+/// https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html#EVP_aead_aes_256_gcm
+pub const AES_GCM_NONCE_LENGTH: usize = 12;
+
 /// Magic value indicating that the default tag length for an AEAD should be used to
-/// initialize `AeadCtx`.
+/// initialize `AeadContext`.
 const AEAD_DEFAULT_TAG_LENGTH: usize = EVP_AEAD_DEFAULT_TAG_LENGTH as usize;
 
 /// Represents an AEAD algorithm.
@@ -60,12 +65,12 @@
 }
 
 /// Represents an AEAD algorithm configuration.
-pub struct AeadCtx {
+pub struct AeadContext {
     ctx: NonNull<EVP_AEAD_CTX>,
     aead: Aead,
 }
 
-impl Drop for AeadCtx {
+impl Drop for AeadContext {
     fn drop(&mut self) {
         // SAFETY: It is safe because the pointer has been created with `EVP_AEAD_CTX_new`
         // and isn't used after this.
@@ -73,8 +78,8 @@
     }
 }
 
-impl AeadCtx {
-    /// Creates a new `AeadCtx` with the given `Aead` algorithm, `key` and `tag_len`.
+impl AeadContext {
+    /// Creates a new `AeadContext` with the given `Aead` algorithm, `key` and `tag_len`.
     ///
     /// The default tag length will be used if `tag_len` is None.
     pub fn new(aead: Aead, key: &[u8], tag_len: Option<usize>) -> Result<Self> {
@@ -153,7 +158,7 @@
         out.get(0..out_len).ok_or(to_call_failed_error(ApiName::EVP_AEAD_CTX_open))
     }
 
-    /// Returns the `Aead` represented by this `AeadCtx`.
+    /// Returns the `Aead` represented by this `AeadContext`.
     pub fn aead(&self) -> Aead {
         self.aead
     }
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 898e16c..709e8ad 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -25,13 +25,15 @@
 mod err;
 mod hkdf;
 mod hmac;
+mod rand;
 mod util;
 
 pub use bssl_avf_error::{ApiName, CipherError, Error, ReasonCode, Result};
 
-pub use aead::{Aead, AeadCtx};
+pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
 pub use cbb::CbbFixed;
 pub use digest::Digester;
 pub use ec_key::{EcKey, ZVec};
 pub use hkdf::hkdf;
 pub use hmac::hmac_sha256;
+pub use rand::rand_bytes;
diff --git a/libs/bssl/src/rand.rs b/libs/bssl/src/rand.rs
new file mode 100644
index 0000000..9343284
--- /dev/null
+++ b/libs/bssl/src/rand.rs
@@ -0,0 +1,26 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Wrappers of the randon number generations functions in BoringSSL rand.h.
+
+use crate::util::check_int_result;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::RAND_bytes;
+
+/// Fills the given `dest` with random data.
+pub fn rand_bytes(dest: &mut [u8]) -> Result<()> {
+    // SAFETY: This function only writes to the given buffer within its bounds.
+    let ret = unsafe { RAND_bytes(dest.as_mut_ptr(), dest.len()) };
+    check_int_result(ret, ApiName::RAND_bytes)
+}
diff --git a/libs/bssl/tests/aead_test.rs b/libs/bssl/tests/aead_test.rs
index 8ac3f12..8bdb0e7 100644
--- a/libs/bssl/tests/aead_test.rs
+++ b/libs/bssl/tests/aead_test.rs
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bssl_avf::{Aead, AeadCtx, ApiName, CipherError, Error, ReasonCode, Result};
+use bssl_avf::{Aead, AeadContext, ApiName, CipherError, Error, ReasonCode, Result};
 
 /// The following vectors are generated randomly with:
 /// `hexdump -vn32 -e'32/1 "0x%02x, " 1 "\n"' /dev/urandom`
@@ -38,7 +38,7 @@
     let tag_len = None;
 
     let ad = &[];
-    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
     let mut out = vec![0u8; ciphertext.len()];
 
     let plaintext = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE1, ad, &mut out)?;
@@ -50,7 +50,7 @@
 #[test]
 fn aes_256_gcm_fails_to_encrypt_with_invalid_nonce() -> Result<()> {
     let tag_len = None;
-    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
     let nonce = &[];
     let ad = &[];
     let mut out = vec![0u8; MESSAGE.len() + aead_ctx.aead().max_overhead()];
@@ -71,7 +71,7 @@
     let tag_len = None;
 
     let ad = &[];
-    let aead_ctx2 = AeadCtx::new(Aead::aes_256_gcm(), &KEY2, tag_len)?;
+    let aead_ctx2 = AeadContext::new(Aead::aes_256_gcm(), &KEY2, tag_len)?;
     let mut plaintext = vec![0u8; ciphertext.len()];
 
     let err = aead_ctx2.open(&ciphertext, &AES_256_GCM_NONCE1, ad, &mut plaintext).unwrap_err();
@@ -88,7 +88,7 @@
     let tag_len = None;
 
     let ad2 = &[1];
-    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
     let mut plaintext = vec![0u8; ciphertext.len()];
 
     let err = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE1, ad2, &mut plaintext).unwrap_err();
@@ -105,7 +105,7 @@
     let tag_len = None;
 
     let ad = &[];
-    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
     let mut plaintext = vec![0u8; ciphertext.len()];
 
     let err = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE2, ad, &mut plaintext).unwrap_err();
@@ -123,7 +123,7 @@
     let tag_len = None;
 
     let ad = &[];
-    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
     let mut plaintext = vec![0u8; ciphertext.len()];
 
     let err = aead_ctx.open(&ciphertext, &AES_256_GCM_NONCE1, ad, &mut plaintext).unwrap_err();
@@ -136,7 +136,7 @@
 
 fn aes_256_gcm_encrypt(message: &[u8]) -> Result<Vec<u8>> {
     let tag_len = None;
-    let aead_ctx = AeadCtx::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), &KEY1, tag_len)?;
     let mut out = vec![0u8; message.len() + aead_ctx.aead().max_overhead()];
 
     assert_eq!(aead_ctx.aead().nonce_length(), AES_256_GCM_NONCE1.len());
diff --git a/libs/dice/OWNERS b/libs/dice/OWNERS
new file mode 100644
index 0000000..fbc501d
--- /dev/null
+++ b/libs/dice/OWNERS
@@ -0,0 +1 @@
+ascull@google.com
diff --git a/libs/dice/TEST_MAPPING b/libs/dice/TEST_MAPPING
new file mode 100644
index 0000000..2045ba5
--- /dev/null
+++ b/libs/dice/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "postsubmit": [
+    {
+      "name": "libdiced_open_dice.integration_test"
+    },
+    {
+      "name": "libdiced_open_dice_nostd.integration_test"
+    },
+    {
+      "name": "libopen_dice_cbor_bindgen_test"
+    },
+    {
+      "name": "libopen_dice_android_bindgen_test"
+    },
+    {
+      "name": "libdiced_sample_inputs.integration_test"
+    },
+    {
+      "name": "libdiced_sample_inputs_nostd.integration_test"
+    }
+  ]
+}
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
new file mode 100644
index 0000000..646080d
--- /dev/null
+++ b/libs/dice/open_dice/Android.bp
@@ -0,0 +1,258 @@
+package {
+    default_visibility: [":__subpackages__"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libdiced_open_dice_defaults",
+    crate_name: "diced_open_dice",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+}
+
+rust_library_rlib {
+    name: "libdiced_open_dice_nostd",
+    defaults: ["libdiced_open_dice_defaults"],
+    rustlibs: [
+        "libopen_dice_android_bindgen_nostd",
+        "libopen_dice_cbor_bindgen_nostd",
+        "libzeroize_nostd",
+    ],
+    features: [
+        "alloc",
+    ],
+    whole_static_libs: [
+        "libopen_dice_cbor",
+        "libcrypto_baremetal",
+    ],
+    visibility: [
+        "//packages/modules/Virtualization:__subpackages__",
+    ],
+}
+
+rust_library {
+    name: "libdiced_open_dice",
+    defaults: ["libdiced_open_dice_defaults"],
+    vendor_available: true,
+    rustlibs: [
+        "libopen_dice_android_bindgen",
+        "libopen_dice_cbor_bindgen",
+        "libzeroize",
+    ],
+    features: [
+        "alloc",
+        "std",
+    ],
+    shared_libs: [
+        "libcrypto",
+    ],
+    static_libs: [
+        "libopen_dice_cbor",
+    ],
+    whole_static_libs: [
+        "libopen_dice_android",
+    ],
+    visibility: [
+        "//packages/modules/Virtualization:__subpackages__",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+}
+
+rust_defaults {
+    name: "libdiced_open_dice_test_defaults",
+    crate_name: "diced_open_dice_test",
+    srcs: ["tests/*.rs"],
+    test_suites: ["general-tests"],
+}
+
+rust_test {
+    name: "libdiced_open_dice.integration_test",
+    defaults: ["libdiced_open_dice_test_defaults"],
+    rustlibs: [
+        "libdiced_open_dice",
+    ],
+}
+
+rust_test {
+    name: "libdiced_open_dice_nostd.integration_test",
+    defaults: ["libdiced_open_dice_test_defaults"],
+    rustlibs: [
+        "libdiced_open_dice_nostd",
+    ],
+}
+
+rust_defaults {
+    name: "libopen_dice_bindgen_nostd.rust_defaults",
+    bindgen_flags: [
+        "--use-core",
+        "--ctypes-prefix=core::ffi",
+        "--raw-line=#![no_std]",
+    ],
+    no_stdlibs: true,
+    prefer_rlib: true,
+    stdlibs: [
+        "libcore.rust_sysroot",
+        "libcompiler_builtins.rust_sysroot",
+    ],
+    target: {
+        musl: {
+            enabled: false,
+        },
+        glibc: {
+            enabled: false,
+        },
+        darwin: {
+            enabled: false,
+        },
+    },
+}
+
+rust_defaults {
+    name: "libopen_dice.rust_defaults",
+    host_supported: true,
+    vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.compos",
+        "com.android.virt",
+    ],
+}
+
+rust_defaults {
+    name: "libopen_dice_cbor_bindgen.rust_defaults",
+    wrapper_src: "bindgen/dice.h",
+    crate_name: "open_dice_cbor_bindgen",
+    source_stem: "bindings",
+    bindgen_flags: [
+        "--rustified-enum DiceConfigType",
+        "--rustified-enum DiceMode",
+        "--rustified-enum DiceResult",
+
+        // By generating only essential functions, we can make bindings concise and
+        // optimize compilation time.
+        "--allowlist-function=DiceDeriveCdiPrivateKeySeed",
+        "--allowlist-function=DiceDeriveCdiCertificateId",
+        "--allowlist-function=DiceMainFlow",
+        "--allowlist-function=DiceHash",
+        "--allowlist-function=DiceKdf",
+        "--allowlist-function=DiceKeypairFromSeed",
+        "--allowlist-function=DiceSign",
+        "--allowlist-function=DiceVerify",
+        "--allowlist-function=DiceGenerateCertificate",
+
+        // We also need some constants in addition to the functions.
+        "--allowlist-var=DICE_CDI_SIZE",
+        "--allowlist-var=DICE_HASH_SIZE",
+        "--allowlist-var=DICE_HIDDEN_SIZE",
+        "--allowlist-var=DICE_INLINE_CONFIG_SIZE",
+        "--allowlist-var=DICE_PRIVATE_KEY_SEED_SIZE",
+        "--allowlist-var=DICE_ID_SIZE",
+        "--allowlist-var=DICE_PUBLIC_KEY_SIZE",
+        "--allowlist-var=DICE_PRIVATE_KEY_SIZE",
+        "--allowlist-var=DICE_SIGNATURE_SIZE",
+    ],
+}
+
+rust_bindgen {
+    name: "libopen_dice_cbor_bindgen",
+    defaults: [
+        "libopen_dice.rust_defaults",
+        "libopen_dice_cbor_bindgen.rust_defaults",
+    ],
+    whole_static_libs: ["libopen_dice_cbor"],
+}
+
+rust_bindgen {
+    name: "libopen_dice_cbor_bindgen_nostd",
+    defaults: [
+        "libopen_dice_cbor_bindgen.rust_defaults",
+        "libopen_dice_bindgen_nostd.rust_defaults",
+    ],
+    whole_static_libs: ["libopen_dice_cbor_baremetal"],
+}
+
+rust_defaults {
+    name: "libopen_dice_android_bindgen.rust_defaults",
+    wrapper_src: "bindgen/android.h",
+    crate_name: "open_dice_android_bindgen",
+    source_stem: "bindings",
+    bindgen_flags: [
+        // By generating only essential functions, we can make bindings concise and
+        // optimize compilation time.
+        "--allowlist-function=DiceAndroidFormatConfigDescriptor",
+        "--allowlist-function=DiceAndroidMainFlow",
+        "--allowlist-function=DiceAndroidHandoverMainFlow",
+        "--allowlist-function=DiceAndroidHandoverParse",
+
+        // We also need some constants in addition to the functions.
+        "--allowlist-var=DICE_ANDROID_CONFIG_.*",
+
+        // Prevent DiceInputValues from being generated a second time and
+        // import it instead from open_dice_cbor_bindgen.
+        "--blocklist-type=DiceInputValues_",
+        "--blocklist-type=DiceInputValues",
+        "--raw-line",
+        "pub use open_dice_cbor_bindgen::DiceInputValues;",
+
+        // Prevent DiceResult from being generated a second time and
+        // import it instead from open_dice_cbor_bindgen.
+        "--blocklist-type=DiceResult",
+        "--raw-line",
+        "pub use open_dice_cbor_bindgen::DiceResult;",
+    ],
+
+}
+
+rust_bindgen {
+    name: "libopen_dice_android_bindgen",
+    defaults: [
+        "libopen_dice.rust_defaults",
+        "libopen_dice_android_bindgen.rust_defaults",
+    ],
+    rustlibs: [
+        "libopen_dice_cbor_bindgen",
+    ],
+    whole_static_libs: ["libopen_dice_android"],
+}
+
+rust_bindgen {
+    name: "libopen_dice_android_bindgen_nostd",
+    defaults: [
+        "libopen_dice_android_bindgen.rust_defaults",
+        "libopen_dice_bindgen_nostd.rust_defaults",
+    ],
+    rustlibs: [
+        "libopen_dice_cbor_bindgen_nostd",
+    ],
+    whole_static_libs: ["libopen_dice_android_baremetal"],
+}
+
+rust_test {
+    name: "libopen_dice_cbor_bindgen_test",
+    srcs: [
+        ":libopen_dice_cbor_bindgen",
+    ],
+    crate_name: "open_dice_cbor_bindgen_test",
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    clippy_lints: "none",
+    lints: "none",
+}
+
+rust_test {
+    name: "libopen_dice_android_bindgen_test",
+    srcs: [
+        ":libopen_dice_android_bindgen",
+    ],
+    crate_name: "open_dice_android_bindgen_test",
+    rustlibs: [
+        "libopen_dice_cbor_bindgen",
+    ],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    clippy_lints: "none",
+    lints: "none",
+}
diff --git a/libs/dice/open_dice/bindgen/android.h b/libs/dice/open_dice/bindgen/android.h
new file mode 100644
index 0000000..18f6476
--- /dev/null
+++ b/libs/dice/open_dice/bindgen/android.h
@@ -0,0 +1,17 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#pragma once
+
+#include <dice/android.h>
diff --git a/libs/dice/open_dice/bindgen/dice.h b/libs/dice/open_dice/bindgen/dice.h
new file mode 100644
index 0000000..47fe911
--- /dev/null
+++ b/libs/dice/open_dice/bindgen/dice.h
@@ -0,0 +1,18 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#pragma once
+
+#include <dice/dice.h>
+#include <dice/ops.h>
diff --git a/libs/dice/open_dice/src/bcc.rs b/libs/dice/open_dice/src/bcc.rs
new file mode 100644
index 0000000..199e1a9
--- /dev/null
+++ b/libs/dice/open_dice/src/bcc.rs
@@ -0,0 +1,223 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! This module mirrors the content in open-dice/include/dice/android.h
+
+use crate::dice::{Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE};
+use crate::error::{check_result, DiceError, Result};
+use open_dice_android_bindgen::{
+    DiceAndroidConfigValues, DiceAndroidFormatConfigDescriptor, DiceAndroidHandoverMainFlow,
+    DiceAndroidHandoverParse, DiceAndroidMainFlow, DICE_ANDROID_CONFIG_COMPONENT_NAME,
+    DICE_ANDROID_CONFIG_COMPONENT_VERSION, DICE_ANDROID_CONFIG_RESETTABLE,
+    DICE_ANDROID_CONFIG_SECURITY_VERSION,
+};
+use std::{ffi::CStr, ptr};
+
+/// Contains the input values used to construct the Android Profile for DICE
+/// configuration descriptor.
+#[derive(Default, Debug)]
+pub struct DiceConfigValues<'a> {
+    /// Name of the component.
+    pub component_name: Option<&'a CStr>,
+    /// Version of the component.
+    pub component_version: Option<u64>,
+    /// Whether the key changes on factory reset.
+    pub resettable: bool,
+    /// Monotonically increasing version of the component.
+    pub security_version: Option<u64>,
+}
+
+/// Formats a configuration descriptor following the Android Profile for DICE specification.
+/// See https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md.
+pub fn bcc_format_config_descriptor(values: &DiceConfigValues, buffer: &mut [u8]) -> Result<usize> {
+    let mut configs = 0;
+
+    let component_name = values.component_name.map_or(ptr::null(), |name| {
+        configs |= DICE_ANDROID_CONFIG_COMPONENT_NAME;
+        name.as_ptr()
+    });
+    let component_version = values.component_version.map_or(0, |version| {
+        configs |= DICE_ANDROID_CONFIG_COMPONENT_VERSION;
+        version
+    });
+    if values.resettable {
+        configs |= DICE_ANDROID_CONFIG_RESETTABLE;
+    }
+    let security_version = values.security_version.map_or(0, |version| {
+        configs |= DICE_ANDROID_CONFIG_SECURITY_VERSION;
+        version
+    });
+
+    let values =
+        DiceAndroidConfigValues { configs, component_name, component_version, security_version };
+
+    let mut buffer_size = 0;
+    check_result(
+        // SAFETY: The function writes to the buffer, within the given bounds, and only reads the
+        // input values. It writes its result to buffer_size.
+        unsafe {
+            DiceAndroidFormatConfigDescriptor(
+                &values,
+                buffer.len(),
+                buffer.as_mut_ptr(),
+                &mut buffer_size,
+            )
+        },
+        buffer_size,
+    )?;
+    Ok(buffer_size)
+}
+
+/// Executes the main Android DICE flow.
+///
+/// Given a full set of input values along with the current DICE chain and CDI values,
+/// computes the next CDI values and matching updated DICE chain.
+pub fn bcc_main_flow(
+    current_cdi_attest: &Cdi,
+    current_cdi_seal: &Cdi,
+    current_chain: &[u8],
+    input_values: &InputValues,
+    next_cdi_values: &mut CdiValues,
+    next_chain: &mut [u8],
+) -> Result<usize> {
+    let mut next_chain_size = 0;
+    check_result(
+        // SAFETY: `DiceAndroidMainFlow` only reads the `current_chain` and CDI values and writes
+        // to `next_chain` and next CDI values within its bounds. It also reads `input_values` as a
+        // constant input and doesn't store any pointer.
+        // The first argument can be null and is not used in the current implementation.
+        unsafe {
+            DiceAndroidMainFlow(
+                ptr::null_mut(), // context
+                current_cdi_attest.as_ptr(),
+                current_cdi_seal.as_ptr(),
+                current_chain.as_ptr(),
+                current_chain.len(),
+                input_values.as_ptr(),
+                next_chain.len(),
+                next_chain.as_mut_ptr(),
+                &mut next_chain_size,
+                next_cdi_values.cdi_attest.as_mut_ptr(),
+                next_cdi_values.cdi_seal.as_mut_ptr(),
+            )
+        },
+        next_chain_size,
+    )?;
+    Ok(next_chain_size)
+}
+
+/// Executes the main Android DICE handover flow.
+///
+/// A handover combines the DICE chain and CDIs in a single CBOR object.
+/// This function takes the current boot stage's handover bundle and produces a
+/// bundle for the next stage.
+pub fn bcc_handover_main_flow(
+    current_handover: &[u8],
+    input_values: &InputValues,
+    next_handover: &mut [u8],
+) -> Result<usize> {
+    let mut next_handover_size = 0;
+    check_result(
+        // SAFETY: The function only reads `current_handover` and writes to `next_handover`
+        // within its bounds,
+        // It also reads `input_values` as a constant input and doesn't store any pointer.
+        // The first argument can be null and is not used in the current implementation.
+        unsafe {
+            DiceAndroidHandoverMainFlow(
+                ptr::null_mut(), // context
+                current_handover.as_ptr(),
+                current_handover.len(),
+                input_values.as_ptr(),
+                next_handover.len(),
+                next_handover.as_mut_ptr(),
+                &mut next_handover_size,
+            )
+        },
+        next_handover_size,
+    )?;
+
+    Ok(next_handover_size)
+}
+
+/// An Android DICE handover object combines the DICE chain and CDIs in a single CBOR object.
+/// This struct is used as return of the function `android_dice_handover_parse`, its lifetime is
+/// tied to the lifetime of the raw handover slice.
+#[derive(Debug)]
+pub struct BccHandover<'a> {
+    /// Attestation CDI.
+    cdi_attest: &'a [u8; CDI_SIZE],
+    /// Sealing CDI.
+    cdi_seal: &'a [u8; CDI_SIZE],
+    /// DICE chain.
+    bcc: Option<&'a [u8]>,
+}
+
+impl<'a> DiceArtifacts for BccHandover<'a> {
+    fn cdi_attest(&self) -> &[u8; CDI_SIZE] {
+        self.cdi_attest
+    }
+
+    fn cdi_seal(&self) -> &[u8; CDI_SIZE] {
+        self.cdi_seal
+    }
+
+    fn bcc(&self) -> Option<&[u8]> {
+        self.bcc
+    }
+}
+
+/// This function parses the `handover` to extracts the DICE chain and CDIs.
+/// The lifetime of the returned `DiceAndroidHandover` is tied to the given `handover` slice.
+pub fn bcc_handover_parse(handover: &[u8]) -> Result<BccHandover> {
+    let mut cdi_attest: *const u8 = ptr::null();
+    let mut cdi_seal: *const u8 = ptr::null();
+    let mut chain: *const u8 = ptr::null();
+    let mut chain_size = 0;
+    check_result(
+        // SAFETY: The `handover` is only read and never stored and the returned pointers should
+        // all point within the address range of the `handover` or be NULL.
+        unsafe {
+            DiceAndroidHandoverParse(
+                handover.as_ptr(),
+                handover.len(),
+                &mut cdi_attest,
+                &mut cdi_seal,
+                &mut chain,
+                &mut chain_size,
+            )
+        },
+        chain_size,
+    )?;
+    let cdi_attest = sub_slice(handover, cdi_attest, CDI_SIZE)?;
+    let cdi_seal = sub_slice(handover, cdi_seal, CDI_SIZE)?;
+    let bcc = sub_slice(handover, chain, chain_size).ok();
+    Ok(BccHandover {
+        cdi_attest: cdi_attest.try_into().map_err(|_| DiceError::PlatformError)?,
+        cdi_seal: cdi_seal.try_into().map_err(|_| DiceError::PlatformError)?,
+        bcc,
+    })
+}
+
+/// Gets a slice the `addr` points to and of length `len`.
+/// The slice should be contained in the buffer.
+fn sub_slice(buffer: &[u8], addr: *const u8, len: usize) -> Result<&[u8]> {
+    if addr.is_null() || !buffer.as_ptr_range().contains(&addr) {
+        return Err(DiceError::PlatformError);
+    }
+    // SAFETY: This is safe because addr is not null and is within the range of the buffer.
+    let start: usize = unsafe {
+        addr.offset_from(buffer.as_ptr()).try_into().map_err(|_| DiceError::PlatformError)?
+    };
+    start.checked_add(len).and_then(|end| buffer.get(start..end)).ok_or(DiceError::PlatformError)
+}
diff --git a/libs/dice/open_dice/src/dice.rs b/libs/dice/open_dice/src/dice.rs
new file mode 100644
index 0000000..e42e373
--- /dev/null
+++ b/libs/dice/open_dice/src/dice.rs
@@ -0,0 +1,287 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Structs and functions about the types used in DICE.
+//! This module mirrors the content in open-dice/include/dice/dice.h
+
+use crate::error::{check_result, Result};
+pub use open_dice_cbor_bindgen::DiceMode;
+use open_dice_cbor_bindgen::{
+    DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, DiceInputValues,
+    DiceMainFlow, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, DICE_ID_SIZE,
+    DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE,
+    DICE_PUBLIC_KEY_SIZE, DICE_SIGNATURE_SIZE,
+};
+use std::{marker::PhantomData, ptr};
+use zeroize::{Zeroize, ZeroizeOnDrop};
+
+/// The size of a DICE hash.
+pub const HASH_SIZE: usize = DICE_HASH_SIZE as usize;
+/// The size of the DICE hidden value.
+pub const HIDDEN_SIZE: usize = DICE_HIDDEN_SIZE as usize;
+/// The size of a DICE inline config.
+const INLINE_CONFIG_SIZE: usize = DICE_INLINE_CONFIG_SIZE as usize;
+/// The size of a CDI.
+pub const CDI_SIZE: usize = DICE_CDI_SIZE as usize;
+/// The size of a private key seed.
+pub const PRIVATE_KEY_SEED_SIZE: usize = DICE_PRIVATE_KEY_SEED_SIZE as usize;
+/// The size of a private key.
+pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_SIZE as usize;
+/// The size of a public key.
+pub const PUBLIC_KEY_SIZE: usize = DICE_PUBLIC_KEY_SIZE as usize;
+/// The size of a signature.
+pub const SIGNATURE_SIZE: usize = DICE_SIGNATURE_SIZE as usize;
+/// The size of an ID.
+pub const ID_SIZE: usize = DICE_ID_SIZE as usize;
+
+/// Array type of hashes used by DICE.
+pub type Hash = [u8; HASH_SIZE];
+/// Array type of additional input.
+pub type Hidden = [u8; HIDDEN_SIZE];
+/// Array type of inline configuration values.
+pub type InlineConfig = [u8; INLINE_CONFIG_SIZE];
+/// Array type of CDIs.
+pub type Cdi = [u8; CDI_SIZE];
+/// Array type of the public key.
+pub type PublicKey = [u8; PUBLIC_KEY_SIZE];
+/// Array type of the signature.
+pub type Signature = [u8; SIGNATURE_SIZE];
+/// Array type of DICE ID.
+pub type DiceId = [u8; ID_SIZE];
+
+/// A trait for types that represent Dice artifacts, which include:
+///
+/// - Attestation CDI
+/// - Sealing CDI
+/// - Boot Certificate Chain
+///
+/// Types that implement this trait provide an access these artifacts.
+pub trait DiceArtifacts {
+    /// Returns a reference to the attestation CDI.
+    fn cdi_attest(&self) -> &[u8; CDI_SIZE];
+
+    /// Returns a reference to the sealing CDI.
+    fn cdi_seal(&self) -> &[u8; CDI_SIZE];
+
+    /// Returns a reference to the Boot Certificate Chain, if present.
+    fn bcc(&self) -> Option<&[u8]>;
+}
+
+/// TODO(b/268587826): Clean up the memory cache after zeroing out the memory
+/// for sensitive data like CDI values and private key.
+/// CDI Values.
+#[derive(Debug, Zeroize, ZeroizeOnDrop, Default)]
+pub struct CdiValues {
+    /// Attestation CDI.
+    pub cdi_attest: [u8; CDI_SIZE],
+    /// Sealing CDI.
+    pub cdi_seal: [u8; CDI_SIZE],
+}
+
+/// Private key seed. The data is zeroed out when the struct is dropped.
+#[derive(Zeroize, ZeroizeOnDrop, Default)]
+pub struct PrivateKeySeed([u8; PRIVATE_KEY_SEED_SIZE]);
+
+impl PrivateKeySeed {
+    /// Returns an array reference of the private key seed.
+    pub fn as_array(&self) -> &[u8; PRIVATE_KEY_SEED_SIZE] {
+        &self.0
+    }
+
+    /// Returns a mutable pointer to the slice buffer of the private key seed.
+    pub fn as_mut_ptr(&mut self) -> *mut u8 {
+        self.0.as_mut_ptr()
+    }
+}
+
+/// Private key. The data is zeroed out when the struct is dropped.
+#[derive(Zeroize, ZeroizeOnDrop)]
+pub struct PrivateKey([u8; PRIVATE_KEY_SIZE]);
+
+impl Default for PrivateKey {
+    /// Creates a new `PrivateKey` instance with all bytes set to 0.
+    ///
+    /// Since the size of the private key array is too large to be initialized
+    /// with a default value, this implementation sets all the bytes in the array
+    /// to 0 using the `[0u8; PRIVATE_KEY_SIZE]` syntax.
+    fn default() -> Self {
+        Self([0u8; PRIVATE_KEY_SIZE])
+    }
+}
+
+impl PrivateKey {
+    /// Returns an array reference of the private key.
+    pub fn as_array(&self) -> &[u8; PRIVATE_KEY_SIZE] {
+        &self.0
+    }
+
+    /// Returns a mutable pointer to the slice buffer of the private key.
+    pub fn as_mut_ptr(&mut self) -> *mut u8 {
+        self.0.as_mut_ptr()
+    }
+}
+
+/// Configuration descriptor for DICE input values.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Config<'a> {
+    /// Reference to an inline descriptor.
+    Inline(&'a InlineConfig),
+    /// Reference to a free form descriptor that will be hashed by the implementation.
+    Descriptor(&'a [u8]),
+}
+
+impl Config<'_> {
+    fn dice_config_type(&self) -> DiceConfigType {
+        match self {
+            Self::Inline(_) => DiceConfigType::kDiceConfigTypeInline,
+            Self::Descriptor(_) => DiceConfigType::kDiceConfigTypeDescriptor,
+        }
+    }
+
+    fn inline_config(&self) -> InlineConfig {
+        match self {
+            Self::Inline(inline) => **inline,
+            Self::Descriptor(_) => [0u8; INLINE_CONFIG_SIZE],
+        }
+    }
+
+    fn descriptor_ptr(&self) -> *const u8 {
+        match self {
+            Self::Descriptor(descriptor) => descriptor.as_ptr(),
+            _ => ptr::null(),
+        }
+    }
+
+    fn descriptor_size(&self) -> usize {
+        match self {
+            Self::Descriptor(descriptor) => descriptor.len(),
+            _ => 0,
+        }
+    }
+}
+
+/// Wrap of `DiceInputValues`.
+#[derive(Clone, Debug)]
+pub struct InputValues<'a> {
+    dice_inputs: DiceInputValues,
+    // DiceInputValues contains a pointer to the separate config descriptor, which must therefore
+    // outlive it. Make sure the borrow checker can enforce that.
+    config_descriptor: PhantomData<&'a [u8]>,
+}
+
+impl<'a> InputValues<'a> {
+    /// Creates a new `InputValues`.
+    pub fn new(
+        code_hash: Hash,
+        config: Config<'a>,
+        authority_hash: Hash,
+        mode: DiceMode,
+        hidden: Hidden,
+    ) -> Self {
+        Self {
+            dice_inputs: DiceInputValues {
+                code_hash,
+                code_descriptor: ptr::null(),
+                code_descriptor_size: 0,
+                config_type: config.dice_config_type(),
+                config_value: config.inline_config(),
+                config_descriptor: config.descriptor_ptr(),
+                config_descriptor_size: config.descriptor_size(),
+                authority_hash,
+                authority_descriptor: ptr::null(),
+                authority_descriptor_size: 0,
+                mode,
+                hidden,
+            },
+            config_descriptor: PhantomData,
+        }
+    }
+
+    /// Returns a raw pointer to the wrapped `DiceInputValues`.
+    pub fn as_ptr(&self) -> *const DiceInputValues {
+        &self.dice_inputs as *const DiceInputValues
+    }
+}
+
+/// Derives a CDI private key seed from a `cdi_attest` value.
+pub fn derive_cdi_private_key_seed(cdi_attest: &Cdi) -> Result<PrivateKeySeed> {
+    let mut seed = PrivateKeySeed::default();
+    check_result(
+        // SAFETY: The function writes to the buffer within the given bounds, and only reads the
+        // input values. The first argument context is not used in this function.
+        unsafe {
+            DiceDeriveCdiPrivateKeySeed(
+                ptr::null_mut(), // context
+                cdi_attest.as_ptr(),
+                seed.as_mut_ptr(),
+            )
+        },
+        seed.0.len(),
+    )?;
+    Ok(seed)
+}
+
+/// Derives an ID from the given `cdi_public_key` value.
+pub fn derive_cdi_certificate_id(cdi_public_key: &[u8]) -> Result<DiceId> {
+    let mut id = [0u8; ID_SIZE];
+    check_result(
+        // SAFETY: The function writes to the buffer within the given bounds, and only reads the
+        // input values. The first argument context is not used in this function.
+        unsafe {
+            DiceDeriveCdiCertificateId(
+                ptr::null_mut(), // context
+                cdi_public_key.as_ptr(),
+                cdi_public_key.len(),
+                id.as_mut_ptr(),
+            )
+        },
+        id.len(),
+    )?;
+    Ok(id)
+}
+
+/// Executes the main DICE flow.
+///
+/// Given a full set of input values and the current CDI values, computes the
+/// next CDI values and a matching certificate.
+/// Returns the actual size of the next CDI certificate.
+pub fn dice_main_flow(
+    current_cdi_attest: &Cdi,
+    current_cdi_seal: &Cdi,
+    input_values: &InputValues,
+    next_cdi_certificate: &mut [u8],
+    next_cdi_values: &mut CdiValues,
+) -> Result<usize> {
+    let mut next_cdi_certificate_actual_size = 0;
+    check_result(
+        // SAFETY: The function only reads the current CDI values and inputs and writes
+        // to `next_cdi_certificate` and next CDI values within its bounds.
+        // The first argument can be null and is not used in the current implementation.
+        unsafe {
+            DiceMainFlow(
+                ptr::null_mut(), // context
+                current_cdi_attest.as_ptr(),
+                current_cdi_seal.as_ptr(),
+                input_values.as_ptr(),
+                next_cdi_certificate.len(),
+                next_cdi_certificate.as_mut_ptr(),
+                &mut next_cdi_certificate_actual_size,
+                next_cdi_values.cdi_attest.as_mut_ptr(),
+                next_cdi_values.cdi_seal.as_mut_ptr(),
+            )
+        },
+        next_cdi_certificate_actual_size,
+    )?;
+    Ok(next_cdi_certificate_actual_size)
+}
diff --git a/libs/dice/open_dice/src/error.rs b/libs/dice/open_dice/src/error.rs
new file mode 100644
index 0000000..53ffd2d
--- /dev/null
+++ b/libs/dice/open_dice/src/error.rs
@@ -0,0 +1,63 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Errors and relating functions thrown in this library.
+
+use open_dice_cbor_bindgen::DiceResult;
+use std::{fmt, result};
+
+#[cfg(feature = "std")]
+use std::error::Error;
+
+/// Error type used by DICE.
+#[derive(Debug)]
+pub enum DiceError {
+    /// Provided input was invalid.
+    InvalidInput,
+    /// Provided buffer was too small.
+    BufferTooSmall(usize),
+    /// Platform error.
+    PlatformError,
+}
+
+/// This makes `DiceError` accepted by anyhow.
+#[cfg(feature = "std")]
+impl Error for DiceError {}
+
+impl fmt::Display for DiceError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::InvalidInput => write!(f, "invalid input"),
+            Self::BufferTooSmall(buffer_required_size) => {
+                write!(f, "buffer too small. Required {buffer_required_size} bytes.")
+            }
+            Self::PlatformError => write!(f, "platform error"),
+        }
+    }
+}
+
+/// DICE result type.
+pub type Result<T> = result::Result<T, DiceError>;
+
+/// Checks the given `DiceResult`. Returns an error if it's not OK.
+pub(crate) fn check_result(result: DiceResult, buffer_required_size: usize) -> Result<()> {
+    match result {
+        DiceResult::kDiceResultOk => Ok(()),
+        DiceResult::kDiceResultInvalidInput => Err(DiceError::InvalidInput),
+        DiceResult::kDiceResultBufferTooSmall => {
+            Err(DiceError::BufferTooSmall(buffer_required_size))
+        }
+        DiceResult::kDiceResultPlatformError => Err(DiceError::PlatformError),
+    }
+}
diff --git a/libs/dice/open_dice/src/lib.rs b/libs/dice/open_dice/src/lib.rs
new file mode 100644
index 0000000..d0004b1
--- /dev/null
+++ b/libs/dice/open_dice/src/lib.rs
@@ -0,0 +1,50 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Implements safe wrappers around the public API of libopen-dice for
+//! both std and nostd usages.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+#[cfg(not(feature = "std"))]
+extern crate core as std;
+
+mod bcc;
+mod dice;
+mod error;
+mod ops;
+#[cfg(feature = "alloc")]
+mod retry;
+
+pub use bcc::{
+    bcc_format_config_descriptor, bcc_handover_main_flow, bcc_handover_parse, bcc_main_flow,
+    BccHandover, DiceConfigValues,
+};
+pub use dice::{
+    derive_cdi_certificate_id, derive_cdi_private_key_seed, dice_main_flow, Cdi, CdiValues, Config,
+    DiceArtifacts, DiceMode, Hash, Hidden, InlineConfig, InputValues, PrivateKey, PrivateKeySeed,
+    PublicKey, Signature, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE,
+};
+pub use error::{DiceError, Result};
+pub use ops::{
+    derive_cdi_leaf_priv, generate_certificate, hash, kdf, keypair_from_seed, sign, verify,
+};
+#[cfg(feature = "alloc")]
+pub use retry::{
+    retry_bcc_format_config_descriptor, retry_bcc_main_flow, retry_dice_main_flow,
+    retry_generate_certificate, OwnedDiceArtifacts,
+};
diff --git a/libs/dice/open_dice/src/ops.rs b/libs/dice/open_dice/src/ops.rs
new file mode 100644
index 0000000..7174d3e
--- /dev/null
+++ b/libs/dice/open_dice/src/ops.rs
@@ -0,0 +1,173 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! This module mirrors the content in open-dice/include/dice/ops.h
+//! It contains the set of functions that implement various operations that the
+//! main DICE functions depend on.
+
+use crate::dice::{
+    derive_cdi_private_key_seed, DiceArtifacts, Hash, InputValues, PrivateKey, PublicKey,
+    Signature, HASH_SIZE, PRIVATE_KEY_SEED_SIZE, PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE, SIGNATURE_SIZE,
+};
+use crate::error::{check_result, Result};
+use open_dice_cbor_bindgen::{
+    DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed, DiceSign, DiceVerify,
+};
+use std::ptr;
+
+/// Hashes the provided input using DICE's hash function `DiceHash`.
+pub fn hash(input: &[u8]) -> Result<Hash> {
+    let mut output: Hash = [0; HASH_SIZE];
+    check_result(
+        // SAFETY: DiceHash takes a sized input buffer and writes to a constant-sized output
+        // buffer. The first argument context is not used in this function.
+        unsafe {
+            DiceHash(
+                ptr::null_mut(), // context
+                input.as_ptr(),
+                input.len(),
+                output.as_mut_ptr(),
+            )
+        },
+        output.len(),
+    )?;
+    Ok(output)
+}
+
+/// An implementation of HKDF-SHA512. Derives a key of `derived_key.len()` bytes from `ikm`, `salt`,
+/// and `info`. The derived key is written to the `derived_key`.
+pub fn kdf(ikm: &[u8], salt: &[u8], info: &[u8], derived_key: &mut [u8]) -> Result<()> {
+    check_result(
+        // SAFETY: The function writes to the `derived_key`, within the given bounds, and only
+        // reads the input values. The first argument context is not used in this function.
+        unsafe {
+            DiceKdf(
+                ptr::null_mut(), // context
+                derived_key.len(),
+                ikm.as_ptr(),
+                ikm.len(),
+                salt.as_ptr(),
+                salt.len(),
+                info.as_ptr(),
+                info.len(),
+                derived_key.as_mut_ptr(),
+            )
+        },
+        derived_key.len(),
+    )
+}
+
+/// Deterministically generates a public and private key pair from `seed`.
+/// Since this is deterministic, `seed` is as sensitive as a private key and can
+/// be used directly as the private key.
+pub fn keypair_from_seed(seed: &[u8; PRIVATE_KEY_SEED_SIZE]) -> Result<(PublicKey, PrivateKey)> {
+    let mut public_key = [0u8; PUBLIC_KEY_SIZE];
+    let mut private_key = PrivateKey::default();
+    check_result(
+        // SAFETY: The function writes to the `public_key` and `private_key` within the given
+        // bounds, and only reads the `seed`. The first argument context is not used in this
+        // function.
+        unsafe {
+            DiceKeypairFromSeed(
+                ptr::null_mut(), // context
+                seed.as_ptr(),
+                public_key.as_mut_ptr(),
+                private_key.as_mut_ptr(),
+            )
+        },
+        public_key.len(),
+    )?;
+    Ok((public_key, private_key))
+}
+
+/// Derives the CDI_Leaf_Priv from the provided `dice_artifacts`.
+///
+/// The corresponding public key is included in the leaf certificate of the DICE chain
+/// contained in `dice_artifacts`.
+pub fn derive_cdi_leaf_priv(dice_artifacts: &dyn DiceArtifacts) -> Result<PrivateKey> {
+    let cdi_priv_key_seed = derive_cdi_private_key_seed(dice_artifacts.cdi_attest())?;
+    let (_, private_key) = keypair_from_seed(cdi_priv_key_seed.as_array())?;
+    Ok(private_key)
+}
+
+/// Signs the `message` with the give `private_key` using `DiceSign`.
+pub fn sign(message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Signature> {
+    let mut signature = [0u8; SIGNATURE_SIZE];
+    check_result(
+        // SAFETY: The function writes to the `signature` within the given bounds, and only reads
+        // the message and the private key. The first argument context is not used in this
+        // function.
+        unsafe {
+            DiceSign(
+                ptr::null_mut(), // context
+                message.as_ptr(),
+                message.len(),
+                private_key.as_ptr(),
+                signature.as_mut_ptr(),
+            )
+        },
+        signature.len(),
+    )?;
+    Ok(signature)
+}
+
+/// Verifies the `signature` of the `message` with the given `public_key` using `DiceVerify`.
+pub fn verify(message: &[u8], signature: &Signature, public_key: &PublicKey) -> Result<()> {
+    check_result(
+        // SAFETY: only reads the messages, signature and public key as constant values.
+        // The first argument context is not used in this function.
+        unsafe {
+            DiceVerify(
+                ptr::null_mut(), // context
+                message.as_ptr(),
+                message.len(),
+                signature.as_ptr(),
+                public_key.as_ptr(),
+            )
+        },
+        0,
+    )
+}
+
+/// Generates an X.509 certificate from the given `subject_private_key_seed` and
+/// `input_values`, and signed by `authority_private_key_seed`.
+/// The subject private key seed is supplied here so the implementation can choose
+/// between asymmetric mechanisms, for example ECDSA vs Ed25519.
+/// Returns the actual size of the generated certificate.
+pub fn generate_certificate(
+    subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE],
+    authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE],
+    input_values: &InputValues,
+    certificate: &mut [u8],
+) -> Result<usize> {
+    let mut certificate_actual_size = 0;
+    check_result(
+        // SAFETY: The function writes to the `certificate` within the given bounds, and only reads
+        // the input values and the key seeds. The first argument context is not used in this
+        // function.
+        unsafe {
+            DiceGenerateCertificate(
+                ptr::null_mut(), // context
+                subject_private_key_seed.as_ptr(),
+                authority_private_key_seed.as_ptr(),
+                input_values.as_ptr(),
+                certificate.len(),
+                certificate.as_mut_ptr(),
+                &mut certificate_actual_size,
+            )
+        },
+        certificate_actual_size,
+    )?;
+    Ok(certificate_actual_size)
+}
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
new file mode 100644
index 0000000..a6303bd
--- /dev/null
+++ b/libs/dice/open_dice/src/retry.rs
@@ -0,0 +1,141 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! This module implements a retry version for multiple DICE functions that
+//! require preallocated output buffer. As the retry functions require
+//! memory allocation on heap, currently we only expose these functions in
+//! std environment.
+
+use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow, DiceConfigValues};
+use crate::dice::{
+    dice_main_flow, Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE, PRIVATE_KEY_SEED_SIZE,
+};
+use crate::error::{DiceError, Result};
+use crate::ops::generate_certificate;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+/// Artifacts stores a set of dice artifacts comprising CDI_ATTEST, CDI_SEAL,
+/// and the BCC formatted attestation certificate chain.
+/// As we align with the DICE standards today, this is the certificate chain
+/// is also called DICE certificate chain.
+#[derive(Debug)]
+pub struct OwnedDiceArtifacts {
+    /// CDI Values.
+    cdi_values: CdiValues,
+    /// Boot Certificate Chain.
+    bcc: Vec<u8>,
+}
+
+impl DiceArtifacts for OwnedDiceArtifacts {
+    fn cdi_attest(&self) -> &[u8; CDI_SIZE] {
+        &self.cdi_values.cdi_attest
+    }
+
+    fn cdi_seal(&self) -> &[u8; CDI_SIZE] {
+        &self.cdi_values.cdi_seal
+    }
+
+    fn bcc(&self) -> Option<&[u8]> {
+        Some(&self.bcc)
+    }
+}
+
+/// Retries the given function with bigger measured buffer size.
+fn retry_with_measured_buffer<F>(mut f: F) -> Result<Vec<u8>>
+where
+    F: FnMut(&mut Vec<u8>) -> Result<usize>,
+{
+    let mut buffer = Vec::new();
+    match f(&mut buffer) {
+        Err(DiceError::BufferTooSmall(actual_size)) => {
+            buffer.resize(actual_size, 0);
+            f(&mut buffer)?;
+        }
+        Err(e) => return Err(e),
+        Ok(_) => {}
+    };
+    Ok(buffer)
+}
+
+/// Formats a configuration descriptor following the BCC's specification.
+pub fn retry_bcc_format_config_descriptor(values: &DiceConfigValues) -> Result<Vec<u8>> {
+    retry_with_measured_buffer(|buffer| bcc_format_config_descriptor(values, buffer))
+}
+
+/// Executes the main BCC flow.
+///
+/// Given a full set of input values along with the current BCC and CDI values,
+/// computes the next CDI values and matching updated BCC.
+pub fn retry_bcc_main_flow(
+    current_cdi_attest: &Cdi,
+    current_cdi_seal: &Cdi,
+    bcc: &[u8],
+    input_values: &InputValues,
+) -> Result<OwnedDiceArtifacts> {
+    let mut next_cdi_values = CdiValues::default();
+    let next_bcc = retry_with_measured_buffer(|next_bcc| {
+        bcc_main_flow(
+            current_cdi_attest,
+            current_cdi_seal,
+            bcc,
+            input_values,
+            &mut next_cdi_values,
+            next_bcc,
+        )
+    })?;
+    Ok(OwnedDiceArtifacts { cdi_values: next_cdi_values, bcc: next_bcc })
+}
+
+/// Executes the main DICE flow.
+///
+/// Given a full set of input values and the current CDI values, computes the
+/// next CDI values and a matching certificate.
+pub fn retry_dice_main_flow(
+    current_cdi_attest: &Cdi,
+    current_cdi_seal: &Cdi,
+    input_values: &InputValues,
+) -> Result<(CdiValues, Vec<u8>)> {
+    let mut next_cdi_values = CdiValues::default();
+    let next_cdi_certificate = retry_with_measured_buffer(|next_cdi_certificate| {
+        dice_main_flow(
+            current_cdi_attest,
+            current_cdi_seal,
+            input_values,
+            next_cdi_certificate,
+            &mut next_cdi_values,
+        )
+    })?;
+    Ok((next_cdi_values, next_cdi_certificate))
+}
+
+/// Generates an X.509 certificate from the given `subject_private_key_seed` and
+/// `input_values`, and signed by `authority_private_key_seed`.
+/// The subject private key seed is supplied here so the implementation can choose
+/// between asymmetric mechanisms, for example ECDSA vs Ed25519.
+/// Returns the generated certificate.
+pub fn retry_generate_certificate(
+    subject_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE],
+    authority_private_key_seed: &[u8; PRIVATE_KEY_SEED_SIZE],
+    input_values: &InputValues,
+) -> Result<Vec<u8>> {
+    retry_with_measured_buffer(|certificate| {
+        generate_certificate(
+            subject_private_key_seed,
+            authority_private_key_seed,
+            input_values,
+            certificate,
+        )
+    })
+}
diff --git a/libs/dice/open_dice/tests/api_test.rs b/libs/dice/open_dice/tests/api_test.rs
new file mode 100644
index 0000000..a47265b
--- /dev/null
+++ b/libs/dice/open_dice/tests/api_test.rs
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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 diced_open_dice::{
+    derive_cdi_certificate_id, derive_cdi_private_key_seed, hash, kdf, keypair_from_seed, sign,
+    verify, CDI_SIZE, HASH_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE,
+};
+
+#[test]
+fn hash_succeeds() {
+    const EXPECTED_HASH: [u8; HASH_SIZE] = [
+        0x30, 0x9e, 0xcc, 0x48, 0x9c, 0x12, 0xd6, 0xeb, 0x4c, 0xc4, 0x0f, 0x50, 0xc9, 0x02, 0xf2,
+        0xb4, 0xd0, 0xed, 0x77, 0xee, 0x51, 0x1a, 0x7c, 0x7a, 0x9b, 0xcd, 0x3c, 0xa8, 0x6d, 0x4c,
+        0xd8, 0x6f, 0x98, 0x9d, 0xd3, 0x5b, 0xc5, 0xff, 0x49, 0x96, 0x70, 0xda, 0x34, 0x25, 0x5b,
+        0x45, 0xb0, 0xcf, 0xd8, 0x30, 0xe8, 0x1f, 0x60, 0x5d, 0xcf, 0x7d, 0xc5, 0x54, 0x2e, 0x93,
+        0xae, 0x9c, 0xd7, 0x6f,
+    ];
+    assert_eq!(EXPECTED_HASH, hash(b"hello world").expect("hash failed"));
+}
+
+#[test]
+fn kdf_succeeds() {
+    let mut derived_key = [0u8; PRIVATE_KEY_SEED_SIZE];
+    kdf(b"myInitialKeyMaterial", b"mySalt", b"myInfo", &mut derived_key).unwrap();
+    const EXPECTED_DERIVED_KEY: [u8; PRIVATE_KEY_SEED_SIZE] = [
+        0x91, 0x9b, 0x8d, 0x29, 0xc4, 0x1b, 0x93, 0xd7, 0xeb, 0x09, 0xfa, 0xd7, 0xc9, 0x87, 0xb0,
+        0xd1, 0xcc, 0x26, 0xef, 0x07, 0x83, 0x42, 0xcf, 0xa3, 0x45, 0x0a, 0x57, 0xe9, 0x19, 0x86,
+        0xef, 0x48,
+    ];
+    assert_eq!(EXPECTED_DERIVED_KEY, derived_key);
+}
+
+#[test]
+fn derive_cdi_certificate_id_succeeds() {
+    const EXPECTED_ID: [u8; ID_SIZE] = [
+        0x7a, 0x36, 0x45, 0x2c, 0x02, 0xf6, 0x2b, 0xec, 0xf9, 0x80, 0x06, 0x75, 0x87, 0xa5, 0xc1,
+        0x44, 0x0c, 0xd3, 0xc0, 0x6d,
+    ];
+    assert_eq!(EXPECTED_ID, derive_cdi_certificate_id(b"MyPubKey").unwrap());
+}
+
+const EXPECTED_SEED: &[u8] = &[
+    0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, 0xaa,
+    0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, 0x3a, 0x08,
+    0x84, 0x8a, 0x98, 0x85, 0x6d, 0xf5, 0x69, 0x21, 0x03, 0xcd, 0x09, 0xc3, 0x28, 0xd6, 0x06, 0xa7,
+    0x57, 0xbd, 0x48, 0x4b, 0x0f, 0x79, 0x0f, 0xf8, 0x2f, 0xf0, 0x0a, 0x41, 0x94, 0xd8, 0x8c, 0xa8,
+];
+
+const EXPECTED_CDI_ATTEST: &[u8] = &[
+    0xfa, 0x3c, 0x2f, 0x58, 0x37, 0xf5, 0x8e, 0x96, 0x16, 0x09, 0xf5, 0x22, 0xa1, 0xf1, 0xba, 0xaa,
+    0x19, 0x95, 0x01, 0x79, 0x2e, 0x60, 0x56, 0xaf, 0xf6, 0x41, 0xe7, 0xff, 0x48, 0xf5, 0x3a, 0x08,
+];
+
+const EXPECTED_CDI_PRIVATE_KEY_SEED: &[u8] = &[
+    0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, 0x0d,
+    0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, 0x02, 0x6e,
+];
+
+const EXPECTED_PUB_KEY: &[u8] = &[
+    0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, 0xc9,
+    0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, 0x06, 0x37,
+];
+const EXPECTED_PRIV_KEY: &[u8] = &[
+    0x5f, 0xcc, 0x8e, 0x1a, 0xd1, 0xc2, 0xb3, 0xe9, 0xfb, 0xe1, 0x68, 0xf0, 0xf6, 0x98, 0xfe, 0x0d,
+    0xee, 0xd4, 0xb5, 0x18, 0xcb, 0x59, 0x70, 0x2d, 0xee, 0x06, 0xe5, 0x70, 0xf1, 0x72, 0x02, 0x6e,
+    0x47, 0x42, 0x4b, 0xbd, 0xd7, 0x23, 0xb4, 0xcd, 0xca, 0xe2, 0x8e, 0xdc, 0x6b, 0xfc, 0x23, 0xc9,
+    0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52, 0xf1, 0x61, 0x06, 0x37,
+];
+
+const EXPECTED_SIGNATURE: &[u8] = &[
+    0x44, 0xae, 0xcc, 0xe2, 0xb9, 0x96, 0x18, 0x39, 0x0e, 0x61, 0x0f, 0x53, 0x07, 0xbf, 0xf2, 0x32,
+    0x3d, 0x44, 0xd4, 0xf2, 0x07, 0x23, 0x30, 0x85, 0x32, 0x18, 0xd2, 0x69, 0xb8, 0x29, 0x3c, 0x26,
+    0xe6, 0x0d, 0x9c, 0xa5, 0xc2, 0x73, 0xcd, 0x8c, 0xb8, 0x3c, 0x3e, 0x5b, 0xfd, 0x62, 0x8d, 0xf6,
+    0xc4, 0x27, 0xa6, 0xe9, 0x11, 0x06, 0x5a, 0xb2, 0x2b, 0x64, 0xf7, 0xfc, 0xbb, 0xab, 0x4a, 0x0e,
+];
+
+#[test]
+fn hash_derive_sign_verify() {
+    let seed = hash(b"MySeedString").unwrap();
+    assert_eq!(seed, EXPECTED_SEED);
+    let cdi_attest = &seed[..CDI_SIZE];
+    assert_eq!(cdi_attest, EXPECTED_CDI_ATTEST);
+    let cdi_private_key_seed = derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap();
+    assert_eq!(cdi_private_key_seed.as_array(), EXPECTED_CDI_PRIVATE_KEY_SEED);
+    let (pub_key, priv_key) = keypair_from_seed(cdi_private_key_seed.as_array()).unwrap();
+    assert_eq!(&pub_key, EXPECTED_PUB_KEY);
+    assert_eq!(priv_key.as_array(), EXPECTED_PRIV_KEY);
+    let mut signature = sign(b"MyMessage", priv_key.as_array()).unwrap();
+    assert_eq!(&signature, EXPECTED_SIGNATURE);
+    assert!(verify(b"MyMessage", &signature, &pub_key).is_ok());
+    assert!(verify(b"MyMessage_fail", &signature, &pub_key).is_err());
+    signature[0] += 1;
+    assert!(verify(b"MyMessage", &signature, &pub_key).is_err());
+}
diff --git a/libs/dice/sample_inputs/Android.bp b/libs/dice/sample_inputs/Android.bp
new file mode 100644
index 0000000..013038c
--- /dev/null
+++ b/libs/dice/sample_inputs/Android.bp
@@ -0,0 +1,81 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libdiced_sample_inputs_defaults",
+    crate_name: "diced_sample_inputs",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/lib.rs"],
+}
+
+rust_library {
+    name: "libdiced_sample_inputs",
+    defaults: ["libdiced_sample_inputs_defaults"],
+    features: [
+        "std",
+    ],
+    rustlibs: [
+        "libciborium",
+        "libcoset",
+        "libdiced_open_dice",
+        "liblog_rust",
+    ],
+}
+
+rust_library_rlib {
+    name: "libdiced_sample_inputs_nostd",
+    defaults: ["libdiced_sample_inputs_defaults"],
+    rustlibs: [
+        "libciborium_nostd",
+        "libcoset_nostd",
+        "libdiced_open_dice_nostd",
+        "liblog_rust_nostd",
+    ],
+    visibility: [
+        "//packages/modules/Virtualization:__subpackages__",
+    ],
+}
+
+rust_defaults {
+    name: "libdiced_sample_inputs_test_defaults",
+    crate_name: "diced_sample_inputs_test",
+    srcs: ["tests/*.rs"],
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libanyhow",
+        "libhwtrust",
+    ],
+}
+
+rust_test {
+    name: "libdiced_sample_inputs.integration_test",
+    defaults: ["libdiced_sample_inputs_test_defaults"],
+    rustlibs: [
+        "libdiced_open_dice",
+        "libdiced_sample_inputs",
+    ],
+}
+
+rust_test {
+    name: "libdiced_sample_inputs_nostd.integration_test",
+    defaults: ["libdiced_sample_inputs_test_defaults"],
+    rustlibs: [
+        "libdiced_open_dice_nostd",
+        "libdiced_sample_inputs_nostd",
+    ],
+}
diff --git a/libs/dice/sample_inputs/src/lib.rs b/libs/dice/sample_inputs/src/lib.rs
new file mode 100644
index 0000000..9d6deca
--- /dev/null
+++ b/libs/dice/sample_inputs/src/lib.rs
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//! Provides a set of sample inputs for a DICE chain and CDI values derived
+//! from it.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+
+mod sample_inputs;
+
+pub use sample_inputs::make_sample_bcc_and_cdis;
diff --git a/libs/dice/sample_inputs/src/sample_inputs.rs b/libs/dice/sample_inputs/src/sample_inputs.rs
new file mode 100644
index 0000000..54f551b
--- /dev/null
+++ b/libs/dice/sample_inputs/src/sample_inputs.rs
@@ -0,0 +1,200 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! This module provides a set of sample input values for a DICE chain, a sample UDS,
+//! as well as tuple of CDIs and BCC derived thereof.
+
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::{de, ser, value::Value};
+use core::ffi::CStr;
+use coset::{iana, Algorithm, AsCborValue, CoseKey, KeyOperation, KeyType, Label};
+use diced_open_dice::{
+    derive_cdi_private_key_seed, keypair_from_seed, retry_bcc_format_config_descriptor,
+    retry_bcc_main_flow, retry_dice_main_flow, Config, DiceArtifacts, DiceConfigValues, DiceError,
+    DiceMode, InputValues, OwnedDiceArtifacts, Result, CDI_SIZE, HASH_SIZE, HIDDEN_SIZE,
+};
+use log::error;
+
+/// Sample UDS used to perform the root dice flow by `make_sample_bcc_and_cdis`.
+const UDS: &[u8; CDI_SIZE] = &[
+    0x65, 0x4f, 0xab, 0xa9, 0xa5, 0xad, 0x0f, 0x5e, 0x15, 0xc3, 0x12, 0xf7, 0x77, 0x45, 0xfa, 0x55,
+    0x18, 0x6a, 0xa6, 0x34, 0xb6, 0x7c, 0x82, 0x7b, 0x89, 0x4c, 0xc5, 0x52, 0xd3, 0x27, 0x35, 0x8e,
+];
+
+const CODE_HASH_ABL: [u8; HASH_SIZE] = [
+    0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38, 0xc3, 0x64, 0x38, 0x63, 0x26,
+    0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34, 0x4c, 0x6d, 0xa2, 0xbe, 0x25,
+    0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2, 0xb3, 0x91, 0x4d, 0xd3, 0xfb,
+    0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba, 0x30, 0xf7, 0x15, 0x98, 0x14,
+];
+const AUTHORITY_HASH_ABL: [u8; HASH_SIZE] = [
+    0xf9, 0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7,
+    0x6b, 0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf,
+    0xfb, 0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd,
+    0xc1, 0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d,
+];
+const HIDDEN_ABL: [u8; HIDDEN_SIZE] = [
+    0xa2, 0x01, 0xd0, 0xc0, 0xaa, 0x75, 0x3c, 0x06, 0x43, 0x98, 0x6c, 0xc3, 0x5a, 0xb5, 0x5f, 0x1f,
+    0x0f, 0x92, 0x44, 0x3b, 0x0e, 0xd4, 0x29, 0x75, 0xe3, 0xdb, 0x36, 0xda, 0xc8, 0x07, 0x97, 0x4d,
+    0xff, 0xbc, 0x6a, 0xa4, 0x8a, 0xef, 0xc4, 0x7f, 0xf8, 0x61, 0x7d, 0x51, 0x4d, 0x2f, 0xdf, 0x7e,
+    0x8c, 0x3d, 0xa3, 0xfc, 0x63, 0xd4, 0xd4, 0x74, 0x8a, 0xc4, 0x14, 0x45, 0x83, 0x6b, 0x12, 0x7e,
+];
+const CODE_HASH_AVB: [u8; HASH_SIZE] = [
+    0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa, 0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d,
+    0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14, 0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa,
+    0x88, 0x70, 0x11, 0x07, 0x39, 0xa4, 0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f,
+    0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54, 0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7,
+];
+const AUTHORITY_HASH_AVB: [u8; HASH_SIZE] = [
+    0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9, 0x02, 0x35, 0x2b, 0xaa,
+    0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9, 0x35, 0x7d, 0xe4, 0x43,
+    0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e, 0xf6, 0x66, 0xef, 0xab,
+    0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a, 0xef, 0xbc, 0x05, 0x98,
+];
+const HIDDEN_AVB: [u8; HIDDEN_SIZE] = [
+    0x5b, 0x3f, 0xc9, 0x6b, 0xe3, 0x95, 0x59, 0x40, 0x5e, 0x64, 0xe5, 0x64, 0x3f, 0xfd, 0x21, 0x09,
+    0x9d, 0xf3, 0xcd, 0xc7, 0xa4, 0x2a, 0xe2, 0x97, 0xdd, 0xe2, 0x4f, 0xb0, 0x7d, 0x7e, 0xf5, 0x8e,
+    0xd6, 0x4d, 0x84, 0x25, 0x54, 0x41, 0x3f, 0x8f, 0x78, 0x64, 0x1a, 0x51, 0x27, 0x9d, 0x55, 0x8a,
+    0xe9, 0x90, 0x35, 0xab, 0x39, 0x80, 0x4b, 0x94, 0x40, 0x84, 0xa2, 0xfd, 0x73, 0xeb, 0x35, 0x7a,
+];
+const AUTHORITY_HASH_ANDROID: [u8; HASH_SIZE] = [
+    0x04, 0x25, 0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, 0xb8, 0xd6,
+    0xe1, 0x99, 0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, 0x68, 0x4e, 0x1d, 0xc0,
+    0x24, 0x74, 0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77,
+    0x0a, 0xde, 0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f,
+];
+
+fn ed25519_public_key_to_cbor_value(public_key: &[u8]) -> Result<Value> {
+    let key = CoseKey {
+        kty: KeyType::Assigned(iana::KeyType::OKP),
+        alg: Some(Algorithm::Assigned(iana::Algorithm::EdDSA)),
+        key_ops: vec![KeyOperation::Assigned(iana::KeyOperation::Verify)].into_iter().collect(),
+        params: vec![
+            (
+                Label::Int(iana::Ec2KeyParameter::Crv as i64),
+                Value::from(iana::EllipticCurve::Ed25519 as u64),
+            ),
+            (Label::Int(iana::Ec2KeyParameter::X as i64), Value::Bytes(public_key.to_vec())),
+        ],
+        ..Default::default()
+    };
+    key.to_cbor_value().map_err(|e| {
+        error!("Failed to serialize the key to CBOR data: {e}");
+        DiceError::InvalidInput
+    })
+}
+
+/// Makes a DICE chain (BCC) from the sample input.
+///
+/// The DICE chain is of the following format:
+/// public key derived from UDS -> ABL certificate -> AVB certificate -> Android certificate
+pub fn make_sample_bcc_and_cdis() -> Result<OwnedDiceArtifacts> {
+    let private_key_seed = derive_cdi_private_key_seed(UDS).map_err(|e| {
+        error!("In make_sample_bcc_and_cdis: Trying to derive private key seed. Error: {e}");
+        e
+    })?;
+
+    // Gets the root public key in DICE chain (BCC).
+    let (public_key, _) = keypair_from_seed(private_key_seed.as_array()).map_err(|e| {
+        error!("In make_sample_bcc_and_cids: Failed to generate key pair. Error: {e}");
+        e
+    })?;
+    let ed25519_public_key_value = ed25519_public_key_to_cbor_value(&public_key)?;
+
+    // Gets the ABL certificate to as the root certificate of DICE chain.
+    let config_values = DiceConfigValues {
+        component_name: Some(CStr::from_bytes_with_nul(b"ABL\0").unwrap()),
+        component_version: Some(1),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    let input_values = InputValues::new(
+        CODE_HASH_ABL,
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_ABL,
+        DiceMode::kDiceModeNormal,
+        HIDDEN_ABL,
+    );
+    let (cdi_values, cert) = retry_dice_main_flow(UDS, UDS, &input_values).map_err(|e| {
+        error!("In make_sample_bcc_and_cdis: Trying to run first main flow. Error: {e}");
+        e
+    })?;
+    let bcc_value = Value::Array(vec![
+        ed25519_public_key_value,
+        de::from_reader(&cert[..]).map_err(|e| {
+            error!("Deserialize root DICE certificate failed: {e}");
+            DiceError::InvalidInput
+        })?,
+    ]);
+    let mut bcc: Vec<u8> = vec![];
+    ser::into_writer(&bcc_value, &mut bcc).map_err(|e| {
+        error!("Serialize BCC failed: {e}");
+        DiceError::InvalidInput
+    })?;
+
+    // Appends AVB certificate to DICE chain.
+    let config_values = DiceConfigValues {
+        component_name: Some(CStr::from_bytes_with_nul(b"AVB\0").unwrap()),
+        component_version: Some(1),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    let input_values = InputValues::new(
+        CODE_HASH_AVB,
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_AVB,
+        DiceMode::kDiceModeNormal,
+        HIDDEN_AVB,
+    );
+    let dice_artifacts =
+        retry_bcc_main_flow(&cdi_values.cdi_attest, &cdi_values.cdi_seal, &bcc, &input_values)
+            .map_err(|e| {
+                error!(
+                    "In make_sample_bcc_and_cdis: Trying to run first bcc main flow. Error: {e}"
+                );
+                e
+            })?;
+
+    // Appends Android certificate to DICE chain.
+    let config_values = DiceConfigValues {
+        component_name: Some(CStr::from_bytes_with_nul(b"Android\0").unwrap()),
+        component_version: Some(12),
+        resettable: true,
+        ..Default::default()
+    };
+    let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
+    let input_values = InputValues::new(
+        [0u8; HASH_SIZE], // code_hash
+        Config::Descriptor(config_descriptor.as_slice()),
+        AUTHORITY_HASH_ANDROID,
+        DiceMode::kDiceModeNormal,
+        [0u8; HIDDEN_SIZE], // hidden
+    );
+    retry_bcc_main_flow(
+        dice_artifacts.cdi_attest(),
+        dice_artifacts.cdi_seal(),
+        dice_artifacts.bcc().ok_or_else(|| {
+            error!("bcc is none");
+            DiceError::InvalidInput
+        })?,
+        &input_values,
+    )
+    .map_err(|e| {
+        error!("In make_sample_bcc_and_cdis: Trying to run second bcc main flow. Error: {e}");
+        e
+    })
+}
diff --git a/libs/dice/sample_inputs/tests/api_test.rs b/libs/dice/sample_inputs/tests/api_test.rs
new file mode 100644
index 0000000..0823f16
--- /dev/null
+++ b/libs/dice/sample_inputs/tests/api_test.rs
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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 anyhow::Result;
+use diced_open_dice::{derive_cdi_leaf_priv, sign, DiceArtifacts};
+use diced_sample_inputs::make_sample_bcc_and_cdis;
+use hwtrust::{dice, session::Session};
+
+const EXPECTED_SAMPLE_CDI_ATTEST: &[u8] = &[
+    0x3e, 0x57, 0x65, 0x5d, 0x48, 0x02, 0xbd, 0x5c, 0x66, 0xcc, 0x1f, 0x0f, 0xbe, 0x5e, 0x32, 0xb6,
+    0x9e, 0x3d, 0x04, 0xaf, 0x00, 0x15, 0xbc, 0xdd, 0x1f, 0xbc, 0x59, 0xe4, 0xc3, 0x87, 0x95, 0x5e,
+];
+
+const EXPECTED_SAMPLE_CDI_SEAL: &[u8] = &[
+    0x36, 0x1b, 0xd2, 0xb3, 0xc4, 0xda, 0x77, 0xb2, 0x9c, 0xba, 0x39, 0x53, 0x82, 0x93, 0xd9, 0xb8,
+    0x9f, 0x73, 0x2d, 0x27, 0x06, 0x15, 0xa8, 0xcb, 0x6d, 0x1d, 0xf2, 0xb1, 0x54, 0xbb, 0x62, 0xf1,
+];
+
+const EXPECTED_SAMPLE_BCC: &[u8] = &[
+    0x84, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x3e, 0x85,
+    0xe5, 0x72, 0x75, 0x55, 0xe5, 0x1e, 0xe7, 0xf3, 0x35, 0x94, 0x8e, 0xbb, 0xbd, 0x74, 0x1e, 0x1d,
+    0xca, 0x49, 0x9c, 0x97, 0x39, 0x77, 0x06, 0xd3, 0xc8, 0x6e, 0x8b, 0xd7, 0x33, 0xf9, 0x84, 0x43,
+    0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28, 0x34, 0x32, 0x64, 0x38, 0x38,
+    0x36, 0x34, 0x66, 0x39, 0x37, 0x62, 0x36, 0x35, 0x34, 0x37, 0x61, 0x35, 0x30, 0x63, 0x31, 0x65,
+    0x30, 0x61, 0x37, 0x34, 0x39, 0x66, 0x38, 0x65, 0x66, 0x38, 0x62, 0x38, 0x31, 0x65, 0x63, 0x36,
+    0x32, 0x61, 0x66, 0x02, 0x78, 0x28, 0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35,
+    0x32, 0x66, 0x32, 0x39, 0x65, 0x39, 0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65,
+    0x33, 0x32, 0x63, 0x64, 0x38, 0x31, 0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x3a, 0x00,
+    0x47, 0x44, 0x50, 0x58, 0x40, 0x16, 0x48, 0xf2, 0x55, 0x53, 0x23, 0xdd, 0x15, 0x2e, 0x83, 0x38,
+    0xc3, 0x64, 0x38, 0x63, 0x26, 0x0f, 0xcf, 0x5b, 0xd1, 0x3a, 0xd3, 0x40, 0x3e, 0x23, 0xf8, 0x34,
+    0x4c, 0x6d, 0xa2, 0xbe, 0x25, 0x1c, 0xb0, 0x29, 0xe8, 0xc3, 0xfb, 0xb8, 0x80, 0xdc, 0xb1, 0xd2,
+    0xb3, 0x91, 0x4d, 0xd3, 0xfb, 0x01, 0x0f, 0xe4, 0xe9, 0x46, 0xa2, 0xc0, 0x26, 0x57, 0x5a, 0xba,
+    0x30, 0xf7, 0x15, 0x98, 0x14, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56, 0xa3, 0x3a, 0x00, 0x01, 0x11,
+    0x71, 0x63, 0x41, 0x42, 0x4c, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01, 0x3a, 0x00, 0x01, 0x11, 0x73,
+    0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x47, 0xae, 0x42, 0x27, 0x4c, 0xcb, 0x65, 0x4d,
+    0xee, 0x74, 0x2d, 0x05, 0x78, 0x2a, 0x08, 0x2a, 0xa5, 0xf0, 0xcf, 0xea, 0x3e, 0x60, 0xee, 0x97,
+    0x11, 0x4b, 0x5b, 0xe6, 0x05, 0x0c, 0xe8, 0x90, 0xf5, 0x22, 0xc4, 0xc6, 0x67, 0x7a, 0x22, 0x27,
+    0x17, 0xb3, 0x79, 0xcc, 0x37, 0x64, 0x5e, 0x19, 0x4f, 0x96, 0x37, 0x67, 0x3c, 0xd0, 0xc5, 0xed,
+    0x0f, 0xdd, 0xe7, 0x2e, 0x4f, 0x70, 0x97, 0x30, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0xf9,
+    0x00, 0x9d, 0xc2, 0x59, 0x09, 0xe0, 0xb6, 0x98, 0xbd, 0xe3, 0x97, 0x4a, 0xcb, 0x3c, 0xe7, 0x6b,
+    0x24, 0xc3, 0xe4, 0x98, 0xdd, 0xa9, 0x6a, 0x41, 0x59, 0x15, 0xb1, 0x23, 0xe6, 0xc8, 0xdf, 0xfb,
+    0x52, 0xb4, 0x52, 0xc1, 0xb9, 0x61, 0xdd, 0xbc, 0x5b, 0x37, 0x0e, 0x12, 0x12, 0xb2, 0xfd, 0xc1,
+    0x09, 0xb0, 0xcf, 0x33, 0x81, 0x4c, 0xc6, 0x29, 0x1b, 0x99, 0xea, 0xae, 0xfd, 0xaa, 0x0d, 0x3a,
+    0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01,
+    0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xb1, 0x02, 0xcc, 0x2c, 0xb2, 0x6a,
+    0x3b, 0xe9, 0xc1, 0xd3, 0x95, 0x10, 0xa0, 0xe1, 0xff, 0x51, 0xde, 0x57, 0xd5, 0x65, 0x28, 0xfd,
+    0x7f, 0xeb, 0xd4, 0xca, 0x15, 0xf3, 0xca, 0xdf, 0x37, 0x88, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41,
+    0x20, 0x58, 0x40, 0x58, 0xd8, 0x03, 0x24, 0x53, 0x60, 0x57, 0xa9, 0x09, 0xfa, 0xab, 0xdc, 0x57,
+    0x1e, 0xf0, 0xe5, 0x1e, 0x51, 0x6f, 0x9e, 0xa3, 0x42, 0xe6, 0x6a, 0x8c, 0xaa, 0xad, 0x08, 0x48,
+    0xde, 0x7f, 0x4f, 0x6e, 0x2f, 0x7f, 0x39, 0x6c, 0xa1, 0xf8, 0x42, 0x71, 0xfe, 0x17, 0x3d, 0xca,
+    0x31, 0x83, 0x92, 0xed, 0xbb, 0x40, 0xb8, 0x10, 0xe0, 0xf2, 0x5a, 0x99, 0x53, 0x38, 0x46, 0x33,
+    0x97, 0x78, 0x05, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x8a, 0xa9, 0x01, 0x78, 0x28,
+    0x31, 0x66, 0x36, 0x39, 0x36, 0x66, 0x30, 0x37, 0x32, 0x35, 0x32, 0x66, 0x32, 0x39, 0x65, 0x39,
+    0x33, 0x66, 0x65, 0x34, 0x64, 0x65, 0x31, 0x39, 0x65, 0x65, 0x33, 0x32, 0x63, 0x64, 0x38, 0x31,
+    0x64, 0x63, 0x34, 0x30, 0x34, 0x65, 0x37, 0x36, 0x02, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38,
+    0x39, 0x65, 0x36, 0x39, 0x37, 0x34, 0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66,
+    0x34, 0x34, 0x32, 0x36, 0x37, 0x65, 0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35,
+    0x37, 0x32, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0xa4, 0x0c, 0xcb, 0xc1, 0xbf, 0xfa,
+    0xcc, 0xfd, 0xeb, 0xf4, 0xfc, 0x43, 0x83, 0x7f, 0x46, 0x8d, 0xd8, 0xd8, 0x14, 0xc1, 0x96, 0x14,
+    0x1f, 0x6e, 0xb3, 0xa0, 0xd9, 0x56, 0xb3, 0xbf, 0x2f, 0xfa, 0x88, 0x70, 0x11, 0x07, 0x39, 0xa4,
+    0xd2, 0xa9, 0x6b, 0x18, 0x28, 0xe8, 0x29, 0x20, 0x49, 0x0f, 0xbb, 0x8d, 0x08, 0x8c, 0xc6, 0x54,
+    0xe9, 0x71, 0xd2, 0x7e, 0xa4, 0xfe, 0x58, 0x7f, 0xd3, 0xc7, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x56,
+    0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x63, 0x41, 0x56, 0x42, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x01,
+    0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x93, 0x17, 0xe1,
+    0x11, 0x27, 0x59, 0xd0, 0xef, 0x75, 0x0b, 0x2b, 0x1c, 0x0f, 0x5f, 0x52, 0xc3, 0x29, 0x23, 0xb5,
+    0x2a, 0xe6, 0x12, 0x72, 0x6f, 0x39, 0x86, 0x65, 0x2d, 0xf2, 0xe4, 0xe7, 0xd0, 0xaf, 0x0e, 0xa7,
+    0x99, 0x16, 0x89, 0x97, 0x21, 0xf7, 0xdc, 0x89, 0xdc, 0xde, 0xbb, 0x94, 0x88, 0x1f, 0xda, 0xe2,
+    0xf3, 0xe0, 0x54, 0xf9, 0x0e, 0x29, 0xb1, 0xbd, 0xe1, 0x0c, 0x0b, 0xd7, 0xf6, 0x3a, 0x00, 0x47,
+    0x44, 0x54, 0x58, 0x40, 0xb2, 0x69, 0x05, 0x48, 0x56, 0xb5, 0xfa, 0x55, 0x6f, 0xac, 0x56, 0xd9,
+    0x02, 0x35, 0x2b, 0xaa, 0x4c, 0xba, 0x28, 0xdd, 0x82, 0x3a, 0x86, 0xf5, 0xd4, 0xc2, 0xf1, 0xf9,
+    0x35, 0x7d, 0xe4, 0x43, 0x13, 0xbf, 0xfe, 0xd3, 0x36, 0xd8, 0x1c, 0x12, 0x78, 0x5c, 0x9c, 0x3e,
+    0xf6, 0x66, 0xef, 0xab, 0x3d, 0x0f, 0x89, 0xa4, 0x6f, 0xc9, 0x72, 0xee, 0x73, 0x43, 0x02, 0x8a,
+    0xef, 0xbc, 0x05, 0x98, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57,
+    0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x96,
+    0x6d, 0x96, 0x42, 0xda, 0x64, 0x51, 0xad, 0xfa, 0x00, 0xbc, 0xbc, 0x95, 0x8a, 0xb0, 0xb9, 0x76,
+    0x01, 0xe6, 0xbd, 0xc0, 0x26, 0x79, 0x26, 0xfc, 0x0f, 0x1d, 0x87, 0x65, 0xf1, 0xf3, 0x99, 0x3a,
+    0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0x10, 0x7f, 0x77, 0xad, 0x70, 0xbd, 0x52, 0x81,
+    0x28, 0x8d, 0x24, 0x81, 0xb4, 0x3f, 0x21, 0x68, 0x9f, 0xc3, 0x80, 0x68, 0x86, 0x55, 0xfb, 0x2e,
+    0x6d, 0x96, 0xe1, 0xe1, 0xb7, 0x28, 0x8d, 0x63, 0x85, 0xba, 0x2a, 0x01, 0x33, 0x87, 0x60, 0x63,
+    0xbb, 0x16, 0x3f, 0x2f, 0x3d, 0xf4, 0x2d, 0x48, 0x5b, 0x87, 0xed, 0xda, 0x34, 0xeb, 0x9c, 0x4d,
+    0x14, 0xac, 0x65, 0xf4, 0xfa, 0xef, 0x45, 0x0b, 0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01,
+    0x8f, 0xa9, 0x01, 0x78, 0x28, 0x32, 0x35, 0x39, 0x34, 0x38, 0x39, 0x65, 0x36, 0x39, 0x37, 0x34,
+    0x38, 0x37, 0x30, 0x35, 0x64, 0x65, 0x33, 0x65, 0x32, 0x66, 0x34, 0x34, 0x32, 0x36, 0x37, 0x65,
+    0x61, 0x34, 0x39, 0x33, 0x38, 0x66, 0x66, 0x36, 0x61, 0x35, 0x37, 0x32, 0x35, 0x02, 0x78, 0x28,
+    0x35, 0x64, 0x34, 0x65, 0x64, 0x37, 0x66, 0x34, 0x31, 0x37, 0x61, 0x39, 0x35, 0x34, 0x61, 0x31,
+    0x38, 0x31, 0x34, 0x30, 0x37, 0x62, 0x35, 0x38, 0x38, 0x35, 0x61, 0x66, 0x64, 0x37, 0x32, 0x61,
+    0x35, 0x62, 0x66, 0x34, 0x30, 0x64, 0x61, 0x36, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a,
+    0x00, 0x47, 0x44, 0x53, 0x58, 0x1a, 0xa3, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x67, 0x41, 0x6e, 0x64,
+    0x72, 0x6f, 0x69, 0x64, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x0c, 0x3a, 0x00, 0x01, 0x11, 0x73, 0xf6,
+    0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x40, 0x26, 0x1a, 0xbd, 0x26, 0xd8, 0x37, 0x8f, 0x4a, 0xf2,
+    0x9e, 0x49, 0x4d, 0x93, 0x23, 0xc4, 0x6e, 0x02, 0xda, 0xe0, 0x00, 0x02, 0xe7, 0xed, 0x29, 0xdf,
+    0x2b, 0xb3, 0x69, 0xf3, 0x55, 0x0e, 0x4c, 0x22, 0xdc, 0xcf, 0xf5, 0x92, 0xc9, 0xfa, 0x78, 0x98,
+    0xf1, 0x0e, 0x55, 0x5f, 0xf4, 0x45, 0xed, 0xc0, 0x0a, 0x72, 0x2a, 0x7a, 0x3a, 0xd2, 0xb1, 0xf7,
+    0x76, 0xfe, 0x2a, 0x6b, 0x7b, 0x2a, 0x53, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40, 0x04, 0x25,
+    0x5d, 0x60, 0x5f, 0x5c, 0x45, 0x0d, 0xf2, 0x9a, 0x6e, 0x99, 0x30, 0x03, 0xb8, 0xd6, 0xe1, 0x99,
+    0x71, 0x1b, 0xf8, 0x44, 0xfa, 0xb5, 0x31, 0x79, 0x1c, 0x37, 0x68, 0x4e, 0x1d, 0xc0, 0x24, 0x74,
+    0x68, 0xf8, 0x80, 0x20, 0x3e, 0x44, 0xb1, 0x43, 0xd2, 0x9c, 0xfc, 0x12, 0x9e, 0x77, 0x0a, 0xde,
+    0x29, 0x24, 0xff, 0x2e, 0xfa, 0xc7, 0x10, 0xd5, 0x73, 0xd4, 0xc6, 0xdf, 0x62, 0x9f, 0x3a, 0x00,
+    0x47, 0x44, 0x56, 0x41, 0x01, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03,
+    0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xdb, 0xe7, 0x5b, 0x3f, 0xa3, 0x42, 0xb0,
+    0x9c, 0xf8, 0x40, 0x8c, 0xb0, 0x9c, 0xf0, 0x0a, 0xaf, 0xdf, 0x6f, 0xe5, 0x09, 0x21, 0x11, 0x92,
+    0xe1, 0xf8, 0xc5, 0x09, 0x02, 0x3d, 0x1f, 0xb7, 0xc5, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20,
+    0x58, 0x40, 0xc4, 0xc1, 0xd7, 0x1c, 0x2d, 0x26, 0x89, 0x22, 0xcf, 0xa6, 0x99, 0x77, 0x30, 0x84,
+    0x86, 0x27, 0x59, 0x8f, 0xd8, 0x08, 0x75, 0xe0, 0xb2, 0xef, 0xf9, 0xfa, 0xa5, 0x40, 0x8c, 0xd3,
+    0xeb, 0xbb, 0xda, 0xf2, 0xc8, 0xae, 0x41, 0x22, 0x50, 0x9c, 0xe8, 0xb2, 0x9c, 0x9b, 0x3f, 0x8a,
+    0x78, 0x76, 0xab, 0xd0, 0xbe, 0xfc, 0xe4, 0x79, 0xcb, 0x1b, 0x2b, 0xaa, 0x4d, 0xdd, 0x15, 0x61,
+    0x42, 0x06,
+];
+const MESSAGE: &[u8] = b"Message for testing";
+
+#[test]
+fn sample_bcc_and_cdis_are_as_expected() {
+    let dice_artifacts = make_sample_bcc_and_cdis().unwrap();
+    assert_eq!(dice_artifacts.cdi_attest(), EXPECTED_SAMPLE_CDI_ATTEST);
+    assert_eq!(dice_artifacts.cdi_seal(), EXPECTED_SAMPLE_CDI_SEAL);
+    assert_eq!(dice_artifacts.bcc(), Some(EXPECTED_SAMPLE_BCC));
+}
+
+#[test]
+fn cdi_leaf_priv_corresponds_to_leaf_public_key_in_dice_chain() -> Result<()> {
+    let dice_artifacts = make_sample_bcc_and_cdis().unwrap();
+    let private_key = derive_cdi_leaf_priv(&dice_artifacts).unwrap();
+    let signature = sign(MESSAGE, private_key.as_array()).unwrap();
+
+    let session = Session::default();
+    let chain = dice::Chain::from_cbor(&session, dice_artifacts.bcc().unwrap())?;
+    let public_key = chain.leaf().subject_public_key();
+    public_key.verify(&signature, MESSAGE)
+}
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 402040c..0a05471 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -66,28 +66,28 @@
 
 genrule {
     name: "fdt_test_tree_one_memory_range_dtb",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["tests/data/test_tree_one_memory_range.dts"],
     out: ["data/test_tree_one_memory_range.dtb"],
 }
 
 genrule {
     name: "fdt_test_tree_multiple_memory_ranges_dtb",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["tests/data/test_tree_multiple_memory_ranges.dts"],
     out: ["data/test_tree_multiple_memory_ranges.dtb"],
 }
 
 genrule {
     name: "fdt_test_tree_empty_memory_range_dtb",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["tests/data/test_tree_empty_memory_range.dts"],
     out: ["data/test_tree_empty_memory_range.dtb"],
 }
 
 genrule {
     name: "fdt_test_tree_no_memory_node_dtb",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["tests/data/test_tree_no_memory_node.dts"],
     out: ["data/test_tree_no_memory_node.dtb"],
 }
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index 758df2a..a6d5739 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -202,11 +202,11 @@
 }
 
 impl<'a> FdtNode<'a> {
-    /// Create immutable node from a mutable node at the same offset
+    /// Creates immutable node from a mutable node at the same offset.
     pub fn from_mut(other: &'a FdtNodeMut) -> Self {
         FdtNode { fdt: other.fdt, offset: other.offset }
     }
-    /// Find parent node.
+    /// Returns parent node.
     pub fn parent(&self) -> Result<Self> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe { libfdt_bindgen::fdt_parent_offset(self.fdt.as_ptr(), self.offset) };
@@ -214,12 +214,12 @@
         Ok(Self { fdt: self.fdt, offset: fdt_err(ret)? })
     }
 
-    /// Retrieve the standard (deprecated) device_type <string> property.
+    /// Returns the standard (deprecated) device_type <string> property.
     pub fn device_type(&self) -> Result<Option<&CStr>> {
         self.getprop_str(CStr::from_bytes_with_nul(b"device_type\0").unwrap())
     }
 
-    /// Retrieve the standard reg <prop-encoded-array> property.
+    /// Returns the standard reg <prop-encoded-array> property.
     pub fn reg(&self) -> Result<Option<RegIterator<'a>>> {
         let reg = CStr::from_bytes_with_nul(b"reg\0").unwrap();
 
@@ -235,7 +235,7 @@
         }
     }
 
-    /// Retrieves the standard ranges property.
+    /// Returns the standard ranges property.
     pub fn ranges<A, P, S>(&self) -> Result<Option<RangesIterator<'a, A, P, S>>> {
         let ranges = CStr::from_bytes_with_nul(b"ranges\0").unwrap();
         if let Some(cells) = self.getprop_cells(ranges)? {
@@ -266,7 +266,7 @@
         CStr::from_bytes_with_nul(name).map_err(|_| FdtError::Internal)
     }
 
-    /// Retrieve the value of a given <string> property.
+    /// Returns the value of a given <string> property.
     pub fn getprop_str(&self, name: &CStr) -> Result<Option<&CStr>> {
         let value = if let Some(bytes) = self.getprop(name)? {
             Some(CStr::from_bytes_with_nul(bytes).map_err(|_| FdtError::BadValue)?)
@@ -276,7 +276,7 @@
         Ok(value)
     }
 
-    /// Retrieve the value of a given property as an array of cells.
+    /// Returns the value of a given property as an array of cells.
     pub fn getprop_cells(&self, name: &CStr) -> Result<Option<CellIterator<'a>>> {
         if let Some(cells) = self.getprop(name)? {
             Ok(Some(CellIterator::new(cells)))
@@ -285,7 +285,7 @@
         }
     }
 
-    /// Retrieve the value of a given <u32> property.
+    /// Returns the value of a given <u32> property.
     pub fn getprop_u32(&self, name: &CStr) -> Result<Option<u32>> {
         let value = if let Some(bytes) = self.getprop(name)? {
             Some(u32::from_be_bytes(bytes.try_into().map_err(|_| FdtError::BadValue)?))
@@ -295,7 +295,7 @@
         Ok(value)
     }
 
-    /// Retrieve the value of a given <u64> property.
+    /// Returns the value of a given <u64> property.
     pub fn getprop_u64(&self, name: &CStr) -> Result<Option<u64>> {
         let value = if let Some(bytes) = self.getprop(name)? {
             Some(u64::from_be_bytes(bytes.try_into().map_err(|_| FdtError::BadValue)?))
@@ -305,7 +305,7 @@
         Ok(value)
     }
 
-    /// Retrieve the value of a given property.
+    /// Returns the value of a given property.
     pub fn getprop(&self, name: &CStr) -> Result<Option<&'a [u8]>> {
         if let Some((prop, len)) = Self::getprop_internal(self.fdt, self.offset, name)? {
             Ok(Some(self.fdt.get_from_ptr(prop, len)?))
@@ -314,7 +314,7 @@
         }
     }
 
-    /// Return the pointer and size of the property named `name`, in a node at offset `offset`, in
+    /// Returns the pointer and size of the property named `name`, in a node at offset `offset`, in
     /// a device tree `fdt`. The pointer is guaranteed to be non-null, in which case error returns.
     fn getprop_internal(
         fdt: &'a Fdt,
@@ -347,7 +347,7 @@
         Ok(Some((prop.cast::<c_void>(), len)))
     }
 
-    /// Get reference to the containing device tree.
+    /// Returns reference to the containing device tree.
     pub fn fdt(&self) -> &Fdt {
         self.fdt
     }
@@ -412,7 +412,7 @@
 }
 
 impl<'a> FdtNodeMut<'a> {
-    /// Append a property name-value (possibly empty) pair to the given node.
+    /// Appends a property name-value (possibly empty) pair to the given node.
     pub fn appendprop<T: AsRef<[u8]>>(&mut self, name: &CStr, value: &T) -> Result<()> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
         let ret = unsafe {
@@ -428,7 +428,7 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Append a (address, size) pair property to the given node.
+    /// Appends a (address, size) pair property to the given node.
     pub fn appendprop_addrrange(&mut self, name: &CStr, addr: u64, size: u64) -> Result<()> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
         let ret = unsafe {
@@ -445,7 +445,9 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Create or change a property name-value pair to the given node.
+    /// Sets a property name-value pair to the given node.
+    ///
+    /// This may create a new prop or replace existing value.
     pub fn setprop(&mut self, name: &CStr, value: &[u8]) -> Result<()> {
         // SAFETY: New value size is constrained to the DT totalsize
         //          (validated by underlying libfdt).
@@ -462,8 +464,10 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Replace the value of the given property with the given value, and ensure that the given
-    /// value has the same length as the current value length
+    /// Sets the value of the given property with the given value, and ensure that the given
+    /// value has the same length as the current value length.
+    ///
+    /// This can only be used to replace existing value.
     pub fn setprop_inplace(&mut self, name: &CStr, value: &[u8]) -> Result<()> {
         // SAFETY: fdt size is not altered
         let ret = unsafe {
@@ -479,19 +483,23 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Replace the value of the given (address, size) pair property with the given value, and
-    /// ensure that the given value has the same length as the current value length
+    /// Sets the value of the given (address, size) pair property with the given value, and
+    /// ensure that the given value has the same length as the current value length.
+    ///
+    /// This can only be used to replace existing value.
     pub fn setprop_addrrange_inplace(&mut self, name: &CStr, addr: u64, size: u64) -> Result<()> {
         let pair = [addr.to_be(), size.to_be()];
         self.setprop_inplace(name, pair.as_bytes())
     }
 
-    /// Create or change a flag-like empty property.
+    /// Sets a flag-like empty property.
+    ///
+    /// This may create a new prop or replace existing value.
     pub fn setprop_empty(&mut self, name: &CStr) -> Result<()> {
         self.setprop(name, &[])
     }
 
-    /// Delete the given property.
+    /// Deletes the given property.
     pub fn delprop(&mut self, name: &CStr) -> Result<()> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) when the
         // library locates the node's property. Removing the property may shift the offsets of
@@ -504,7 +512,7 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Overwrite the given property with FDT_NOP, effectively removing it from the DT.
+    /// Sets the given property with FDT_NOP, effectively removing it from the DT.
     pub fn nop_property(&mut self, name: &CStr) -> Result<()> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor) when the
         // library locates the node's property.
@@ -515,7 +523,7 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Reduce the size of the given property to new_size
+    /// Trims the size of the given property to new_size.
     pub fn trimprop(&mut self, name: &CStr, new_size: usize) -> Result<()> {
         let (prop, len) =
             FdtNode::getprop_internal(self.fdt, self.offset, name)?.ok_or(FdtError::NotFound)?;
@@ -540,12 +548,12 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Get reference to the containing device tree.
+    /// Returns reference to the containing device tree.
     pub fn fdt(&mut self) -> &mut Fdt {
         self.fdt
     }
 
-    /// Add a new subnode to the given node and return it as a FdtNodeMut on success.
+    /// Adds a new subnode to the given node and return it as a FdtNodeMut on success.
     pub fn add_subnode(&'a mut self, name: &CStr) -> Result<Self> {
         // SAFETY: Accesses are constrained to the DT totalsize (validated by ctor).
         let ret = unsafe {
@@ -562,7 +570,7 @@
         Ok(FdtNode { fdt: &*self.fdt, offset: fdt_err(ret)? })
     }
 
-    /// Returns the compatible node of the given name that is next after this node
+    /// Returns the compatible node of the given name that is next after this node.
     pub fn next_compatible(self, compatible: &CStr) -> Result<Option<Self>> {
         // SAFETY: Accesses (read-only) are constrained to the DT totalsize.
         let ret = unsafe {
@@ -576,8 +584,8 @@
         Ok(fdt_err_or_option(ret)?.map(|offset| Self { fdt: self.fdt, offset }))
     }
 
-    /// Replace this node and its subtree with nop tags, effectively removing it from the tree, and
-    /// then return the next compatible node of the given name.
+    /// Deletes the node effectively by overwriting this node and its subtree with nop tags.
+    /// Returns the next compatible node of the given name.
     // Side note: without this, filterint out excessive compatible nodes from the DT is impossible.
     // The reason is that libfdt ensures that the node from where the search for the next
     // compatible node is started is always a valid one -- except for the special case of offset =
@@ -678,7 +686,7 @@
         unsafe { mem::transmute::<&mut [u8], &mut Self>(fdt) }
     }
 
-    /// Update this FDT from a slice containing another FDT
+    /// Updates this FDT from a slice containing another FDT.
     pub fn copy_from_slice(&mut self, new_fdt: &[u8]) -> Result<()> {
         if self.buffer.len() < new_fdt.len() {
             Err(FdtError::NoSpace)
@@ -693,7 +701,7 @@
         }
     }
 
-    /// Make the whole slice containing the DT available to libfdt.
+    /// Unpacks the DT to cover the whole slice it is contained in.
     pub fn unpack(&mut self) -> Result<()> {
         // SAFETY: "Opens" the DT in-place (supported use-case) by updating its header and
         // internal structures to make use of the whole self.fdt slice but performs no accesses
@@ -708,7 +716,7 @@
         fdt_err_expect_zero(ret)
     }
 
-    /// Pack the DT to take a minimum amount of memory.
+    /// Packs the DT to take a minimum amount of memory.
     ///
     /// Doesn't shrink the underlying memory slice.
     pub fn pack(&mut self) -> Result<()> {
@@ -752,22 +760,22 @@
         self.memory()?.next().ok_or(FdtError::NotFound)
     }
 
-    /// Retrieve the standard /chosen node.
+    /// Returns the standard /chosen node.
     pub fn chosen(&self) -> Result<Option<FdtNode>> {
         self.node(CStr::from_bytes_with_nul(b"/chosen\0").unwrap())
     }
 
-    /// Retrieve the standard /chosen node as mutable.
+    /// Returns the standard /chosen node as mutable.
     pub fn chosen_mut(&mut self) -> Result<Option<FdtNodeMut>> {
         self.node_mut(CStr::from_bytes_with_nul(b"/chosen\0").unwrap())
     }
 
-    /// Get the root node of the tree.
+    /// Returns the root node of the tree.
     pub fn root(&self) -> Result<FdtNode> {
         self.node(CStr::from_bytes_with_nul(b"/\0").unwrap())?.ok_or(FdtError::Internal)
     }
 
-    /// Find a tree node by its full path.
+    /// Returns a tree node by its full path.
     pub fn node(&self, path: &CStr) -> Result<Option<FdtNode>> {
         Ok(self.path_offset(path)?.map(|offset| FdtNode { fdt: self, offset }))
     }
@@ -777,17 +785,17 @@
         CompatibleIterator::new(self, compatible)
     }
 
-    /// Get the mutable root node of the tree.
+    /// Returns the mutable root node of the tree.
     pub fn root_mut(&mut self) -> Result<FdtNodeMut> {
         self.node_mut(CStr::from_bytes_with_nul(b"/\0").unwrap())?.ok_or(FdtError::Internal)
     }
 
-    /// Find a mutable tree node by its full path.
+    /// Returns a mutable tree node by its full path.
     pub fn node_mut(&mut self, path: &CStr) -> Result<Option<FdtNodeMut>> {
         Ok(self.path_offset(path)?.map(|offset| FdtNodeMut { fdt: self, offset }))
     }
 
-    /// Return the device tree as a slice (may be smaller than the containing buffer).
+    /// Returns the device tree as a slice (may be smaller than the containing buffer).
     pub fn as_slice(&self) -> &[u8] {
         &self.buffer[..self.totalsize()]
     }
@@ -820,7 +828,7 @@
         self.buffer.get(offset..(offset + len)).ok_or(FdtError::Internal)
     }
 
-    /// Return a shared pointer to the device tree.
+    /// Returns a shared pointer to the device tree.
     pub fn as_ptr(&self) -> *const c_void {
         self.buffer.as_ptr().cast::<_>()
     }
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index c91519c..db65193 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -16,6 +16,7 @@
         "android.system.virtualization.payload-rust",
         "libandroid_logger",
         "libanyhow",
+        "libapkmanifest",
         "libavflog",
         "libapexutil_rust",
         "libapkverify",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 27905c9..a576416 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -1,10 +1,10 @@
-// Copyright 2022, The Android Open Source Project
+// Copyright 2023 The Android Open Source Project
 //
 // 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
+//      http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,142 +12,44 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-//! Logic for handling the DICE values and boot operations.
-
-use anyhow::{anyhow, bail, Context, Error, Result};
-use byteorder::{NativeEndian, ReadBytesExt};
+use crate::dice_driver::DiceDriver;
+use crate::{is_debuggable, MicrodroidData};
+use anyhow::{bail, Context, Result};
 use ciborium::{cbor, ser};
-use diced_open_dice::{
-    bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
-    Hidden, InputValues, OwnedDiceArtifacts,
-};
-use keystore2_crypto::ZVec;
-use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use diced_open_dice::OwnedDiceArtifacts;
 use microdroid_metadata::PayloadMetadata;
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
-use std::fs;
-use std::os::unix::io::AsRawFd;
-use std::path::{Path, PathBuf};
-use std::ptr::null_mut;
-use std::slice;
+use openssl::sha::Sha512;
 
-/// Artifacts that are mapped into the process address space from the driver.
-pub enum DiceDriver<'a> {
-    Real {
-        driver_path: PathBuf,
-        mmap_addr: *mut c_void,
-        mmap_size: usize,
-        bcc_handover: BccHandover<'a>,
-    },
-    Fake(OwnedDiceArtifacts),
-}
-
-impl DiceDriver<'_> {
-    fn dice_artifacts(&self) -> &dyn DiceArtifacts {
-        match self {
-            Self::Real { bcc_handover, .. } => bcc_handover,
-            Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
-        }
+/// Perform an open DICE derivation for the payload.
+pub fn dice_derivation(
+    dice: DiceDriver,
+    verified_data: &MicrodroidData,
+    payload_metadata: &PayloadMetadata,
+) -> Result<OwnedDiceArtifacts> {
+    // Calculate compound digests of code and authorities
+    let mut code_hash_ctx = Sha512::new();
+    let mut authority_hash_ctx = Sha512::new();
+    code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
+    authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
+    for extra_apk in &verified_data.extra_apks_data {
+        code_hash_ctx.update(extra_apk.root_hash.as_ref());
+        authority_hash_ctx.update(extra_apk.pubkey.as_ref());
     }
-
-    pub fn new(driver_path: &Path) -> Result<Self> {
-        if driver_path.exists() {
-            log::info!("Using DICE values from driver");
-        } else if super::is_strict_boot() {
-            bail!("Strict boot requires DICE value from driver but none were found");
-        } else {
-            log::warn!("Using sample DICE values");
-            let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
-                .expect("Failed to create sample dice artifacts.");
-            return Ok(Self::Fake(dice_artifacts));
-        };
-
-        let mut file = fs::File::open(driver_path)
-            .map_err(|error| Error::new(error).context("Opening driver"))?;
-        let mmap_size =
-            file.read_u64::<NativeEndian>()
-                .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
-        // SAFETY: It's safe to map the driver as the service will only create a single
-        // mapping per process.
-        let mmap_addr = unsafe {
-            let fd = file.as_raw_fd();
-            mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
-        };
-        if mmap_addr == MAP_FAILED {
-            bail!("Failed to mmap {:?}", driver_path);
-        }
-        let mmap_buf =
-        // SAFETY: The slice is created for the region of memory that was just
-        // successfully mapped into the process address space so it will be
-        // accessible and not referenced from anywhere else.
-            unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
-        let bcc_handover =
-            bcc_handover_parse(mmap_buf).map_err(|_| anyhow!("Failed to parse Bcc Handover"))?;
-        Ok(Self::Real {
-            driver_path: driver_path.to_path_buf(),
-            mmap_addr,
-            mmap_size,
-            bcc_handover,
-        })
+    for apex in &verified_data.apex_data {
+        code_hash_ctx.update(apex.root_digest.as_ref());
+        authority_hash_ctx.update(apex.public_key.as_ref());
     }
+    let code_hash = code_hash_ctx.finish();
+    let authority_hash = authority_hash_ctx.finish();
 
-    /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
-    pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
-        // Deterministically derive a key to use for sealing data, rather than using the CDI
-        // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
-        // input key material is already cryptographically strong.
-        let mut key = ZVec::new(key_length)?;
-        let salt = &[];
-        hkdf(&mut key, Md::sha256(), self.dice_artifacts().cdi_seal(), salt, identifier)?;
-        Ok(key)
-    }
+    let config_descriptor = format_payload_config_descriptor(payload_metadata)?;
 
-    pub fn derive(
-        self,
-        code_hash: Hash,
-        config_desc: &[u8],
-        authority_hash: Hash,
-        debug: bool,
-        hidden: Hidden,
-    ) -> Result<OwnedDiceArtifacts> {
-        let input_values = InputValues::new(
-            code_hash,
-            Config::Descriptor(config_desc),
-            authority_hash,
-            if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
-            hidden,
-        );
-        let current_dice_artifacts = self.dice_artifacts();
-        let next_dice_artifacts = retry_bcc_main_flow(
-            current_dice_artifacts.cdi_attest(),
-            current_dice_artifacts.cdi_seal(),
-            current_dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?,
-            &input_values,
-        )
-        .context("DICE derive from driver")?;
-        if let Self::Real { driver_path, .. } = &self {
-            // Writing to the device wipes the artifacts. The string is ignored by the driver but
-            // included for documentation.
-            fs::write(driver_path, "wipe")
-                .map_err(|err| Error::new(err).context("Wiping driver"))?;
-        }
-        Ok(next_dice_artifacts)
-    }
-}
+    // Check debuggability, conservatively assuming it is debuggable
+    let debuggable = is_debuggable()?;
 
-impl Drop for DiceDriver<'_> {
-    fn drop(&mut self) {
-        if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
-            // SAFETY: All references to the mapped region have the same lifetime as self. Since
-            // self is being dropped, so are all the references to the mapped region meaning it's
-            // safe to unmap.
-            let ret = unsafe { munmap(mmap_addr, mmap_size) };
-            if ret != 0 {
-                log::warn!("Failed to munmap ({})", ret);
-            }
-        }
-    }
+    // Send the details to diced
+    let hidden = verified_data.salt.clone().try_into().unwrap();
+    dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
 }
 
 /// Returns a configuration descriptor of the given payload following the BCC's specification:
@@ -160,7 +62,7 @@
 /// PayloadConfig = {
 ///   1: tstr ; payload_binary_name
 /// }
-pub fn format_payload_config_descriptor(payload: &PayloadMetadata) -> Result<Vec<u8>> {
+fn format_payload_config_descriptor(payload: &PayloadMetadata) -> Result<Vec<u8>> {
     const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
 
     let config_descriptor_cbor_value = match payload {
diff --git a/microdroid_manager/src/dice_driver.rs b/microdroid_manager/src/dice_driver.rs
new file mode 100644
index 0000000..229f3e0
--- /dev/null
+++ b/microdroid_manager/src/dice_driver.rs
@@ -0,0 +1,149 @@
+// Copyright 2022, The Android Open Source Project
+//
+// 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.
+
+//! Logic for handling the DICE values and boot operations.
+
+use anyhow::{anyhow, bail, Context, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
+use diced_open_dice::{
+    bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
+    Hidden, InputValues, OwnedDiceArtifacts,
+};
+use keystore2_crypto::ZVec;
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use std::fs;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
+
+/// Artifacts that are mapped into the process address space from the driver.
+pub enum DiceDriver<'a> {
+    Real {
+        driver_path: PathBuf,
+        mmap_addr: *mut c_void,
+        mmap_size: usize,
+        bcc_handover: BccHandover<'a>,
+    },
+    Fake(OwnedDiceArtifacts),
+}
+
+impl DiceDriver<'_> {
+    fn dice_artifacts(&self) -> &dyn DiceArtifacts {
+        match self {
+            Self::Real { bcc_handover, .. } => bcc_handover,
+            Self::Fake(owned_dice_artifacts) => owned_dice_artifacts,
+        }
+    }
+
+    pub fn new(driver_path: &Path) -> Result<Self> {
+        if driver_path.exists() {
+            log::info!("Using DICE values from driver");
+        } else if super::is_strict_boot() {
+            bail!("Strict boot requires DICE value from driver but none were found");
+        } else {
+            log::warn!("Using sample DICE values");
+            let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
+                .expect("Failed to create sample dice artifacts.");
+            return Ok(Self::Fake(dice_artifacts));
+        };
+
+        let mut file = fs::File::open(driver_path)
+            .map_err(|error| Error::new(error).context("Opening driver"))?;
+        let mmap_size =
+            file.read_u64::<NativeEndian>()
+                .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+        // SAFETY: It's safe to map the driver as the service will only create a single
+        // mapping per process.
+        let mmap_addr = unsafe {
+            let fd = file.as_raw_fd();
+            mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+        };
+        if mmap_addr == MAP_FAILED {
+            bail!("Failed to mmap {:?}", driver_path);
+        }
+        let mmap_buf =
+        // SAFETY: The slice is created for the region of memory that was just
+        // successfully mapped into the process address space so it will be
+        // accessible and not referenced from anywhere else.
+            unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+        let bcc_handover =
+            bcc_handover_parse(mmap_buf).map_err(|_| anyhow!("Failed to parse Bcc Handover"))?;
+        Ok(Self::Real {
+            driver_path: driver_path.to_path_buf(),
+            mmap_addr,
+            mmap_size,
+            bcc_handover,
+        })
+    }
+
+    /// Derives a sealing key of `key_length` bytes from the DICE sealing CDI.
+    pub fn get_sealing_key(&self, identifier: &[u8], key_length: usize) -> Result<ZVec> {
+        // Deterministically derive a key to use for sealing data, rather than using the CDI
+        // directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+        // input key material is already cryptographically strong.
+        let mut key = ZVec::new(key_length)?;
+        let salt = &[];
+        hkdf(&mut key, Md::sha256(), self.dice_artifacts().cdi_seal(), salt, identifier)?;
+        Ok(key)
+    }
+
+    pub fn derive(
+        self,
+        code_hash: Hash,
+        config_desc: &[u8],
+        authority_hash: Hash,
+        debug: bool,
+        hidden: Hidden,
+    ) -> Result<OwnedDiceArtifacts> {
+        let input_values = InputValues::new(
+            code_hash,
+            Config::Descriptor(config_desc),
+            authority_hash,
+            if debug { DiceMode::kDiceModeDebug } else { DiceMode::kDiceModeNormal },
+            hidden,
+        );
+        let current_dice_artifacts = self.dice_artifacts();
+        let next_dice_artifacts = retry_bcc_main_flow(
+            current_dice_artifacts.cdi_attest(),
+            current_dice_artifacts.cdi_seal(),
+            current_dice_artifacts.bcc().ok_or_else(|| anyhow!("bcc is none"))?,
+            &input_values,
+        )
+        .context("DICE derive from driver")?;
+        if let Self::Real { driver_path, .. } = &self {
+            // Writing to the device wipes the artifacts. The string is ignored by the driver but
+            // included for documentation.
+            fs::write(driver_path, "wipe")
+                .map_err(|err| Error::new(err).context("Wiping driver"))?;
+        }
+        Ok(next_dice_artifacts)
+    }
+}
+
+impl Drop for DiceDriver<'_> {
+    fn drop(&mut self) {
+        if let &mut Self::Real { mmap_addr, mmap_size, .. } = self {
+            // SAFETY: All references to the mapped region have the same lifetime as self. Since
+            // self is being dropped, so are all the references to the mapped region meaning it's
+            // safe to unmap.
+            let ret = unsafe { munmap(mmap_addr, mmap_size) };
+            if ret != 0 {
+                log::warn!("Failed to munmap ({})", ret);
+            }
+        }
+    }
+}
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index b16a1e1..2ff04f1 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,7 +33,7 @@
 //! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
 //! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
 
-use crate::dice::DiceDriver;
+use crate::dice_driver::DiceDriver;
 use crate::ioutil;
 
 use anyhow::{anyhow, bail, Context, Result};
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index dd0ddbb..8175753 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -15,16 +15,15 @@
 //! Microdroid Manager
 
 mod dice;
+mod dice_driver;
 mod instance;
 mod ioutil;
 mod payload;
 mod swap;
+mod verify;
 mod vm_payload_service;
 mod vm_secret;
 
-use crate::dice::{DiceDriver, format_payload_config_descriptor};
-use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
-use crate::vm_payload_service::register_vm_payload_service;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
@@ -32,54 +31,46 @@
     VM_PAYLOAD_SERVICE_SOCKET_NAME,
     ENCRYPTEDSTORE_MOUNTPOINT,
 };
+
+use crate::dice::dice_derivation;
+use crate::dice_driver::DiceDriver;
+use crate::instance::{ApexData, InstanceDisk, MicrodroidData};
+use crate::verify::verify_payload;
+use crate::vm_payload_service::register_vm_payload_service;
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
-use apkverify::{get_public_key_der, verify, V4Signature};
 use binder::Strong;
-use diced_open_dice::OwnedDiceArtifacts;
-use glob::glob;
-use itertools::sorted;
+use keystore2_crypto::ZVec;
 use libc::VMADDR_CID_HOST;
 use log::{error, info};
-use keystore2_crypto::ZVec;
-use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
+use microdroid_metadata::{write_metadata, PayloadMetadata};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
 use nix::sys::signal::Signal;
-use openssl::sha::Sha512;
-use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
-use rand::Fill;
+use payload::{load_metadata, to_metadata};
 use rpcbinder::RpcSession;
 use rustutils::sockets::android_get_control_socket;
 use rustutils::system_properties;
 use rustutils::system_properties::PropertyWatcher;
 use std::borrow::Cow::{Borrowed, Owned};
-use std::convert::TryInto;
 use std::env;
 use std::ffi::CString;
-use std::fs::{self, create_dir, OpenOptions, File};
+use std::fs::{self, create_dir, File, OpenOptions};
 use std::io::{Read, Write};
+use std::os::unix::io::{FromRawFd, OwnedFd};
 use std::os::unix::process::CommandExt;
 use std::os::unix::process::ExitStatusExt;
-use std::os::unix::io::{FromRawFd, OwnedFd};
 use std::path::Path;
 use std::process::{Child, Command, Stdio};
 use std::str;
-use std::time::{Duration, SystemTime};
+use std::time::Duration;
 use vm_secret::VmSecret;
 
 const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
-const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
-const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
-const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
-const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
-const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
-const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
 const AVF_STRICT_BOOT: &str = "/sys/firmware/devicetree/base/chosen/avf,strict-boot";
 const AVF_NEW_INSTANCE: &str = "/sys/firmware/devicetree/base/chosen/avf,new-instance";
 const AVF_DEBUG_POLICY_RAMDUMP: &str = "/sys/firmware/devicetree/base/avf/guest/common/ramdump";
 const DEBUG_MICRODROID_NO_VERIFIED_BOOT: &str =
     "/sys/firmware/devicetree/base/virtualization/guest/debug-microdroid,no-verified-boot";
 
-const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
 const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
 const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
 
@@ -163,7 +154,7 @@
 
 fn main() -> Result<()> {
     // If debuggable, print full backtrace to console log with stdio_to_kmsg
-    if system_properties::read_bool(DEBUGGABLE_PROP, true)? {
+    if is_debuggable()? {
         env::set_var("RUST_BACKTRACE", "full");
     }
 
@@ -278,36 +269,6 @@
     }
     Ok(())
 }
-fn dice_derivation(
-    dice: DiceDriver,
-    verified_data: &MicrodroidData,
-    payload_metadata: &PayloadMetadata,
-) -> Result<OwnedDiceArtifacts> {
-    // Calculate compound digests of code and authorities
-    let mut code_hash_ctx = Sha512::new();
-    let mut authority_hash_ctx = Sha512::new();
-    code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
-    authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
-    for extra_apk in &verified_data.extra_apks_data {
-        code_hash_ctx.update(extra_apk.root_hash.as_ref());
-        authority_hash_ctx.update(extra_apk.pubkey.as_ref());
-    }
-    for apex in &verified_data.apex_data {
-        code_hash_ctx.update(apex.root_digest.as_ref());
-        authority_hash_ctx.update(apex.public_key.as_ref());
-    }
-    let code_hash = code_hash_ctx.finish();
-    let authority_hash = authority_hash_ctx.finish();
-
-    let config_descriptor = format_payload_config_descriptor(payload_metadata)?;
-
-    // Check debuggability, conservatively assuming it is debuggable
-    let debuggable = system_properties::read_bool(DEBUGGABLE_PROP, true)?;
-
-    // Send the details to diced
-    let hidden = verified_data.salt.clone().try_into().unwrap();
-    dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
-}
 
 fn is_strict_boot() -> bool {
     Path::new(AVF_STRICT_BOOT).exists()
@@ -321,10 +282,14 @@
     !Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
 }
 
+fn is_debuggable() -> Result<bool> {
+    Ok(system_properties::read_bool(DEBUGGABLE_PROP, true)?)
+}
+
 fn should_export_tombstones(config: &VmPayloadConfig) -> bool {
     match config.export_tombstones {
         Some(b) => b,
-        None => system_properties::read_bool(DEBUGGABLE_PROP, true).unwrap_or(false),
+        None => is_debuggable().unwrap_or(false),
     }
 }
 
@@ -424,7 +389,7 @@
     zipfuse.mount(
         MountForExec::Allowed,
         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
-        Path::new("/dev/block/mapper/microdroid-apk"),
+        Path::new(verify::DM_MOUNTED_APK_PATH),
         Path::new(VM_APK_CONTENTS_PATH),
         "microdroid_manager.apk.mounted".to_owned(),
     )?;
@@ -490,28 +455,6 @@
     exec_task(task, service).context("Failed to run payload")
 }
 
-struct ApkDmverityArgument<'a> {
-    apk: &'a str,
-    idsig: &'a str,
-    name: &'a str,
-    saved_root_hash: Option<&'a RootHash>,
-}
-
-fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
-    let mut cmd = Command::new(APKDMVERITY_BIN);
-
-    for argument in args {
-        cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
-        if let Some(root_hash) = argument.saved_root_hash {
-            cmd.arg(&to_hex_string(root_hash));
-        } else {
-            cmd.arg("none");
-        }
-    }
-
-    cmd.spawn().context("Spawn apkdmverity")
-}
-
 enum MountForExec {
     Allowed,
     Disallowed,
@@ -582,147 +525,6 @@
     Ok(())
 }
 
-// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
-// when the root_hash values from the idsig file and the instance disk are different. This function
-// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
-// saved to the instance disk.
-fn verify_payload(
-    metadata: &Metadata,
-    saved_data: Option<&MicrodroidData>,
-) -> Result<MicrodroidData> {
-    let start_time = SystemTime::now();
-
-    // Verify main APK
-    let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
-    let root_hash_trustful =
-        saved_data.map(|d| d.apk_data.root_hash_eq(root_hash_from_idsig.as_ref())).unwrap_or(false);
-
-    // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
-    // instead of the value read from the idsig file.
-    let main_apk_argument = {
-        ApkDmverityArgument {
-            apk: MAIN_APK_PATH,
-            idsig: MAIN_APK_IDSIG_PATH,
-            name: MAIN_APK_DEVICE_NAME,
-            saved_root_hash: if root_hash_trustful {
-                Some(root_hash_from_idsig.as_ref())
-            } else {
-                None
-            },
-        }
-    };
-    let mut apkdmverity_arguments = vec![main_apk_argument];
-
-    // Verify extra APKs
-    // For now, we can't read the payload config, so glob APKs and idsigs.
-    // Later, we'll see if it matches with the payload config.
-
-    // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
-    // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
-    let extra_apks =
-        sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
-    let extra_idsigs =
-        sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
-    ensure!(
-        extra_apks.len() == extra_idsigs.len(),
-        "Extra apks/idsigs mismatch: {} apks but {} idsigs",
-        extra_apks.len(),
-        extra_idsigs.len()
-    );
-
-    let extra_root_hashes_from_idsig: Vec<_> = extra_idsigs
-        .iter()
-        .map(|idsig| {
-            get_apk_root_hash_from_idsig(idsig).expect("Can't find root hash from extra idsig")
-        })
-        .collect();
-
-    let extra_root_hashes_trustful: Vec<_> = if let Some(data) = saved_data {
-        extra_root_hashes_from_idsig
-            .iter()
-            .enumerate()
-            .map(|(i, root_hash)| data.extra_apk_root_hash_eq(i, root_hash))
-            .collect()
-    } else {
-        vec![false; extra_root_hashes_from_idsig.len()]
-    };
-    let extra_apk_names: Vec<_> =
-        (0..extra_apks.len()).map(|i| format!("extra-apk-{}", i)).collect();
-
-    for (i, extra_apk) in extra_apks.iter().enumerate() {
-        apkdmverity_arguments.push({
-            ApkDmverityArgument {
-                apk: extra_apk.to_str().unwrap(),
-                idsig: extra_idsigs[i].to_str().unwrap(),
-                name: &extra_apk_names[i],
-                saved_root_hash: if extra_root_hashes_trustful[i] {
-                    Some(&extra_root_hashes_from_idsig[i])
-                } else {
-                    None
-                },
-            }
-        });
-    }
-
-    // Start apkdmverity and wait for the dm-verify block
-    let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
-
-    // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
-    // APEX payload.
-    let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
-
-    // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
-    // Skip writing it if the debug policy ignoring identity is on
-    if is_verified_boot() {
-        write_apex_payload_data(saved_data, &apex_data_from_payload)?;
-    }
-
-    // Start apexd to activate APEXes
-    system_properties::write("ctl.start", "apexd-vm")?;
-
-    // TODO(inseob): add timeout
-    apkdmverity_child.wait()?;
-
-    // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
-    // the APK file and therefore can be very slow if the APK is large. Note that this step is
-    // taken only when the root_hash is un-trustful which can be either when this is the first boot
-    // of the VM or APK was updated in the host.
-    // TODO(jooyung): consider multithreading to make this faster
-    let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
-    let extra_apks_data = extra_root_hashes_from_idsig
-        .into_iter()
-        .enumerate()
-        .map(|(i, extra_root_hash)| {
-            let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
-            let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
-            Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
-        })
-        .collect::<Result<Vec<_>>>()?;
-
-    info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
-
-    // Use the salt from a verified instance, or generate a salt for a new instance.
-    let salt = if let Some(saved_data) = saved_data {
-        saved_data.salt.clone()
-    } else if is_strict_boot() {
-        // No need to add more entropy as a previous stage must have used a new, random salt.
-        vec![0u8; 64]
-    } else {
-        let mut salt = vec![0u8; 64];
-        salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
-        salt
-    };
-
-    // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
-    // fully verifying the APK or by comparing it with the saved root_hash.
-    Ok(MicrodroidData {
-        salt,
-        apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
-        extra_apks_data,
-        apex_data: apex_data_from_payload,
-    })
-}
-
 fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
     // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
     for i in 0..config.extra_apks.len() {
@@ -770,28 +572,6 @@
     Ok(())
 }
 
-fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
-    Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
-}
-
-fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
-    let current_sdk = get_current_sdk()?;
-    if !root_hash_trustful {
-        verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
-            "failed to verify {}",
-            apk
-        )))
-    } else {
-        get_public_key_der(apk, current_sdk)
-    }
-}
-
-fn get_current_sdk() -> Result<u32> {
-    let current_sdk = system_properties::read("ro.build.version.sdk")?;
-    let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
-    current_sdk.parse().context("Malformed SDK version")
-}
-
 fn load_config(payload_metadata: PayloadMetadata) -> Result<VmPayloadConfig> {
     match payload_metadata {
         PayloadMetadata::ConfigPath(path) => {
@@ -831,7 +611,7 @@
         return Ok(());
     }
 
-    let debuggable = system_properties::read_bool(DEBUGGABLE_PROP, true)?;
+    let debuggable = is_debuggable()?;
     let ramdump = get_debug_policy_bool(AVF_DEBUG_POLICY_RAMDUMP)?.unwrap_or_default();
     let requested = debuggable | ramdump;
 
@@ -908,10 +688,6 @@
     Ok(path)
 }
 
-fn to_hex_string(buf: &[u8]) -> String {
-    buf.iter().map(|b| format!("{:02X}", b)).collect()
-}
-
 fn prepare_encryptedstore(vm_secret: &VmSecret) -> Result<Child> {
     let mut key = ZVec::new(ENCRYPTEDSTORE_KEYSIZE)?;
     vm_secret.derive_encryptedstore_key(&mut key)?;
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
new file mode 100644
index 0000000..06b15f7
--- /dev/null
+++ b/microdroid_manager/src/verify.rs
@@ -0,0 +1,236 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::instance::{ApkData, MicrodroidData, RootHash};
+use crate::payload::get_apex_data_from_payload;
+use crate::{is_strict_boot, is_verified_boot, write_apex_payload_data, MicrodroidError};
+use anyhow::{anyhow, ensure, Context, Result};
+use apkmanifest::get_manifest_info;
+use apkverify::{get_public_key_der, verify, V4Signature};
+use glob::glob;
+use itertools::sorted;
+use log::{info, warn};
+use microdroid_metadata::Metadata;
+use rand::Fill;
+use rustutils::system_properties;
+use std::path::Path;
+use std::process::{Child, Command};
+use std::str;
+use std::time::SystemTime;
+
+pub const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
+
+const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
+const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
+const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
+const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
+const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
+
+const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
+
+/// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
+/// when the root_hash values from the idsig file and the instance disk are different. This function
+/// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
+/// saved to the instance disk.
+pub fn verify_payload(
+    metadata: &Metadata,
+    saved_data: Option<&MicrodroidData>,
+) -> Result<MicrodroidData> {
+    let start_time = SystemTime::now();
+
+    // Verify main APK
+    let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
+    let root_hash_trustful =
+        saved_data.map(|d| d.apk_data.root_hash_eq(root_hash_from_idsig.as_ref())).unwrap_or(false);
+
+    // If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
+    // instead of the value read from the idsig file.
+    let main_apk_argument = {
+        ApkDmverityArgument {
+            apk: MAIN_APK_PATH,
+            idsig: MAIN_APK_IDSIG_PATH,
+            name: MAIN_APK_DEVICE_NAME,
+            saved_root_hash: if root_hash_trustful {
+                Some(root_hash_from_idsig.as_ref())
+            } else {
+                None
+            },
+        }
+    };
+    let mut apkdmverity_arguments = vec![main_apk_argument];
+
+    // Verify extra APKs
+    // For now, we can't read the payload config, so glob APKs and idsigs.
+    // Later, we'll see if it matches with the payload config.
+
+    // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
+    // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
+    let extra_apks =
+        sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
+    let extra_idsigs =
+        sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
+    ensure!(
+        extra_apks.len() == extra_idsigs.len(),
+        "Extra apks/idsigs mismatch: {} apks but {} idsigs",
+        extra_apks.len(),
+        extra_idsigs.len()
+    );
+
+    let extra_root_hashes_from_idsig: Vec<_> = extra_idsigs
+        .iter()
+        .map(|idsig| {
+            get_apk_root_hash_from_idsig(idsig).expect("Can't find root hash from extra idsig")
+        })
+        .collect();
+
+    let extra_root_hashes_trustful: Vec<_> = if let Some(data) = saved_data {
+        extra_root_hashes_from_idsig
+            .iter()
+            .enumerate()
+            .map(|(i, root_hash)| data.extra_apk_root_hash_eq(i, root_hash))
+            .collect()
+    } else {
+        vec![false; extra_root_hashes_from_idsig.len()]
+    };
+    let extra_apk_names: Vec<_> =
+        (0..extra_apks.len()).map(|i| format!("extra-apk-{}", i)).collect();
+
+    for (i, extra_apk) in extra_apks.iter().enumerate() {
+        apkdmverity_arguments.push({
+            ApkDmverityArgument {
+                apk: extra_apk.to_str().unwrap(),
+                idsig: extra_idsigs[i].to_str().unwrap(),
+                name: &extra_apk_names[i],
+                saved_root_hash: if extra_root_hashes_trustful[i] {
+                    Some(&extra_root_hashes_from_idsig[i])
+                } else {
+                    None
+                },
+            }
+        });
+    }
+
+    // Start apkdmverity and wait for the dm-verify block
+    let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
+
+    // While waiting for apkdmverity to mount APK, gathers public keys and root digests from
+    // APEX payload.
+    let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
+
+    // Writing /apex/vm-payload-metadata is to verify that the payload isn't changed.
+    // Skip writing it if the debug policy ignoring identity is on
+    if is_verified_boot() {
+        write_apex_payload_data(saved_data, &apex_data_from_payload)?;
+    }
+
+    // Start apexd to activate APEXes
+    system_properties::write("ctl.start", "apexd-vm")?;
+
+    // TODO(inseob): add timeout
+    apkdmverity_child.wait()?;
+
+    // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
+    // the APK file and therefore can be very slow if the APK is large. Note that this step is
+    // taken only when the root_hash is un-trustful which can be either when this is the first boot
+    // of the VM or APK was updated in the host.
+    // TODO(jooyung): consider multithreading to make this faster
+    let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
+    let extra_apks_data = extra_root_hashes_from_idsig
+        .into_iter()
+        .enumerate()
+        .map(|(i, extra_root_hash)| {
+            let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
+            let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
+            Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
+        })
+        .collect::<Result<Vec<_>>>()?;
+
+    info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
+
+    // Use the salt from a verified instance, or generate a salt for a new instance.
+    let salt = if let Some(saved_data) = saved_data {
+        saved_data.salt.clone()
+    } else if is_strict_boot() {
+        // No need to add more entropy as a previous stage must have used a new, random salt.
+        vec![0u8; 64]
+    } else {
+        let mut salt = vec![0u8; 64];
+        salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
+        salt
+    };
+
+    // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
+    // fully verifying the APK or by comparing it with the saved root_hash.
+    Ok(MicrodroidData {
+        salt,
+        apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
+        extra_apks_data,
+        apex_data: apex_data_from_payload,
+    })
+}
+
+fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
+    Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
+}
+
+fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
+    let current_sdk = get_current_sdk()?;
+
+    let public_key_der = if !root_hash_trustful {
+        verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
+            "failed to verify {}",
+            apk
+        )))?
+    } else {
+        get_public_key_der(apk, current_sdk)?
+    };
+
+    match get_manifest_info(apk) {
+        Ok(manifest_info) => {
+            // TODO (b/299591171): Do something with this info
+            info!("Manifest info is {manifest_info:?}")
+        }
+        Err(e) => warn!("Failed to read manifest info from APK: {e:?}"),
+    };
+
+    Ok(public_key_der)
+}
+
+fn get_current_sdk() -> Result<u32> {
+    let current_sdk = system_properties::read("ro.build.version.sdk")?;
+    let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
+    current_sdk.parse().context("Malformed SDK version")
+}
+
+struct ApkDmverityArgument<'a> {
+    apk: &'a str,
+    idsig: &'a str,
+    name: &'a str,
+    saved_root_hash: Option<&'a RootHash>,
+}
+
+fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
+    let mut cmd = Command::new(APKDMVERITY_BIN);
+
+    for argument in args {
+        cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
+        if let Some(root_hash) = argument.saved_root_hash {
+            cmd.arg(&hex::encode(root_hash));
+        } else {
+            cmd.arg("none");
+        }
+    }
+
+    cmd.spawn().context("Spawn apkdmverity")
+}
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 47a2492..78b6323 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -23,11 +23,11 @@
 use log::{info, warn};
 use static_assertions::const_assert_eq;
 use vmbase::util::RangeExt;
-use zerocopy::{FromBytes, LayoutVerified};
+use zerocopy::{FromBytes, FromZeroes, LayoutVerified};
 
 /// Configuration data header.
 #[repr(C, packed)]
-#[derive(Clone, Copy, Debug, FromBytes)]
+#[derive(Clone, Copy, Debug, FromZeroes, FromBytes)]
 struct Header {
     /// Magic number; must be `Header::MAGIC`.
     magic: u32,
@@ -132,14 +132,14 @@
 }
 
 #[repr(packed)]
-#[derive(Clone, Copy, Debug, FromBytes)]
+#[derive(Clone, Copy, Debug, FromZeroes, FromBytes)]
 struct HeaderEntry {
     offset: u32,
     size: u32,
 }
 
 #[repr(C, packed)]
-#[derive(Clone, Copy, Debug, Eq, FromBytes, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, FromZeroes, FromBytes, PartialEq)]
 pub struct Version {
     minor: u16,
     major: u16,
diff --git a/pvmfw/src/gpt.rs b/pvmfw/src/gpt.rs
index 06bf994..71eb569 100644
--- a/pvmfw/src/gpt.rs
+++ b/pvmfw/src/gpt.rs
@@ -26,6 +26,7 @@
 use vmbase::util::ceiling_div;
 use vmbase::virtio::{pci, HalImpl};
 use zerocopy::FromBytes;
+use zerocopy::FromZeroes;
 
 type VirtIOBlk = pci::VirtIOBlk<HalImpl>;
 
@@ -156,7 +157,7 @@
 type Lba = u64;
 
 /// Structure as defined in release 2.10 of the UEFI Specification (5.3.2 GPT Header).
-#[derive(FromBytes)]
+#[derive(FromZeroes, FromBytes)]
 #[repr(C, packed)]
 struct Header {
     signature: u64,
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index 22839cb..f2cd6a3 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -35,6 +35,7 @@
 use vmbase::virtio::HalImpl;
 use zerocopy::AsBytes;
 use zerocopy::FromBytes;
+use zerocopy::FromZeroes;
 
 pub enum Error {
     /// Unexpected I/O error while accessing the underlying disk.
@@ -174,7 +175,7 @@
     }
 }
 
-#[derive(FromBytes)]
+#[derive(FromZeroes, FromBytes)]
 #[repr(C, packed)]
 struct Header {
     magic: [u8; Header::MAGIC.len()],
@@ -258,7 +259,7 @@
 /// Marks the start of an instance.img entry.
 ///
 /// Note: Virtualization/microdroid_manager/src/instance.rs uses the name "partition".
-#[derive(AsBytes, FromBytes)]
+#[derive(AsBytes, FromZeroes, FromBytes)]
 #[repr(C, packed)]
 struct EntryHeader {
     uuid: u128,
@@ -279,7 +280,7 @@
     }
 }
 
-#[derive(AsBytes, FromBytes)]
+#[derive(AsBytes, FromZeroes, FromBytes)]
 #[repr(C)]
 struct EntryBody {
     code_hash: Hash,
diff --git a/rialto/src/communication.rs b/rialto/src/communication.rs
index 50722f2..1b94912 100644
--- a/rialto/src/communication.rs
+++ b/rialto/src/communication.rs
@@ -100,7 +100,16 @@
     }
 
     fn recv(&mut self, buffer: &mut [u8]) -> virtio_drivers::Result<usize> {
-        self.connection_manager.recv(self.peer_addr, self.peer_addr.port, buffer)
+        let bytes_read =
+            self.connection_manager.recv(self.peer_addr, self.peer_addr.port, buffer)?;
+
+        let buffer_available_bytes = self
+            .connection_manager
+            .recv_buffer_available_bytes(self.peer_addr, self.peer_addr.port)?;
+        if buffer_available_bytes == 0 && bytes_read > 0 {
+            self.connection_manager.update_credit(self.peer_addr, self.peer_addr.port)?;
+        }
+        Ok(bytes_read)
     }
 
     fn wait_for_send(&mut self, buffer: &[u8]) -> virtio_drivers::Result {
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 6a6dcf4..e13b7a1 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -57,9 +57,7 @@
 }
 
 fn check_processing_reverse_request(vm: &mut ServiceVm) -> Result<()> {
-    // TODO(b/292080257): Test with message longer than the receiver's buffer capacity
-    // 1024 bytes once the guest virtio-vsock driver fixes the credit update in recv().
-    let message = "abc".repeat(166);
+    let message = "abc".repeat(500);
     let request = Request::Reverse(message.as_bytes().to_vec());
 
     let response = vm.process_request(request)?;
@@ -77,8 +75,9 @@
     info!("Received response: {response:?}.");
 
     match response {
-        Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, .. }) => {
-            assert_array_has_nonzero(&maced_public_key[..]);
+        Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, key_blob }) => {
+            assert_array_has_nonzero(&maced_public_key);
+            assert_array_has_nonzero(&key_blob);
             Ok(maced_public_key)
         }
         _ => bail!("Incorrect response type: {response:?}"),
diff --git a/libs/dice_policy/Android.bp b/secretkeeper/dice_policy/Android.bp
similarity index 100%
rename from libs/dice_policy/Android.bp
rename to secretkeeper/dice_policy/Android.bp
diff --git a/libs/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
similarity index 100%
rename from libs/dice_policy/src/lib.rs
rename to secretkeeper/dice_policy/src/lib.rs
diff --git a/libs/dice_policy/testdata/composbcc b/secretkeeper/dice_policy/testdata/composbcc
similarity index 100%
rename from libs/dice_policy/testdata/composbcc
rename to secretkeeper/dice_policy/testdata/composbcc
Binary files differ
diff --git a/service_vm/requests/Android.bp b/service_vm/requests/Android.bp
index 4b9b46f..f85064a 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -3,7 +3,7 @@
 }
 
 rust_defaults {
-    name: "libservice_vm_requests_defaults",
+    name: "libservice_vm_requests_nostd_defaults",
     crate_name: "service_vm_requests",
     defaults: ["avf_build_flags_rust"],
     srcs: ["src/lib.rs"],
@@ -11,11 +11,6 @@
     apex_available: [
         "com.android.virt",
     ],
-}
-
-rust_library_rlib {
-    name: "libservice_vm_requests_nostd",
-    defaults: ["libservice_vm_requests_defaults"],
     no_stdlibs: true,
     stdlibs: [
         "libcore.rust_sysroot",
@@ -32,3 +27,14 @@
         "libzeroize_nostd",
     ],
 }
+
+rust_library_rlib {
+    name: "libservice_vm_requests_nostd",
+    defaults: ["libservice_vm_requests_nostd_defaults"],
+}
+
+rust_test {
+    name: "libservice_vm_requests.test",
+    defaults: ["libservice_vm_requests_nostd_defaults"],
+    test_suites: ["general-tests"],
+}
diff --git a/service_vm/requests/TEST_MAPPING b/service_vm/requests/TEST_MAPPING
new file mode 100644
index 0000000..c95f9e3
--- /dev/null
+++ b/service_vm/requests/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+  "avf-presubmit" : [
+    {
+      "name" : "libservice_vm_requests.test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/service_vm/requests/src/cbor.rs b/service_vm/requests/src/cbor.rs
new file mode 100644
index 0000000..36492e5
--- /dev/null
+++ b/service_vm/requests/src/cbor.rs
@@ -0,0 +1,37 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Utility functions for CBOR serialization/deserialization.
+
+use alloc::vec::Vec;
+use coset::{CoseError, Result};
+use serde::{de::DeserializeOwned, Serialize};
+
+/// Serializes the given data to a CBOR-encoded byte vector.
+pub(crate) fn serialize<T: ?Sized + Serialize>(v: &T) -> Result<Vec<u8>> {
+    let mut data = Vec::new();
+    ciborium::into_writer(v, &mut data)?;
+    Ok(data)
+}
+
+/// Deserializes the given type from a CBOR-encoded byte slice, failing if any extra
+/// data remains after the type has been read.
+pub(crate) fn deserialize<T: DeserializeOwned>(mut data: &[u8]) -> Result<T> {
+    let res = ciborium::from_reader(&mut data)?;
+    if data.is_empty() {
+        Ok(res)
+    } else {
+        Err(CoseError::ExtraneousData)
+    }
+}
diff --git a/service_vm/requests/src/keyblob.rs b/service_vm/requests/src/keyblob.rs
new file mode 100644
index 0000000..a714edd
--- /dev/null
+++ b/service_vm/requests/src/keyblob.rs
@@ -0,0 +1,158 @@
+// Copyright 2023, The Android Open Source Project
+//
+// 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.
+
+//! Handles the encryption and decryption of the key blob.
+
+use crate::cbor;
+use alloc::vec;
+use alloc::vec::Vec;
+use bssl_avf::{hkdf, rand_bytes, Aead, AeadContext, Digester, AES_GCM_NONCE_LENGTH};
+use core::result;
+use serde::{Deserialize, Serialize};
+use service_vm_comm::RequestProcessingError;
+// TODO(b/241428146): This will be used once the retrieval mechanism is available.
+#[cfg(test)]
+use zeroize::Zeroizing;
+
+type Result<T> = result::Result<T, RequestProcessingError>;
+
+/// The KEK (Key Encryption Key) info is used as information to derive the KEK using HKDF.
+const KEK_INFO: &[u8] = b"rialto keyblob kek";
+
+/// An all-zero nonce is utilized to encrypt the private key. This is because each key
+/// undergoes encryption using a distinct KEK, which is derived from a secret and a random
+/// salt. Since the uniqueness of the IV/key combination is already guaranteed by the uniqueness
+/// of the KEK, there is no need for an additional random nonce.
+const PRIVATE_KEY_NONCE: &[u8; AES_GCM_NONCE_LENGTH] = &[0; AES_GCM_NONCE_LENGTH];
+
+/// Since Rialto functions as both the sender and receiver of the message, no additional data is
+/// needed.
+const PRIVATE_KEY_AD: &[u8] = &[];
+
+// Encrypted key blob.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub(crate) enum EncryptedKeyBlob {
+    /// Version 1 key blob.
+    V1(EncryptedKeyBlobV1),
+}
+
+/// Encrypted key blob version 1.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub(crate) struct EncryptedKeyBlobV1 {
+    /// Salt used to derive the KEK.
+    kek_salt: [u8; 32],
+
+    /// Private key encrypted with AES-256-GCM.
+    encrypted_private_key: Vec<u8>,
+}
+
+impl EncryptedKeyBlob {
+    pub(crate) fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> {
+        EncryptedKeyBlobV1::new(private_key, kek_secret).map(Self::V1)
+    }
+
+    // TODO(b/241428146): Use this function to decrypt the retrieved keyblob once the retrieval
+    // mechanism is available.
+    #[cfg(test)]
+    pub(crate) fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
+        match self {
+            Self::V1(blob) => blob.decrypt_private_key(kek_secret),
+        }
+    }
+
+    // TODO(b/241428146): This function will be used once the retrieval mechanism is available.
+    #[cfg(test)]
+    pub(crate) fn from_cbor_slice(slice: &[u8]) -> coset::Result<Self> {
+        cbor::deserialize(slice)
+    }
+
+    pub(crate) fn to_cbor_vec(&self) -> coset::Result<Vec<u8>> {
+        cbor::serialize(&self)
+    }
+}
+
+impl EncryptedKeyBlobV1 {
+    fn new(private_key: &[u8], kek_secret: &[u8]) -> Result<Self> {
+        let mut kek_salt = [0u8; 32];
+        rand_bytes(&mut kek_salt)?;
+        let kek = hkdf::<32>(kek_secret, &kek_salt, KEK_INFO, Digester::sha512())?;
+
+        let tag_len = None;
+        let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?;
+        let mut out = vec![0u8; private_key.len() + aead_ctx.aead().max_overhead()];
+        let ciphertext = aead_ctx.seal(private_key, PRIVATE_KEY_NONCE, PRIVATE_KEY_AD, &mut out)?;
+
+        Ok(Self { kek_salt, encrypted_private_key: ciphertext.to_vec() })
+    }
+
+    #[cfg(test)]
+    fn decrypt_private_key(&self, kek_secret: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
+        let kek = hkdf::<32>(kek_secret, &self.kek_salt, KEK_INFO, Digester::sha512())?;
+        let mut out = Zeroizing::new(vec![0u8; self.encrypted_private_key.len()]);
+        let tag_len = None;
+        let aead_ctx = AeadContext::new(Aead::aes_256_gcm(), kek.as_slice(), tag_len)?;
+        let plaintext = aead_ctx.open(
+            &self.encrypted_private_key,
+            PRIVATE_KEY_NONCE,
+            PRIVATE_KEY_AD,
+            &mut out,
+        )?;
+        Ok(Zeroizing::new(plaintext.to_vec()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use bssl_avf::{ApiName, CipherError, Error};
+
+    /// The test data are generated randomly with /dev/urandom.
+    const TEST_KEY: [u8; 32] = [
+        0x76, 0xf7, 0xd5, 0x36, 0x1f, 0x78, 0x58, 0x2e, 0x55, 0x2f, 0x88, 0x9d, 0xa3, 0x3e, 0xba,
+        0xfb, 0xc1, 0x2b, 0x17, 0x85, 0x24, 0xdc, 0x0e, 0xc4, 0xbf, 0x6d, 0x2e, 0xe8, 0xa8, 0x36,
+        0x93, 0x62,
+    ];
+    const TEST_SECRET1: [u8; 32] = [
+        0xac, 0xb1, 0x6b, 0xdf, 0x45, 0x30, 0x20, 0xa5, 0x60, 0x6d, 0x81, 0x07, 0x30, 0x68, 0x6e,
+        0x01, 0x3d, 0x5e, 0x86, 0xd6, 0xc6, 0x17, 0xfa, 0xd6, 0xe0, 0xff, 0xd4, 0xf0, 0xb0, 0x7c,
+        0x5c, 0x8f,
+    ];
+    const TEST_SECRET2: [u8; 32] = [
+        0x04, 0x6e, 0xca, 0x30, 0x5e, 0x6c, 0x8f, 0xe5, 0x1a, 0x47, 0x12, 0xbc, 0x45, 0xd7, 0xa8,
+        0x38, 0xfb, 0x06, 0xc6, 0x44, 0xa1, 0x21, 0x40, 0x0b, 0x48, 0x88, 0xe2, 0x31, 0x64, 0x42,
+        0x9d, 0x1c,
+    ];
+
+    #[test]
+    fn decrypting_keyblob_succeeds_with_the_same_kek() -> Result<()> {
+        let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
+        let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
+        let decrypted_key = encrypted_key_blob.decrypt_private_key(&TEST_SECRET1)?;
+
+        assert_eq!(TEST_KEY, decrypted_key.as_slice());
+        Ok(())
+    }
+
+    #[test]
+    fn decrypting_keyblob_fails_with_a_different_kek() -> Result<()> {
+        let encrypted_key_blob = EncryptedKeyBlob::new(&TEST_KEY, &TEST_SECRET1)?.to_cbor_vec()?;
+        let encrypted_key_blob = EncryptedKeyBlob::from_cbor_slice(&encrypted_key_blob)?;
+        let err = encrypted_key_blob.decrypt_private_key(&TEST_SECRET2).unwrap_err();
+
+        let expected_err: RequestProcessingError =
+            Error::CallFailed(ApiName::EVP_AEAD_CTX_open, CipherError::BadDecrypt.into()).into();
+        assert_eq!(expected_err, err);
+        Ok(())
+    }
+}
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index fc0c87d..6fa6e0b 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -19,6 +19,8 @@
 extern crate alloc;
 
 mod api;
+mod cbor;
+mod keyblob;
 mod pub_key;
 mod rkp;
 
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index f96b85d..933737c 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -15,7 +15,9 @@
 //! This module contains functions related to the attestation of the
 //! service VM via the RKP (Remote Key Provisioning) server.
 
-use super::pub_key::{build_maced_public_key, validate_public_key};
+use crate::cbor;
+use crate::keyblob::EncryptedKeyBlob;
+use crate::pub_key::{build_maced_public_key, validate_public_key};
 use alloc::string::String;
 use alloc::vec;
 use alloc::vec::Vec;
@@ -23,7 +25,7 @@
 use ciborium::{cbor, value::Value};
 use core::result;
 use coset::{iana, AsCborValue, CoseSign1, CoseSign1Builder, HeaderBuilder};
-use diced_open_dice::{kdf, keypair_from_seed, sign, DiceArtifacts, PrivateKey};
+use diced_open_dice::{derive_cdi_leaf_priv, kdf, sign, DiceArtifacts, PrivateKey};
 use log::error;
 use service_vm_comm::{EcdsaP256KeyPair, GenerateCertificateRequestParams, RequestProcessingError};
 use zeroize::Zeroizing;
@@ -36,7 +38,7 @@
     0x82, 0x80, 0xFA, 0xD3, 0xA8, 0x0A, 0x9A, 0x4B, 0xF7, 0xA5, 0x7D, 0x7B, 0xE9, 0xC3, 0xAB, 0x13,
     0x89, 0xDC, 0x7B, 0x46, 0xEE, 0x71, 0x22, 0xB4, 0x5F, 0x4C, 0x3F, 0xE2, 0x40, 0x04, 0x3B, 0x6C,
 ];
-const HMAC_KEY_INFO: &[u8] = b"rialto hmac key";
+const HMAC_KEY_INFO: &[u8] = b"rialto hmac wkey";
 const HMAC_KEY_LENGTH: usize = 32;
 
 pub(super) fn generate_ecdsa_p256_key_pair(
@@ -44,13 +46,12 @@
 ) -> Result<EcdsaP256KeyPair> {
     let hmac_key = derive_hmac_key(dice_artifacts)?;
     let ec_key = EcKey::new_p256()?;
+
     let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
+    let key_blob =
+        EncryptedKeyBlob::new(ec_key.private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
 
-    // TODO(b/279425980): Encrypt the private key in a key blob.
-    // Remove the printing of the private key.
-    log::debug!("Private key: {:?}", ec_key.private_key()?.as_slice());
-
-    let key_pair = EcdsaP256KeyPair { maced_public_key, key_blob: Vec::new() };
+    let key_pair = EcdsaP256KeyPair { maced_public_key, key_blob: key_blob.to_cbor_vec()? };
     Ok(key_pair)
 }
 
@@ -80,7 +81,7 @@
         // TODO(b/299256925): Add device info in CBOR format here.
         Value::Array(public_keys),
     ])?;
-    let csr_payload = cbor_to_vec(&csr_payload)?;
+    let csr_payload = cbor::serialize(&csr_payload)?;
 
     // Builds `SignedData`.
     let signed_data_payload =
@@ -91,17 +92,15 @@
     // Currently `UdsCerts` is left empty because it is only needed for Samsung devices.
     // Check http://b/301574013#comment3 for more information.
     let uds_certs = Value::Map(Vec::new());
-    let dice_cert_chain = dice_artifacts
-        .bcc()
-        .map(read_to_value)
-        .ok_or(RequestProcessingError::MissingDiceChain)??;
+    let dice_cert_chain = dice_artifacts.bcc().ok_or(RequestProcessingError::MissingDiceChain)?;
+    let dice_cert_chain: Value = cbor::deserialize(dice_cert_chain)?;
     let auth_req = cbor!([
         Value::Integer(AUTH_REQ_SCHEMA_V1.into()),
         uds_certs,
         dice_cert_chain,
         signed_data,
     ])?;
-    cbor_to_vec(&auth_req)
+    Ok(cbor::serialize(&auth_req)?)
 }
 
 fn derive_hmac_key(dice_artifacts: &dyn DiceArtifacts) -> Result<Zeroizing<[u8; HMAC_KEY_LENGTH]>> {
@@ -123,17 +122,12 @@
     let protected = HeaderBuilder::new().algorithm(signing_algorithm).build();
     let signed_data = CoseSign1Builder::new()
         .protected(protected)
-        .payload(cbor_to_vec(payload)?)
+        .payload(cbor::serialize(payload)?)
         .try_create_signature(&[], |message| sign_message(message, &cdi_leaf_priv))?
         .build();
     Ok(signed_data)
 }
 
-fn derive_cdi_leaf_priv(dice_artifacts: &dyn DiceArtifacts) -> diced_open_dice::Result<PrivateKey> {
-    let (_, private_key) = keypair_from_seed(dice_artifacts.cdi_attest())?;
-    Ok(private_key)
-}
-
 fn sign_message(message: &[u8], private_key: &PrivateKey) -> Result<Vec<u8>> {
     Ok(sign(message, private_key.as_array())
         .map_err(|e| {
@@ -142,24 +136,3 @@
         })?
         .to_vec())
 }
-
-fn cbor_to_vec(v: &Value) -> Result<Vec<u8>> {
-    let mut data = Vec::new();
-    ciborium::into_writer(v, &mut data).map_err(coset::CoseError::from)?;
-    Ok(data)
-}
-
-/// Read a CBOR `Value` from a byte slice, failing if any extra data remains
-/// after the `Value` has been read.
-fn read_to_value(mut data: &[u8]) -> Result<Value> {
-    let value = ciborium::from_reader(&mut data).map_err(|e| {
-        error!("Failed to deserialize the data into CBOR value: {e}");
-        RequestProcessingError::CborValueError
-    })?;
-    if data.is_empty() {
-        Ok(value)
-    } else {
-        error!("CBOR input has extra data.");
-        Err(RequestProcessingError::CborValueError)
-    }
-}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index 80fdff7..657241c 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -14,7 +14,7 @@
         "androidx.test.runner",
         "androidx.test.ext.junit",
         "com.android.microdroid.testservice-java",
-        "truth-prebuilt",
+        "truth",
     ],
     jni_libs: [
         "MicrodroidBenchmarkNativeLib",
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 6f07efd..614c70c 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -17,7 +17,7 @@
         "androidx.test.ext.junit",
         "com.android.microdroid.testservice-java",
         "MicrodroidTestHelper",
-        "truth-prebuilt",
+        "truth",
     ],
     sdk_version: "test_current",
 }
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index e8b6f36..75553d0 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -9,7 +9,7 @@
         "androidx.annotation_annotation",
         "compatibility-tradefed",
         "tradefed",
-        "truth-prebuilt",
+        "truth",
     ],
     static_libs: [
         "MicrodroidTestHelper",
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 8e12114..692b1b8 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -190,7 +190,7 @@
         long start = System.currentTimeMillis();
         while ((System.currentTimeMillis() - start < timeoutMillis)
                 && !matcher.matches(callable.call())) {
-            Thread.sleep(500);
+            RunUtil.getDefault().sleep(500);
         }
         assertThat(callable.call(), matcher);
     }
diff --git a/tests/no_avf/Android.bp b/tests/no_avf/Android.bp
index fd0d5e2..22d099e 100644
--- a/tests/no_avf/Android.bp
+++ b/tests/no_avf/Android.bp
@@ -13,9 +13,9 @@
         "androidx.test.runner",
         "androidx.test.ext.junit",
         "compatibility-common-util-devicesidelib",
-        "truth-prebuilt",
+        "truth",
     ],
     sdk_version: "test_current",
     compile_multilib: "both",
-    min_sdk_version: "UpsideDownCake",
+    min_sdk_version: "34",
 }
diff --git a/tests/pvmfw/Android.bp b/tests/pvmfw/Android.bp
index 474c62e..c12f67a 100644
--- a/tests/pvmfw/Android.bp
+++ b/tests/pvmfw/Android.bp
@@ -2,36 +2,30 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-genrule_defaults {
-    name: "test_avf_dts_to_dtb",
-    tools: ["dtc"],
-    cmd: "$(location dtc) -I dts -O dtb $(in) -o $(out)",
-}
-
 genrule {
     name: "test_avf_debug_policy_with_ramdump",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["assets/avf_debug_policy_with_ramdump.dts"],
     out: ["avf_debug_policy_with_ramdump.dtbo"],
 }
 
 genrule {
     name: "test_avf_debug_policy_without_ramdump",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["assets/avf_debug_policy_without_ramdump.dts"],
     out: ["avf_debug_policy_without_ramdump.dtbo"],
 }
 
 genrule {
     name: "test_avf_debug_policy_with_adb",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["assets/avf_debug_policy_with_adb.dts"],
     out: ["avf_debug_policy_with_adb.dtbo"],
 }
 
 genrule {
     name: "test_avf_debug_policy_without_adb",
-    defaults: ["test_avf_dts_to_dtb"],
+    defaults: ["dts_to_dtb"],
     srcs: ["assets/avf_debug_policy_without_adb.dts"],
     out: ["avf_debug_policy_without_adb.dtbo"],
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 526f240..6e8216f 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -41,7 +41,7 @@
         "androidx.test.ext.junit",
         "authfs_test_apk_assets",
         "cbor-java",
-        "truth-prebuilt",
+        "truth",
         "compatibility-common-util-devicesidelib",
         "measure_io_as_jar",
     ],
diff --git a/tests/vmshareapp/Android.bp b/tests/vmshareapp/Android.bp
index 6c2c9e4..5f6dc57 100644
--- a/tests/vmshareapp/Android.bp
+++ b/tests/vmshareapp/Android.bp
@@ -12,5 +12,5 @@
         // Defined in ../testapk/Android.bp
         "MicrodroidPayloadInOtherAppNativeLib",
     ],
-    min_sdk_version: "UpsideDownCake",
+    min_sdk_version: "34",
 }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 684aa64..5283ffe 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -474,7 +474,7 @@
                 .into_iter()
                 .map(|x| VfioDevice {
                     sysfs_path: PathBuf::from(&x.sysfsPath),
-                    dtbo_node: x.dtboNode,
+                    dtbo_label: x.dtboLabel,
                 })
                 .collect::<Vec<_>>()
         } else {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index b053d99..bb6066f 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -128,7 +128,7 @@
 #[derive(Clone, Debug)]
 pub struct VfioDevice {
     pub sysfs_path: PathBuf,
-    pub dtbo_node: String,
+    pub dtbo_label: String,
 }
 
 /// The lifecycle state which the payload in the VM has reported itself to be in.
@@ -716,7 +716,7 @@
     }
 
     if let Some(p) = path.to_str() {
-        Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_node))
+        Ok(format!("--vfio={p},iommu=viommu,dt-symbol={0}", device.dtbo_label))
     } else {
         bail!("invalid path {path:?}");
     }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index f3a7617..172dc59 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -25,7 +25,7 @@
 interface IVirtualizationServiceInternal {
     parcelable BoundDevice {
         String sysfsPath;
-        String dtboNode;
+        String dtboLabel;
     }
     /**
      * Removes the memlock rlimit of the calling process.
diff --git a/virtualizationservice/assignable_devices.xsd b/virtualizationservice/assignable_devices.xsd
index 8f43019..2fbc1c9 100644
--- a/virtualizationservice/assignable_devices.xsd
+++ b/virtualizationservice/assignable_devices.xsd
@@ -25,7 +25,7 @@
     </xs:element>
     <xs:complexType name="device">
         <xs:attribute name="kind" type="xs:string"/>
-        <xs:attribute name="dtbo_node" type="xs:string"/>
+        <xs:attribute name="dtbo_label" type="xs:string"/>
         <xs:attribute name="sysfs_path" type="xs:string"/>
     </xs:complexType>
 </xs:schema>
diff --git a/virtualizationservice/schema/current.txt b/virtualizationservice/schema/current.txt
index ef99294..6e3fbb6 100644
--- a/virtualizationservice/schema/current.txt
+++ b/virtualizationservice/schema/current.txt
@@ -3,10 +3,10 @@
 
   public class Device {
     ctor public Device();
-    method public String getDtbo_node();
+    method public String getDtbo_label();
     method public String getKind();
     method public String getSysfs_path();
-    method public void setDtbo_node(String);
+    method public void setDtbo_label(String);
     method public void setKind(String);
     method public void setSysfs_path(String);
   }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index ed5c513..a19ecd2 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -209,7 +209,7 @@
             .into_iter()
             .filter_map(|x| {
                 if devices.contains(&x.sysfs_path) {
-                    Some(BoundDevice { sysfsPath: x.sysfs_path, dtboNode: x.dtbo_node })
+                    Some(BoundDevice { sysfsPath: x.sysfs_path, dtboLabel: x.dtbo_label })
                 } else {
                     None
                 }
@@ -222,7 +222,7 @@
 #[derive(Debug, Deserialize)]
 struct Device {
     kind: String,
-    dtbo_node: String,
+    dtbo_label: String,
     sysfs_path: String,
 }
 
diff --git a/virtualizationservice/vfio_handler/src/aidl.rs b/virtualizationservice/vfio_handler/src/aidl.rs
index 1c3c5d9..2968ff9 100644
--- a/virtualizationservice/vfio_handler/src/aidl.rs
+++ b/virtualizationservice/vfio_handler/src/aidl.rs
@@ -26,6 +26,7 @@
 use rustutils::system_properties;
 use zerocopy::{
     byteorder::{BigEndian, U32},
+    FromZeroes,
     FromBytes,
 };
 
@@ -82,7 +83,7 @@
 /// The structure of DT table header in dtbo.img.
 /// https://source.android.com/docs/core/architecture/dto/partitions
 #[repr(C)]
-#[derive(Debug, FromBytes)]
+#[derive(Debug, FromZeroes, FromBytes)]
 struct DtTableHeader {
     /// DT_TABLE_MAGIC
     magic: U32<BigEndian>,
@@ -106,7 +107,7 @@
 /// The structure of each DT table entry (v0) in dtbo.img.
 /// https://source.android.com/docs/core/architecture/dto/partitions
 #[repr(C)]
-#[derive(Debug, FromBytes)]
+#[derive(Debug, FromZeroes, FromBytes)]
 struct DtTableEntry {
     /// size of each DT
     dt_size: U32<BigEndian>,
@@ -238,7 +239,7 @@
     if dt_table_header.magic.get() != DT_TABLE_MAGIC
         || dt_table_header.header_size.get() as usize != size_of::<DtTableHeader>()
     {
-        return Err(anyhow!("DtTableHeader is invalid")).or_service_specific_exception(-1)?;
+        return Err(anyhow!("DtTableHeader is invalid")).or_service_specific_exception(-1);
     }
     Ok(dt_table_header)
 }
@@ -249,7 +250,7 @@
     index: u32,
 ) -> binder::Result<DtTableEntry> {
     if index >= header.dt_entry_count.get() {
-        return Err(anyhow!("Invalid dtbo index {index}")).or_service_specific_exception(-1)?;
+        return Err(anyhow!("Invalid dtbo index {index}")).or_service_specific_exception(-1);
     }
     let Some(prev_dt_entry_total_size) = header.dt_entry_size.get().checked_mul(index) else {
         return Err(anyhow!("Unexpected arithmetic result"))