Repository: chipsalliance/caliptra-sw
Pull request #251 (2023-05-10 00:57:57)

Add ECDSA verify mailbox command
Commit 1: Add support to sw-emulator for using the sha512acc from SoC in streaming mode

Commit 2: Add scaffolding more mailbox commands and implement the ECDSA verify command. The signature is assumed to be over the data hashed into the sha512acc peripheral.


Patch:
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index 6e6e0c76..d50f16c1 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -121,7 +121,7 @@ pub struct BootParams<'a> {
 
 #[derive(Debug, Eq, PartialEq)]
 pub enum ModelError {
-    MailboxCmdFailed,
+    MailboxCmdFailed(u32),
     UnableToLockMailbox,
     BufferTooLargeForMailbox,
     UploadFirmwareUnexpectedResponse,
@@ -131,12 +131,13 @@ pub enum ModelError {
     ProvidedIccmTooLarge,
     ProvidedDccmTooLarge,
     UnexpectedMailboxFsmStatus { expected: u32, actual: u32 },
+    UnableToLockSha512Acc,
 }
 impl Error for ModelError {}
 impl Display for ModelError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            ModelError::MailboxCmdFailed => write!(f, "Mailbox command failed"),
+            ModelError::MailboxCmdFailed(err) => write!(f, "Mailbox command failed. fw_err={err}"),
             ModelError::UnableToLockMailbox => write!(f, "Unable to lock mailbox"),
             ModelError::BufferTooLargeForMailbox => write!(f, "Buffer too large for mailbox"),
             ModelError::UploadFirmwareUnexpectedResponse => {
@@ -157,6 +158,7 @@ impl Display for ModelError {
                 f,
                 "Expected mailbox FSM status to be {expected}, was {actual}"
             ),
+            ModelError::UnableToLockSha512Acc => write!(f, "Unable to lock sha512acc"),
         }
     }
 }
@@ -443,6 +445,19 @@ pub trait HwModel {
         }
     }
 
+    /// A register block that can be used to manipulate the sha512_acc peripheral
+    /// over the simulated SoC->Caliptra APB bus.
+    fn soc_sha512_acc(
+        &mut self,
+    ) -> caliptra_registers::sha512_acc::RegisterBlock<BusMmio<Self::TBus<'_>>> {
+        unsafe {
+            caliptra_registers::sha512_acc::RegisterBlock::new_with_mmio(
+                0x3002_1000 as *mut u32,
+                BusMmio::new(self.apb_bus()),
+            )
+        }
+    }
+
     fn tracing_hint(&mut self, enable: bool);
 
     /// Executes `cmd` with request data `buf`. Returns `Ok(Some(_))` if
@@ -480,7 +495,10 @@ pub trait HwModel {
         if status.cmd_failure() {
             writeln!(self.output().logger(), ">>> mbox cmd response: failed").unwrap();
             self.soc_mbox().execute().write(|w| w.execute(false));
-            return Err(ModelError::MailboxCmdFailed);
+            let soc_ifc = self.soc_ifc();
+            return Err(ModelError::MailboxCmdFailed(
+                soc_ifc.cptra_fw_error_non_fatal().read(),
+            ));
         }
         if status.cmd_complete() {
             writeln!(self.output().logger(), ">>> mbox cmd response: success").unwrap();
@@ -507,6 +525,51 @@ pub trait HwModel {
         Ok(Some(result))
     }
 
+    /// Streams `data` to the sha512acc SoC interface. If `sha384` computes
+    /// the sha384 hash of `data`, else computes sha512 hash.
+    ///
+    /// `dlen` is the size of data in bytes. sha512acc does not require streamed
+    /// data to be full words.
+    ///
+    /// Returns the computed digest if the sha512acc lock can be acquired.
+    /// Else, returns an error.
+    fn compute_sha512_acc_digest(
+        &mut self,
+        data: &[u32],
+        dlen: u32,
+        sha384: bool,
+    ) -> Result<Vec<u32>, ModelError> {
+        self.soc_sha512_acc().control().write(|w| w.zeroize(true));
+
+        if self.soc_sha512_acc().lock().read().lock() {
+            return Err(ModelError::UnableToLockSha512Acc);
+        }
+
+        self.soc_sha512_acc().dlen().write(|_| dlen);
+
+        self.soc_sha512_acc().mode().write(|w| {
+            w.mode(|w| {
+                if sha384 {
+                    w.sha_stream_384()
+                } else {
+                    w.sha_stream_512()
+                }
+            })
+        });
+
+        for word in data {
+            self.soc_sha512_acc().datain().write(|_| word.swap_bytes());
+        }
+
+        self.soc_sha512_acc().execute().write(|w| w.execute(true));
+
+        self.step_until(|m| m.soc_sha512_acc().status().read().valid());
+
+        self.soc_sha512_acc().lock().write(|w| w.lock(true)); // clear lock
+
+        Ok(self.soc_sha512_acc().digest().read().to_vec())
+    }
+
     /// Upload firmware to the mailbox.
     fn upload_firmware(&mut self, firmware: &[u8]) -> Result<(), ModelError> {
         let response = self.mailbox_execute(FW_LOAD_CMD_OPCODE, firmware)?;
@@ -740,7 +803,7 @@ mod tests {
         // Send command that returns failure
         assert_eq!(
             model.mailbox_execute(0x4000_0000, &message),
-            Err(ModelError::MailboxCmdFailed)
+            Err(ModelError::MailboxCmdFailed(0))
         );
     }
 
@@ -790,4 +853,81 @@ mod tests {
         // TODO: Add test for txn.respond_with_data (this doesn't work yet due
         // to https://github.com/chipsalliance/caliptra-rtl/issues/78)
     }
+
+    struct Sha384Test<'a> {
+        msg: &'a [u32],
+        dlen: u32,
+        expected: &'a [u32],
+    }
+
+    #[test]
+    fn test_sha512_acc() {
+        // This test doesn't rely on any firmware features. mailbox_responder
+        // image is sufficient
+        let rom = caliptra_builder::build_firmware_rom(&FwId {
+            crate_name: "caliptra-hw-model-test-fw",
+            bin_name: "mailbox_responder",
+            features: &["emu"],
+        })
+        .unwrap();
+
+        let mut model = caliptra_hw_model::new(BootParams {
+            init_params: InitParams {
+                rom: &rom,
+                ..Default::default()
+            },
+            ..Default::default()
+        })
+        .unwrap();
+
+        // Note: Streamed `msg` data is big endian. `expected` digest values are little-endian
+        // words.
+        let tests = vec![
+            Sha384Test {
+                msg: &[
+                    0xea89d79d, 0x4547c025, 0x1f387ad5, 0xfb01de22, 0x723cbd0a, 0x44fddedb,
+                    0xc11332e4, 0xef3e5889, 0x2066ba85, 0xe23dda44, 0xe67086dd, 0x48545132,
+                    0xeebb5501, 0x752c70bb, 0x2ec31a78, 0x60189413, 0xe36f57cb, 0x57b7057a,
+                    0x415b5bda, 0xc3d76d8f, 0x402e040b, 0x345a39f4, 0xe0dce42a, 0x36c33456,
+                    0x52bce225, 0x1f484543, 0x953d257e, 0x23682651, 0x17251b77, 0x51a8b405,
+                    0x372a0266, 0xbdf128ac,
+                ],
+                dlen: 128,
+                expected: &[
+                    0x965b83f5, 0xd34f7443, 0xeb88e78f, 0xcc234791, 0x56c9cb00, 0x80dd6833,
+                    0x4dac0ad3, 0x3ba8c774, 0x100e4400, 0x63db28b4, 0xb51ac37, 0x705d4d70,
+                    0xa6bb0a3c, 0xf3d9dd14, 0xa9496a55, 0x67c16cb6,
+                ],
+            },
+            Sha384Test {
+                msg: &[0x74736574], // Bytes "test" converted to little endian
+                dlen: 4,
+                // Computed with sha384sum
+                expected: &[
+                    0x76841232, 0x0f7b0aa5, 0x812fce42, 0x8dc4706b, 0x3cae50e0, 0x2a64caa1,
+                    0x6a782249, 0xbfe8efc4, 0xb7ef1ccb, 0x126255d1, 0x96047dfe, 0xdf17a0a9,
+                    0xd5bc262b, 0xa2b797a3, 0xc4fea9c9, 0xc99e829a,
+                ],
+            },
+            Sha384Test {
+                msg: &[0x74736574], // Bytes "test" converted to little endian
+                dlen: 2,            // Only hash the bytes "te"
+                // Computed with sha384sum
+                expected: &[
+                    0x7c9c881a, 0x549e5c64, 0x9a49c98a, 0xf4ead3ca, 0x907e6c6c, 0xfc7f98f4,
+                    0x8f640ad7, 0xf783584b, 0xf460cacc, 0x6fa3dd75, 0x55d71820, 0x199afcf8,
+                    0xf23c8f0, 0xa48ae0df, 0x9583c0da, 0xa52da0e6,
+                ],
+            },
+        ];
+
+        for test in tests {
+            assert_eq!(
+                model
+                    .compute_sha512_acc_digest(test.msg, test.dlen, /*sha384=*/ true)
+                    .unwrap(),
+                test.expected
+            );
+        }
+    }
 }
diff --git a/runtime/README.md b/runtime/README.md
index 250d4f0c..05c0c350 100644
--- a/runtime/README.md
+++ b/runtime/README.md
@@ -140,6 +140,9 @@ Table: `GET_LDEV_CERT` output arguments
 
 ### ECDSA384\_SIGNATURE\_VERIFY
 
+Verifies an ECDSA P-384 signature. The hash to be verified is taken from
+Caliptra's SHA384 accelerator peripheral.
+
 Command Code: `0x5349_4756` ("SIGV")
 
 Table: `ECDSA384_SIGNATURE_VERIFY` input arguments
@@ -147,7 +150,6 @@ Table: `ECDSA384_SIGNATURE_VERIFY` input arguments
 | **Name**     | **Type** | **Description**
 | --------     | -------- | ---------------
 | chksum       | u32      | Checksum over other input arguments, computed by the caller. Little endian.
-| data         | u8[48]   | Signed hash to verify
 | pub\_key\_x  | u8[48]   | X portion of ECDSA verification key
 | pub\_key\_y  | u8[48]   | Y portion of ECDSA verification key
 | signature\_r | u8[48]   | R portion of signature to verify
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 63445f46..93511f3b 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -3,14 +3,14 @@
 #![no_std]
 
 mod mailbox;
+mod verify;
 
 use mailbox::Mailbox;
 
 use caliptra_common::cprintln;
 use caliptra_drivers::{caliptra_err_def, CaliptraResult};
-use caliptra_registers::mbox::enums::MboxStatusE;
-
-use core::mem::size_of;
+use caliptra_registers::{mbox::enums::MboxStatusE, soc_ifc};
+use zerocopy::{AsBytes, FromBytes};
 
 caliptra_err_def! {
     Runtime,
@@ -18,9 +18,62 @@ caliptra_err_def! {
     {
         // Internal
         InternalErr = 0x1,
+        UnimplementedCommand = 0x2,
+        InsufficientMemory = 0x3,
+    }
+}
+
+#[derive(PartialEq, Eq)]
+pub struct CommandId(pub u32);
+
+impl CommandId {
+    pub const FIRMWARE_LOAD: Self = Self(0x46574C44); // "FWLD"
+    pub const GET_IDEV_CSR: Self = Self(0x49444556); // "IDEV"
+    pub const GET_LDEV_CERT: Self = Self(0x4C444556); // "LDEV"
+    pub const ECDSA384_VERIFY: Self = Self(0x53494756); // "SIGV"
+    pub const STASH_MEASUREMENT: Self = Self(0x4D454153); // "MEAS"
+    pub const INVOKE_DPE: Self = Self(0x44504543); // "DPEC"
+}
+impl From<u32> for CommandId {
+    fn from(value: u32) -> Self {
+        Self(value)
+    }
+}
+impl From<CommandId> for u32 {
+    fn from(value: CommandId) -> Self {
+        value.0
+    }
+}
+
+#[repr(C)]
+#[derive(AsBytes, FromBytes)]
+pub struct EcdsaVerifyCmd {
+    pub chksum: u32,
+    pub pub_key_x: [u8; 48],
+    pub pub_key_y: [u8; 48],
+    pub signature_r: [u8; 48],
+    pub signature_s: [u8; 48],
+}
+
+impl Default for EcdsaVerifyCmd {
+    fn default() -> Self {
+        Self {
+            chksum: 0,
+            pub_key_x: [0u8; 48],
+            pub_key_y: [0u8; 48],
+            signature_r: [0u8; 48],
+            signature_s: [0u8; 48],
+        }
     }
 }
 
+#[repr(C)]
+#[derive(AsBytes, FromBytes)]
+pub struct EcdsaVerifyResponse {
+    pub chksum: u32,
+    pub result: u32,
+}
+
 fn wait_for_cmd() {
     // TODO: Enable interrupts?
     //#[cfg(feature = "riscv")]
@@ -29,21 +82,40 @@ fn wait_for_cmd() {
     //}
 }
 
-fn handle_command() -> CaliptraResult<()> {
+fn handle_command() -> CaliptraResult<MboxStatusE> {
     let cmd_id = Mailbox::cmd();
+    let dlen = Mailbox::dlen() as usize;
     let dlen_words = Mailbox::dlen_words() as usize;
     let mut buf = [0u32; 1024];
     Mailbox::copy_from_mbox(buf.get_mut(..dlen_words).ok_or(err_u32!(InternalErr))?);
 
-    // TODO: Actually handle command
-    cprintln!("[rt] Received command={}, len={}", cmd_id, Mailbox::dlen());
+    if dlen > buf.len() * 4 {
+        // dlen larger than max message
+        return Err(err_u32!(InsufficientMemory));
+    }
 
-    // Write response
-    let out_buf = [0xFFFFFFFFu32; 4];
-    Mailbox::set_dlen((out_buf.len() * size_of::<u32>()) as u32);
-    Mailbox::copy_to_mbox(&out_buf);
+    let cmd_bytes = buf
+        .as_bytes()
+        .get(..dlen)
+        .ok_or(err_u32!(InsufficientMemory))?;
 
-    Ok(())
+    cprintln!(
+        "[rt] Received command=0x{:x}, len={}",
+        cmd_id,
+        Mailbox::dlen()
+    );
+    match CommandId::from(cmd_id) {
+        CommandId::FIRMWARE_LOAD => Err(err_u32!(UnimplementedCommand)),
+        CommandId::GET_IDEV_CSR => Err(err_u32!(UnimplementedCommand)),
+        CommandId::GET_LDEV_CERT => Err(err_u32!(UnimplementedCommand)),
+        CommandId::ECDSA384_VERIFY => {
+            verify::handle_ecdsa_verify(cmd_bytes)?;
+            Ok(MboxStatusE::DataReady)
+        }
+        CommandId::STASH_MEASUREMENT => Err(err_u32!(UnimplementedCommand)),
+        CommandId::INVOKE_DPE => Err(err_u32!(UnimplementedCommand)),
+        _ => Err(err_u32!(UnimplementedCommand)),
+    }
 }
 
 pub fn handle_mailbox_commands() {
@@ -51,10 +123,16 @@ pub fn handle_mailbox_commands() {
         wait_for_cmd();
 
         if Mailbox::is_cmd_ready() {
-            if handle_command().is_ok() {
-                Mailbox::set_status(MboxStatusE::DataReady);
-            } else {
-                Mailbox::set_status(MboxStatusE::CmdFailure);
+            match handle_command() {
+                Ok(status) => {
+                    Mailbox::set_status(status);
+                }
+                Err(e) => {
+                    let soc_ifc_regs = soc_ifc::RegisterBlock::soc_ifc_reg();
+                    soc_ifc_regs.cptra_fw_error_non_fatal().write(|_| e.into());
+
+                    Mailbox::set_status(MboxStatusE::CmdFailure);
+                }
             }
         }
     }
diff --git a/runtime/src/mailbox.rs b/runtime/src/mailbox.rs
index 2a7fa344..62785df1 100644
--- a/runtime/src/mailbox.rs
+++ b/runtime/src/mailbox.rs
@@ -1,6 +1,9 @@
 // Licensed under the Apache-2.0 license
 
+use crate::RuntimeErr;
+use caliptra_drivers::CaliptraResult;
 use caliptra_registers::mbox::{self, enums::MboxStatusE};
+use zerocopy::{LayoutVerified, Unalign};
 
 pub struct Mailbox {}
 
@@ -23,7 +26,7 @@ impl Mailbox {
 
     // Get the length of the current mailbox data in words
     pub fn dlen_words() -> u32 {
-        (Self::dlen() + 7) / 8
+        (Self::dlen() + 3) / 4
     }
 
     pub fn cmd() -> u32 {
@@ -36,12 +39,27 @@ impl Mailbox {
         }
     }
 
-    pub fn copy_to_mbox(buf: &[u32]) {
+    pub fn copy_to_mbox(buf: &[Unalign<u32>]) {
         for word in buf {
-            mbox::RegisterBlock::mbox_csr().datain().write(|_| *word);
+            mbox::RegisterBlock::mbox_csr()
+                .datain()
+                .write(|_| word.get());
         }
     }
 
+    /// Write a word-aligned `buf` to the mailbox
+    pub fn write_response(buf: &[u8]) -> CaliptraResult<()> {
+        let Some(buf_words) = LayoutVerified::new_slice_unaligned(buf) else {
+            // buf size is not a multiple of word size
+            return Err(RuntimeErr::InternalErr.into());
+        };
+
+        Self::set_dlen(buf.len() as u32);
+        Self::copy_to_mbox(&buf_words);
+
+        Ok(())
+    }
+
     pub fn set_status(status: MboxStatusE) {
         mbox::RegisterBlock::mbox_csr()
             .status()
diff --git a/runtime/src/verify.rs b/runtime/src/verify.rs
new file mode 100644
index 00000000..bd43c04d
--- /dev/null
+++ b/runtime/src/verify.rs
@@ -0,0 +1,46 @@
+// Licensed under the Apache-2.0 license
+
+use crate::{EcdsaVerifyCmd, EcdsaVerifyResponse, Mailbox, RuntimeErr};
+use caliptra_drivers::{
+    Array4x12, CaliptraResult, Ecc384, Ecc384PubKey, Ecc384Scalar, Ecc384Signature,
+};
+use caliptra_registers::sha512_acc;
+use zerocopy::{AsBytes, FromBytes};
+
+/// Handle the `ECDSA384_SIGNATURE_VERIFY` mailbox command
+pub fn handle_ecdsa_verify(cmd_args: &[u8]) -> CaliptraResult<()> {
+    if let Some(cmd) = EcdsaVerifyCmd::read_from(cmd_args) {
+        let sha_acc = sha512_acc::RegisterBlock::sha512_acc_csr();
+
+        // Won't panic, full_digest is always larger than digest
+        let full_digest = sha_acc.digest().read();
+        let mut digest = Array4x12::default();
+        for (i, target_word) in digest.0.iter_mut().enumerate() {
+            *target_word = full_digest[i];
+        }
+
+        let pubkey = Ecc384PubKey {
+            x: Ecc384Scalar::from(cmd.pub_key_x),
+            y: Ecc384Scalar::from(cmd.pub_key_y),
+        };
+
+        let sig = Ecc384Signature {
+            r: Ecc384Scalar::from(cmd.signature_r),
+            s: Ecc384Scalar::from(cmd.signature_s),
+        };
+
+        let ecdsa = Ecc384::default();
+        let success = ecdsa.verify(&pubkey, &digest, &sig)?;
+
+        let response = EcdsaVerifyResponse {
+            chksum: 0, // TODO: Implement checksum
+            result: success as u32,
+        };
+
+        Mailbox::write_response(response.as_bytes())?;
+    } else {
+        return Err(RuntimeErr::InsufficientMemory.into());
+    };
+
+    Ok(())
+}
diff --git a/sw-emulator/lib/crypto/src/sha512.rs b/sw-emulator/lib/crypto/src/sha512.rs
index a48f2f4a..e8e76fe1 100644
--- a/sw-emulator/lib/crypto/src/sha512.rs
+++ b/sw-emulator/lib/crypto/src/sha512.rs
@@ -39,6 +39,12 @@ pub struct Sha512 {
 
     /// SHA 512 Mode
     mode: Sha512Mode,
+
+    /// Partial block to be processed
+    partial_block: Vec<u8>,
+
+    /// Number of full blocks processed to create hash
+    blocks_processed: usize,
 }
 
 impl Sha512 {
@@ -105,6 +111,8 @@ impl Sha512 {
         Self {
             hash: Self::hash_iv(mode),
             mode,
+            partial_block: vec![],
+            blocks_processed: 0,
         }
     }
 
@@ -118,14 +126,60 @@ impl Sha512 {
     ///
     /// # Arguments
     ///
-    /// * `block` - Block to compress
+    /// * `block` - Block to compress. Each word in `block` is expected to
+    ///   be little-endian
     pub fn update(&mut self, block: &[u8; Self::BLOCK_SIZE]) {
         let mut block = *Block::<U128>::from_slice(block);
+        block.to_big_endian();
 
-        // Block is received as a list of big-endian DWORDs.
-        // Changing them to little-endian.
-        block.to_little_endian();
         sha2::compress512(&mut self.hash, &[block]);
+        self.blocks_processed += 1;
+    }
+
+    /// Update the hash with an arbitrary number to bytes
+    ///
+    /// `bytes` is expected to be big-endian data
+    pub fn update_bytes(&mut self, bytes: &[u8]) {
+        self.partial_block.extend_from_slice(bytes);
+
+        while self.partial_block.len() >= Self::BLOCK_SIZE {
+            // Safe to unwrap becasue slice is guaranteed to be correct size
+            self.partial_block[..Self::BLOCK_SIZE].to_little_endian();
+            self.update(&self.partial_block[..Self::BLOCK_SIZE].try_into().unwrap());
+            self.partial_block.drain(..Self::BLOCK_SIZE);
+        }
+    }
+
+    /// Finalize the hash by adding padding
+    pub fn finalize(&mut self, dlen: u32) {
+        // Check if dlen is less than the amount of data streamed
+        // TODO: What to do if dlen is less than the blocks we've already processed?
+        let bytes_of_blocks = self.blocks_processed * Self::BLOCK_SIZE;
+        let partial = if (dlen as usize) < bytes_of_blocks + self.partial_block.len() {
+            (dlen as usize) - bytes_of_blocks
+        } else {
+            self.partial_block.len()
+        };
+
+        let msg_len = (self.blocks_processed * Self::BLOCK_SIZE) + partial;
+        self.partial_block.resize(partial, 0);
+
+        self.partial_block.push(0b1000_0000);
+        let zeros: usize = Self::BLOCK_SIZE - ((msg_len + 1 + 16) % 128);
+        self.partial_block.extend_from_slice(&vec![0u8; zeros]);
+
+        // Add bit length of hashed data
+        self.partial_block
+            .extend_from_slice(&((msg_len * 8) as u128).to_be_bytes());
+
+        // update function expects little endian words
+        self.partial_block.to_little_endian();
+
+        while self.partial_block.len() >= Self::BLOCK_SIZE {
+            // Safe to unwrap becasue slice is guaranteed to be correct size
+            self.update(&self.partial_block[..Self::BLOCK_SIZE].try_into().unwrap());
+            self.partial_block.drain(..Self::BLOCK_SIZE);
+        }
     }
 
     /// Retrieve the hash
@@ -141,7 +195,7 @@ impl Sha512 {
         hash_be
             .iter()
             .flat_map(|i| i.to_be_bytes())
-            .take(self.hash_len())
+            .take(std::cmp::min(hash.len(), Self::HASH_SIZE))
             .zip(hash)
             .for_each(|(src, dest)| *dest = src);
     }
diff --git a/sw-emulator/lib/periph/src/root_bus.rs b/sw-emulator/lib/periph/src/root_bus.rs
index c1653f72..ab39aa3c 100644
--- a/sw-emulator/lib/periph/src/root_bus.rs
+++ b/sw-emulator/lib/periph/src/root_bus.rs
@@ -294,9 +294,8 @@ impl CaliptraRootBus {
 
     pub fn soc_to_caliptra_bus(&self) -> SocToCaliptraBus {
         SocToCaliptraBus {
-            // TODO: This should not be the same mailbox bus as the one used
-            // internaly
             mailbox: self.mailbox.as_external(),
+            sha512_acc: self.sha512_acc.clone(),
             soc_ifc: self.soc_reg.external_regs(),
         }
     }
@@ -307,6 +306,9 @@ pub struct SocToCaliptraBus {
     #[peripheral(offset = 0x3002_0000, mask = 0x0000_0fff)]
     mailbox: MailboxExternal,
 
+    #[peripheral(offset = 0x3002_1000, mask = 0x0000_0fff)]
+    sha512_acc: Sha512Accelerator,
+
     #[peripheral(offset = 0x3003_0000, mask = 0x0000_ffff)]
     soc_ifc: SocRegistersExternal,
 }
diff --git a/sw-emulator/lib/periph/src/sha512_acc.rs b/sw-emulator/lib/periph/src/sha512_acc.rs
index 39cc0f5f..0ec8ba32 100644
--- a/sw-emulator/lib/periph/src/sha512_acc.rs
+++ b/sw-emulator/lib/periph/src/sha512_acc.rs
@@ -17,8 +17,10 @@ use caliptra_emu_bus::{
 };
 use caliptra_emu_crypto::{EndianessTransform, Sha512, Sha512Mode};
 use caliptra_emu_derive::Bus;
-use caliptra_emu_types::{RvData, RvSize};
+use caliptra_emu_types::{RvAddr, RvData, RvSize};
 use smlang::statemachine;
+use std::cell::RefCell;
+use std::rc::Rc;
 use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
 use tock_registers::register_bitfields;
 use tock_registers::registers::InMemoryRegister;
@@ -34,6 +36,8 @@ const SHA_ACC_OP_TICKS: u64 = 1000;
 
 const SHA512_BLOCK_SIZE: usize = 128;
 const SHA512_HASH_SIZE: usize = 64;
+
+#[cfg(test)]
 const SHA384_HASH_SIZE: usize = 48;
 const SHA512_HASH_HALF_SIZE: usize = SHA512_HASH_SIZE / 2;
 
@@ -81,7 +85,7 @@ register_bitfields! [
 #[poll_fn(poll)]
 #[warm_reset_fn(warm_reset)]
 #[update_reset_fn(update_reset)]
-pub struct Sha512Accelerator {
+pub struct Sha512AcceleratorRegs {
     /// LOCK register
     #[register(offset = 0x0000_0000, read_fn = on_read_lock, write_fn = on_write_lock)]
     _lock: ReadWriteRegister<u32, Lock::Register>,
@@ -136,10 +140,12 @@ pub struct Sha512Accelerator {
 
     /// Operation complete action
     op_complete_action: Option<ActionHandle>,
+
+    /// Hasher for streamed hash data
+    sha_stream: Sha512,
 }
 
-impl Sha512Accelerator {
-    /// Create a new instance of SHA-512 Accelerator
+impl Sha512AcceleratorRegs {
     pub fn new(clock: &Clock, mailbox_ram: MailboxRam) -> Self {
         Self {
             status: ReadOnlyRegister::new(Status::VALID::CLEAR.value),
@@ -157,6 +163,7 @@ impl Sha512Accelerator {
             op_complete_action: None,
             state_machine: StateMachine::new(Context::new()),
             control: ReadWriteRegister::new(0),
+            sha_stream: Sha512::new(Sha512Mode::Sha512),
         }
     }
 
@@ -239,12 +246,14 @@ impl Sha512Accelerator {
             Err(BusError::StoreAccessFault)?
         }
 
-        let val_reg = InMemoryRegister::<u32, ShaMode::Register>::new(val);
-        if val_reg.read(ShaMode::MODE) != ShaMode::MODE::SHA512_ACC_MODE_MBOX_384.value {
-            Err(BusError::StoreAccessFault)?
-        }
         self.mode.reg.set(val);
 
+        let mode = self.mode.reg.read(ShaMode::MODE);
+        if mode == ShaMode::MODE::SHA512_ACC_MODE_SHA_STREAM_384.value {
+            self.sha_stream = Sha512::new(Sha512Mode::Sha384);
+        } else if mode == ShaMode::MODE::SHA512_ACC_MODE_SHA_STREAM_512.value {
+            self.sha_stream = Sha512::new(Sha512Mode::Sha512);
+        }
         Ok(())
     }
 
@@ -309,9 +318,14 @@ impl Sha512Accelerator {
     /// # Error
     ///
     /// * `BusError` - Exception with cause `BusError::StoreAccessFault` or `BusError::StoreAddrMisaligned`
-    pub fn on_write_data_in(&mut self, _size: RvSize, _val: RvData) -> Result<(), BusError> {
-        // Not implemented
-        Err(BusError::StoreAccessFault)?
+    pub fn on_write_data_in(&mut self, size: RvSize, val: RvData) -> Result<(), BusError> {
+        if size != RvSize::Word {
+            Err(BusError::StoreAccessFault)?
+        }
+
+        self.sha_stream.update_bytes(&val.to_be_bytes());
+
+        Ok(())
     }
 
     /// On Write callback for `execute` register
@@ -332,18 +346,25 @@ impl Sha512Accelerator {
         // Set the execute register
         self.execute.reg.set(val);
 
-        if self.execute.reg.read(Execute::EXECUTE) == 1
-            && self.mode.reg.read(ShaMode::MODE) == ShaMode::MODE::SHA512_ACC_MODE_MBOX_384.value
-        {
-            self.compute_hash();
-
-            // Schedule a future call to poll() complete the operation.
-            self.op_complete_action = Some(self.timer.schedule_poll_in(SHA_ACC_OP_TICKS));
-
-            Ok(())
+        if self.execute.reg.read(Execute::EXECUTE) == 1 {
+            let mode = self.mode.reg.read(ShaMode::MODE);
+            if mode == ShaMode::MODE::SHA512_ACC_MODE_MBOX_384.value {
+                self.compute_mbox_hash();
+
+                // Schedule a future call to poll() complete the operation.
+                self.op_complete_action = Some(self.timer.schedule_poll_in(SHA_ACC_OP_TICKS));
+            } else if mode == ShaMode::MODE::SHA512_ACC_MODE_SHA_STREAM_384.value
+                || mode == ShaMode::MODE::SHA512_ACC_MODE_SHA_STREAM_512.value
+            {
+                self.finalize_stream_hash();
+            } else {
+                return Err(BusError::StoreAccessFault);
+            }
         } else {
             Err(BusError::StoreAccessFault)?
         }
+
+        Ok(())
     }
 
     /// On Write callback for `control` register
@@ -379,7 +400,7 @@ impl Sha512Accelerator {
     /// # Error
     ///
     /// * `BusError` - Exception with cause `BusError::StoreAccessFault` or `BusError::StoreAddrMisaligned`
-    fn compute_hash(&mut self) {
+    fn compute_mbox_hash(&mut self) {
         let data_len = self.dlen.reg.get() as usize;
         let totaldwords = (data_len + (RvSize::Word as usize - 1)) / (RvSize::Word as usize);
         let totalblocks = ((data_len + 16) + SHA512_BLOCK_SIZE) / SHA512_BLOCK_SIZE;
@@ -433,6 +454,24 @@ impl Sha512Accelerator {
             .copy_from_slice(&hash[SHA512_HASH_HALF_SIZE..]);
     }
 
+    fn finalize_stream_hash(&mut self) {
+        self.sha_stream.finalize(self.dlen.reg.get());
+
+        let mut hash = [0u8; SHA512_HASH_SIZE];
+        self.sha_stream.copy_hash(&mut hash);
+
+        // Place the hash in the DIGEST registers.
+        self.hash_lower
+            .data_mut()
+            .copy_from_slice(&hash[..SHA512_HASH_HALF_SIZE]);
+
+        self.hash_upper
+            .data_mut()
+            .copy_from_slice(&hash[SHA512_HASH_HALF_SIZE..]);
+
+        self.op_complete();
+    }
+
     /// Called by Bus::poll() to indicate that time has passed
     fn poll(&mut self) {
         if self.timer.fired(&mut self.op_complete_action) {
@@ -456,6 +495,7 @@ impl Sha512Accelerator {
     }
 
     /// Get the length of the hash
+    #[cfg(test)]
     pub fn hash_len(&self) -> usize {
         let mode = self.mode.reg.read(ShaMode::MODE);
         if mode == ShaMode::MODE::SHA512_ACC_MODE_MBOX_384.value
@@ -467,6 +507,7 @@ impl Sha512Accelerator {
         }
     }
 
+    #[cfg(test)]
     pub fn copy_hash(&self, hash_out: &mut [u8]) {
         let mut hash = [0u8; SHA512_HASH_SIZE];
 
@@ -481,6 +522,36 @@ impl Sha512Accelerator {
     }
 }
 
+#[derive(Clone)]
+pub struct Sha512Accelerator {
+    regs: Rc<RefCell<Sha512AcceleratorRegs>>,
+}
+
+impl Sha512Accelerator {
+    /// Create a new instance of SHA-512 Accelerator
+    pub fn new(clock: &Clock, mailbox_ram: MailboxRam) -> Self {
+        Self {
+            regs: Rc::new(RefCell::new(Sha512AcceleratorRegs::new(clock, mailbox_ram))),
+        }
+    }
+}
+
+impl Bus for Sha512Accelerator {
+    /// Read data of specified size from given address
+    fn read(&mut self, size: RvSize, addr: RvAddr) -> Result<RvData, BusError> {
+        self.regs.borrow_mut().read(size, addr)
+    }
+
+    /// Write data of specified size to given address
+    fn write(&mut self, size: RvSize, addr: RvAddr, val: RvData) -> Result<(), BusError> {
+        self.regs.borrow_mut().write(size, addr, val)
+    }
+
+    fn poll(&mut self) {
+        self.regs.borrow_mut().poll();
+    }
+}
+
 pub struct Owner(pub u32);
 
 statemachine! {
@@ -637,7 +708,7 @@ mod tests {
 
         // Read the hash.
         let mut hash: [u8; SHA512_HASH_SIZE] = [0; SHA512_HASH_SIZE];
-        sha_accl.copy_hash(&mut hash);
+        sha_accl.regs.borrow().copy_hash(&mut hash);
 
         // Release the lock.
         assert_eq!(sha_accl.write(RvSize::Word, OFFSET_LOCK, 1).ok(), Some(()));
@@ -745,20 +816,30 @@ mod tests {
     #[test]
     fn test_sm_lock() {
         let clock = Clock::new();
-        let mut sha_accl = Sha512Accelerator::new(&clock, MailboxRam::new());
-        assert_eq!(sha_accl.state_machine.context.locked, 0);
+        let sha_accl = Sha512Accelerator::new(&clock, MailboxRam::new());
+        assert_eq!(sha_accl.regs.borrow().state_machine.context.locked, 0);
 
         let _ = sha_accl
+            .regs
+            .borrow_mut()
             .state_machine
             .process_event(Events::RdLock(Owner(0)));
-        assert!(matches!(sha_accl.state_machine.state(), States::RdyForExc));
-        assert_eq!(sha_accl.state_machine.context.locked, 1);
+        assert!(matches!(
+            sha_accl.regs.borrow().state_machine.state(),
+            States::RdyForExc
+        ));
+        assert_eq!(sha_accl.regs.borrow().state_machine.context.locked, 1);
 
         let _ = sha_accl
+            .regs
+            .borrow_mut()
             .state_machine
             .process_event(Events::WrLock(Owner(0)));
-        assert!(matches!(sha_accl.state_machine.state(), States::Idle));
-        assert_eq!(sha_accl.state_machine.context.locked, 0);
+        assert!(matches!(
+            sha_accl.regs.borrow().state_machine.state(),
+            States::Idle
+        ));
+        assert_eq!(sha_accl.regs.borrow().state_machine.context.locked, 0);
     }
 
     #[test]


Test patch:
diff --git a/drivers/tests/integration_tests.rs b/drivers/tests/integration_tests.rs
index ff3947b4..8aa8a87b 100644
--- a/drivers/tests/integration_tests.rs
+++ b/drivers/tests/integration_tests.rs
@@ -190,7 +190,7 @@ fn test_mailbox_soc_to_uc() {
     {
         assert_eq!(
             model.mailbox_execute(0x8000_0000, &[0x88, 0x99, 0xaa, 0xbb]),
-            Err(ModelError::MailboxCmdFailed)
+            Err(ModelError::MailboxCmdFailed(0))
         );
         assert_eq!(model.output().take(usize::MAX), "cmd: 0x80000000\n");
 
diff --git a/rom/dev/tests/test_fmcalias_derivation.rs b/rom/dev/tests/test_fmcalias_derivation.rs
index 9a7d3d18..73d00dc6 100644
--- a/rom/dev/tests/test_fmcalias_derivation.rs
+++ b/rom/dev/tests/test_fmcalias_derivation.rs
@@ -17,7 +17,7 @@ fn test_zero_firmware_size() {
     // Zero-sized firmware.
     assert_eq!(
         hw.upload_firmware(&[]).unwrap_err(),
-        ModelError::MailboxCmdFailed
+        ModelError::MailboxCmdFailed(0x01020003)
     );
     assert_eq!(
         hw.soc_ifc().cptra_fw_error_non_fatal().read(),
diff --git a/rom/dev/tests/test_image_validation.rs b/rom/dev/tests/test_image_validation.rs
index 425697bb..8eaed76d 100644
--- a/rom/dev/tests/test_image_validation.rs
+++ b/rom/dev/tests/test_image_validation.rs
@@ -96,14 +96,10 @@ fn test_invalid_manifest_marker() {
     image_bundle.manifest.marker = 0xDEADBEEF;
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(MANIFEST_MARKER_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        MANIFEST_MARKER_MISMATCH
-    );
 }
 
 #[test]
@@ -113,14 +109,10 @@ fn test_invalid_manifest_size() {
     image_bundle.manifest.size = (core::mem::size_of::<ImageManifest>() - 1) as u32;
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(MANIFEST_SIZE_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        MANIFEST_SIZE_MISMATCH
-    );
 }
 
 #[test]
@@ -134,14 +126,10 @@ fn test_preamble_zero_vendor_pubkey_digest() {
         helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_PUB_KEY_DIGEST_INVALID),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_PUB_KEY_DIGEST_INVALID
-    );
 }
 
 #[test]
@@ -155,14 +143,10 @@ fn test_preamble_vendor_pubkey_digest_mismatch() {
     let (mut hw, image_bundle) =
         helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_PUB_KEY_DIGEST_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_PUB_KEY_DIGEST_MISMATCH
-    );
 }
 
 #[test]
@@ -176,14 +160,10 @@ fn test_preamble_owner_pubkey_digest_mismatch() {
         helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_PUB_KEY_DIGEST_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_PUB_KEY_DIGEST_MISMATCH
-    );
 }
 
 #[test]
@@ -232,14 +212,10 @@ fn test_preamble_vendor_pubkey_revocation() {
                 .unwrap();
         } else {
             assert_eq!(
-                ModelError::MailboxCmdFailed,
+                ModelError::MailboxCmdFailed(VENDOR_ECC_PUB_KEY_REVOKED),
                 hw.upload_firmware(&image_bundle.to_bytes().unwrap())
                     .unwrap_err()
             );
-            assert_eq!(
-                hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-                VENDOR_ECC_PUB_KEY_REVOKED
-            );
         }
     }
 }
@@ -251,14 +227,10 @@ fn test_preamble_vendor_pubkey_out_of_bounds() {
     image_bundle.manifest.preamble.vendor_ecc_pub_key_idx = VENDOR_ECC_KEY_COUNT;
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_ECC_PUB_KEY_INDEX_OUT_OF_BOUNDS),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_ECC_PUB_KEY_INDEX_OUT_OF_BOUNDS
-    );
 }
 
 #[test]
@@ -275,16 +247,11 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
         .fill(0);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_PUB_KEY_DIGEST_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
 
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_PUB_KEY_DIGEST_INVALID_ARG
-    );
-
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
@@ -296,14 +263,10 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
         .fill(0);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_PUB_KEY_DIGEST_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_PUB_KEY_DIGEST_INVALID_ARG
-    );
 }
 
 #[test]
@@ -316,14 +279,10 @@ fn test_header_verify_vendor_sig_zero_signature() {
     image_bundle.manifest.preamble.vendor_sigs.ecc_sig.r.fill(0);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_ECC_SIGNATURE_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_ECC_SIGNATURE_INVALID_ARG
-    );
 
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
@@ -333,14 +292,10 @@ fn test_header_verify_vendor_sig_zero_signature() {
     image_bundle.manifest.preamble.vendor_sigs.ecc_sig.s.fill(0);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_ECC_SIGNATURE_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_ECC_SIGNATURE_INVALID_ARG
-    );
 }
 
 #[test]
@@ -361,14 +316,10 @@ fn test_header_verify_vendor_sig_mismatch() {
         .clone_from_slice(Array4x12::from(PUB_KEY_Y).0.as_slice());
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_ECC_SIGNATURE_INVALID),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_ECC_SIGNATURE_INVALID
-    );
 
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
@@ -392,14 +343,10 @@ fn test_header_verify_vendor_sig_mismatch() {
         .clone_from_slice(Array4x12::from(SIGNATURE_S).0.as_slice());
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_ECC_SIGNATURE_INVALID),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_ECC_SIGNATURE_INVALID
-    );
 }
 
 #[test]
@@ -413,14 +360,10 @@ fn test_header_verify_vendor_pub_key_in_preamble_and_header() {
     update_header(&mut image_bundle);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(VENDOR_ECC_PUB_KEY_INDEX_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        VENDOR_ECC_PUB_KEY_INDEX_MISMATCH
-    );
 }
 
 #[test]
@@ -484,15 +427,10 @@ fn test_header_verify_owner_sig_zero_fuses_zero_pubkey_x() {
     .unwrap();
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_PUB_KEY_DIGEST_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_PUB_KEY_DIGEST_INVALID_ARG
-    );
 }
 
 #[test]
@@ -522,15 +460,10 @@ fn test_header_verify_owner_sig_corrupt_fuses() {
     .unwrap();
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_PUB_KEY_DIGEST_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_PUB_KEY_DIGEST_MISMATCH
-    );
 }
 
 #[test]
@@ -573,15 +506,10 @@ fn test_header_verify_owner_sig_zero_pubkey_x() {
     .unwrap();
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_PUB_KEY_DIGEST_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_PUB_KEY_DIGEST_INVALID_ARG
-    );
 }
 
 #[test]
@@ -624,15 +552,10 @@ fn test_header_verify_owner_sig_zero_pubkey_y() {
     .unwrap();
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_PUB_KEY_DIGEST_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_PUB_KEY_DIGEST_INVALID_ARG
-    );
 }
 
 #[test]
@@ -669,15 +592,10 @@ fn test_header_verify_owner_sig_zero_signature_r() {
     image_bundle.manifest.preamble.owner_sigs.ecc_sig.r.fill(0);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_ECC_SIGNATURE_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_ECC_SIGNATURE_INVALID_ARG
-    );
 }
 
 #[test]
@@ -714,15 +632,10 @@ fn test_header_verify_owner_sig_zero_signature_s() {
     image_bundle.manifest.preamble.owner_sigs.ecc_sig.s.fill(0);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(OWNER_ECC_SIGNATURE_INVALID_ARG),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        OWNER_ECC_SIGNATURE_INVALID_ARG
-    );
 }
 
 #[test]
@@ -735,14 +648,10 @@ fn test_toc_invalid_entry_count() {
     update_header(&mut image_bundle);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(TOC_ENTRY_COUNT_INVALID),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        TOC_ENTRY_COUNT_INVALID
-    );
 }
 
 #[test]
@@ -755,14 +664,10 @@ fn test_toc_invalid_toc_digest() {
     update_header(&mut image_bundle);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(TOC_DIGEST_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        TOC_DIGEST_MISMATCH
-    );
 }
 
 #[test]
@@ -784,13 +689,9 @@ fn test_toc_fmc_range_overlap() {
         runtime_new_size,
     );
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_RUNTIME_OVERLAP),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_RUNTIME_OVERLAP
-    );
 
     // Case 2: FMC offset > Runtime offset
     let (mut hw, mut image_bundle) =
@@ -809,13 +710,9 @@ fn test_toc_fmc_range_overlap() {
     );
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_RUNTIME_OVERLAP),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_RUNTIME_OVERLAP
-    );
 
     // // Case 3: FMC start offset < Runtime offset < FMC end offset
     let (mut hw, mut image_bundle) =
@@ -834,13 +731,9 @@ fn test_toc_fmc_range_overlap() {
     );
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_RUNTIME_OVERLAP),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_RUNTIME_OVERLAP
-    );
 }
 
 #[test]
@@ -860,13 +753,9 @@ fn test_toc_fmc_range_incorrect_order() {
         runtime_new_size,
     );
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_RUNTIME_INCORRECT_ORDER),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_RUNTIME_INCORRECT_ORDER
-    );
 }
 
 #[test]
@@ -878,14 +767,10 @@ fn test_fmc_digest_mismatch() {
     image_bundle.fmc[0..4].copy_from_slice(0xDEADBEEFu32.as_bytes());
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_DIGEST_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_DIGEST_MISMATCH
-    );
 }
 
 #[test]
@@ -895,13 +780,9 @@ fn test_fmc_invalid_load_addr_before_iccm() {
 
     let image = update_load_addr(&mut image_bundle, true, ICCM_START_ADDR - 4);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_LOAD_ADDR_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_LOAD_ADDR_INVALID
-    );
 }
 
 #[test]
@@ -911,13 +792,9 @@ fn test_fmc_invalid_load_addr_after_iccm() {
 
     let image = update_load_addr(&mut image_bundle, true, ICCM_END_ADDR + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_LOAD_ADDR_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_LOAD_ADDR_INVALID
-    );
 }
 
 #[test]
@@ -927,13 +804,9 @@ fn test_fmc_load_addr_unaligned() {
     let load_addr = image_bundle.manifest.fmc.load_addr;
     let image = update_load_addr(&mut image_bundle, true, load_addr + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_LOAD_ADDR_UNALIGNED),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_LOAD_ADDR_UNALIGNED
-    );
 }
 
 #[test]
@@ -943,13 +816,9 @@ fn test_fmc_invalid_entry_point_before_iccm() {
 
     let image = update_entry_point(&mut image_bundle, true, ICCM_START_ADDR - 4);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_ENTRY_POINT_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_ENTRY_POINT_INVALID
-    );
 }
 
 #[test]
@@ -959,13 +828,9 @@ fn test_fmc_invalid_entry_point_after_iccm() {
 
     let image = update_entry_point(&mut image_bundle, true, ICCM_END_ADDR + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_ENTRY_POINT_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_ENTRY_POINT_INVALID
-    );
 }
 
 #[test]
@@ -976,13 +841,9 @@ fn test_fmc_entry_point_unaligned() {
 
     let image = update_entry_point(&mut image_bundle, true, entry_point + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_ENTRY_POINT_UNALIGNED),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_ENTRY_POINT_UNALIGNED
-    );
 }
 
 #[test]
@@ -1008,14 +869,10 @@ fn test_fmc_svn_greater_than_32() {
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_SVN_GREATER_THAN_MAX_SUPPORTED),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_SVN_GREATER_THAN_MAX_SUPPORTED
-    );
 }
 
 #[test]
@@ -1041,14 +898,10 @@ fn test_fmc_svn_less_than_min_svn() {
     };
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_SVN_LESS_THAN_MIN_SUPPORTED),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_SVN_LESS_THAN_MIN_SUPPORTED
-    );
 }
 
 #[test]
@@ -1075,14 +928,10 @@ fn test_fmc_svn_less_than_fuse_svn() {
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(FMC_SVN_LESS_THAN_FUSE),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        FMC_SVN_LESS_THAN_FUSE
-    );
 }
 
 #[test]
@@ -1093,14 +942,10 @@ fn test_runtime_digest_mismatch() {
     // Change the FMC image.
     image_bundle.runtime[0..4].copy_from_slice(0xDEADBEEFu32.as_bytes());
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_DIGEST_MISMATCH),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_DIGEST_MISMATCH
-    );
 }
 
 #[test]
@@ -1110,13 +955,9 @@ fn test_runtime_invalid_load_addr_before_iccm() {
 
     let image = update_load_addr(&mut image_bundle, false, ICCM_START_ADDR - 4);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_LOAD_ADDR_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_LOAD_ADDR_INVALID
-    );
 }
 
 #[test]
@@ -1126,13 +967,9 @@ fn test_runtime_invalid_load_addr_after_iccm() {
 
     let image = update_load_addr(&mut image_bundle, false, ICCM_END_ADDR + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_LOAD_ADDR_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_LOAD_ADDR_INVALID
-    );
 }
 
 #[test]
@@ -1142,13 +979,9 @@ fn test_runtime_load_addr_unaligned() {
     let load_addr = image_bundle.manifest.runtime.load_addr;
     let image = update_load_addr(&mut image_bundle, false, load_addr + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_LOAD_ADDR_UNALIGNED),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_LOAD_ADDR_UNALIGNED
-    );
 }
 
 #[test]
@@ -1158,13 +991,9 @@ fn test_runtime_invalid_entry_point_before_iccm() {
 
     let image = update_entry_point(&mut image_bundle, false, ICCM_START_ADDR - 4);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_ENTRY_POINT_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_ENTRY_POINT_INVALID
-    );
 }
 
 #[test]
@@ -1174,13 +1003,9 @@ fn test_runtime_invalid_entry_point_after_iccm() {
 
     let image = update_entry_point(&mut image_bundle, false, ICCM_END_ADDR + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_ENTRY_POINT_INVALID),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_ENTRY_POINT_INVALID
-    );
 }
 
 #[test]
@@ -1190,13 +1015,9 @@ fn test_runtime_entry_point_unaligned() {
     let entry_point = image_bundle.manifest.runtime.entry_point;
     let image = update_entry_point(&mut image_bundle, false, entry_point + 1);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_ENTRY_POINT_UNALIGNED),
         hw.upload_firmware(&image).unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_ENTRY_POINT_UNALIGNED
-    );
 }
 
 #[test]
@@ -1221,14 +1042,10 @@ fn test_runtime_svn_greater_than_64() {
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_SVN_GREATER_THAN_MAX_SUPPORTED),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_SVN_GREATER_THAN_MAX_SUPPORTED
-    );
 }
 
 #[test]
@@ -1255,14 +1072,10 @@ fn test_runtime_svn_less_than_min_svn() {
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
 
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_SVN_LESS_THAN_MIN_SUPPORTED),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        RUNTIME_SVN_LESS_THAN_MIN_SUPPORTED
-    );
 }
 
 #[test]
@@ -1289,7 +1102,7 @@ fn test_runtime_svn_less_than_fuse_svn() {
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
     assert_eq!(
-        ModelError::MailboxCmdFailed,
+        ModelError::MailboxCmdFailed(RUNTIME_SVN_LESS_THAN_FUSE),
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
diff --git a/rom/dev/tests/test_mailbox_errors.rs b/rom/dev/tests/test_mailbox_errors.rs
index de997ade..a036d78c 100644
--- a/rom/dev/tests/test_mailbox_errors.rs
+++ b/rom/dev/tests/test_mailbox_errors.rs
@@ -16,7 +16,7 @@ fn test_unknown_command_is_not_fatal() {
     // This command does not exist
     assert_eq!(
         hw.mailbox_execute(0xabcd_1234, &[]),
-        Err(ModelError::MailboxCmdFailed)
+        Err(ModelError::MailboxCmdFailed(0))
     );
 
     // The ROM does not currently report an error for this
@@ -33,23 +33,17 @@ fn test_unknown_command_is_not_fatal() {
 fn test_mailbox_command_aborted_after_report_error() {
     let (mut hw, image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    assert_eq!(Err(ModelError::MailboxCmdFailed), hw.upload_firmware(&[]));
-
     assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        INVALID_IMAGE_SIZE
+        Err(ModelError::MailboxCmdFailed(INVALID_IMAGE_SIZE)),
+        hw.upload_firmware(&[])
     );
 
     // Make sure a new attempt to upload firmware is rejected (even though this
     // command would otherwise succeed)
-    assert_eq!(
-        hw.upload_firmware(&image_bundle.to_bytes().unwrap()),
-        Err(ModelError::MailboxCmdFailed)
-    );
-
+    //
     // The original failure reason should still be in the register
     assert_eq!(
-        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
-        INVALID_IMAGE_SIZE
+        hw.upload_firmware(&image_bundle.to_bytes().unwrap()),
+        Err(ModelError::MailboxCmdFailed(INVALID_IMAGE_SIZE))
     );
 }
diff --git a/runtime/tests/integration_tests.rs b/runtime/tests/integration_tests.rs
index 23b096e5..9ab7bead 100644
--- a/runtime/tests/integration_tests.rs
+++ b/runtime/tests/integration_tests.rs
@@ -1,7 +1,9 @@
 // Licensed under the Apache-2.0 license.
 
 use caliptra_builder::{FwId, ImageOptions, APP_WITH_UART, FMC_WITH_UART, ROM_WITH_UART};
-use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams};
+use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, ModelError};
+use caliptra_runtime::{CommandId, EcdsaVerifyCmd, EcdsaVerifyResponse};
+use zerocopy::{AsBytes, FromBytes};
 
 // Run test_bin as a ROM image. The is used for faster tests that can run
 // against verilator
@@ -62,41 +64,6 @@ fn run_rt_test(test_bin_name: Option<&'static str>) -> DefaultHwModel {
     model
 }
 
-fn send_mailbox_command(model: &mut DefaultHwModel, cmd_id: u32, arg_buf: &[u32]) -> Vec<u32> {
-    model.soc_mbox().lock().read().lock();
-    model.soc_mbox().cmd().write(|_| cmd_id);
-    model
-        .soc_mbox()
-        .dlen()
-        .write(|_| u32::try_from(arg_buf.len()).unwrap() * 4);
-
-    for word in arg_buf {
-        model.soc_mbox().datain().write(|_| *word);
-    }
-    model
-        .soc_mbox()
-        .execute()
-        .write(|exec_reg| exec_reg.execute(true));
-
-    model.step_until(|m| !m.soc_mbox().status().read().status().cmd_busy());
-
-    let dlen_bytes = model.soc_mbox().dlen().read() as usize;
-    let dlen_words = (dlen_bytes + 3) / 4;
-    let mut resp = vec![0u32; dlen_words];
-
-    for dest_word in resp.iter_mut() {
-        *dest_word = model.soc_mbox().dataout().read();
-    }
-
-    model
-        .soc_mbox()
-        .execute()
-        .write(|exec_reg| exec_reg.execute(false));
-    model.step(); // Step once to get mailbox to idel state
-
-    resp
-}
-
 #[test]
 fn test_standard() {
     // Test that the normal runtime firmware boots.
@@ -117,15 +84,86 @@ fn test_boot() {
 }
 
 #[test]
-fn test_mbox() {
+fn test_verify_cmd() {
+    let mut model = run_rom_test("mbox");
+
+    model.step_until(|m| m.soc_mbox().status().read().mbox_fsm_ps().mbox_idle());
+
+    // Message to hash
+    let msg: &[u32] = &[
+        0xea89d79d, 0x4547c025, 0x1f387ad5, 0xfb01de22, 0x723cbd0a, 0x44fddedb, 0xc11332e4,
+        0xef3e5889, 0x2066ba85, 0xe23dda44, 0xe67086dd, 0x48545132, 0xeebb5501, 0x752c70bb,
+        0x2ec31a78, 0x60189413, 0xe36f57cb, 0x57b7057a, 0x415b5bda, 0xc3d76d8f, 0x402e040b,
+        0x345a39f4, 0xe0dce42a, 0x36c33456, 0x52bce225, 0x1f484543, 0x953d257e, 0x23682651,
+        0x17251b77, 0x51a8b405, 0x372a0266, 0xbdf128ac,
+    ];
+
+    // Stream to SHA ACC
+    let _ = model
+        .compute_sha512_acc_digest(
+            msg,
+            (msg.len() * 4).try_into().unwrap(),
+            /*sha384=*/ true,
+        )
+        .unwrap();
+
+    // ECDSAVS NIST test vector
+    let cmd = EcdsaVerifyCmd {
+        chksum: 0,
+        pub_key_x: [
+            0xcb, 0x90, 0x8b, 0x1f, 0xd5, 0x16, 0xa5, 0x7b, 0x8e, 0xe1, 0xe1, 0x43, 0x83, 0x57,
+            0x9b, 0x33, 0xcb, 0x15, 0x4f, 0xec, 0xe2, 0x0c, 0x50, 0x35, 0xe2, 0xb3, 0x76, 0x51,
+            0x95, 0xd1, 0x95, 0x1d, 0x75, 0xbd, 0x78, 0xfb, 0x23, 0xe0, 0x0f, 0xef, 0x37, 0xd7,
+            0xd0, 0x64, 0xfd, 0x9a, 0xf1, 0x44,
+        ],
+        pub_key_y: [
+            0xcd, 0x99, 0xc4, 0x6b, 0x58, 0x57, 0x40, 0x1d, 0xdc, 0xff, 0x2c, 0xf7, 0xcf, 0x82,
+            0x21, 0x21, 0xfa, 0xf1, 0xcb, 0xad, 0x9a, 0x01, 0x1b, 0xed, 0x8c, 0x55, 0x1f, 0x6f,
+            0x59, 0xb2, 0xc3, 0x60, 0xf7, 0x9b, 0xfb, 0xe3, 0x2a, 0xdb, 0xca, 0xa0, 0x95, 0x83,
+            0xbd, 0xfd, 0xf7, 0xc3, 0x74, 0xbb,
+        ],
+        signature_r: [
+            0x33, 0xf6, 0x4f, 0xb6, 0x5c, 0xd6, 0xa8, 0x91, 0x85, 0x23, 0xf2, 0x3a, 0xea, 0x0b,
+            0xbc, 0xf5, 0x6b, 0xba, 0x1d, 0xac, 0xa7, 0xaf, 0xf8, 0x17, 0xc8, 0x79, 0x1d, 0xc9,
+            0x24, 0x28, 0xd6, 0x05, 0xac, 0x62, 0x9d, 0xe2, 0xe8, 0x47, 0xd4, 0x3c, 0xee, 0x55,
+            0xba, 0x9e, 0x4a, 0x0e, 0x83, 0xba,
+        ],
+        signature_s: [
+            0x44, 0x28, 0xbb, 0x47, 0x8a, 0x43, 0xac, 0x73, 0xec, 0xd6, 0xde, 0x51, 0xdd, 0xf7,
+            0xc2, 0x8f, 0xf3, 0xc2, 0x44, 0x16, 0x25, 0xa0, 0x81, 0x71, 0x43, 0x37, 0xdd, 0x44,
+            0xfe, 0xa8, 0x01, 0x1b, 0xae, 0x71, 0x95, 0x9a, 0x10, 0x94, 0x7b, 0x6e, 0xa3, 0x3f,
+            0x77, 0xe1, 0x28, 0xd3, 0xc6, 0xae,
+        ],
+    };
+
+    let resp = model
+        .mailbox_execute(u32::from(CommandId::ECDSA384_VERIFY), cmd.as_bytes())
+        .unwrap()
+        .unwrap();
+    let resp_bytes: &[u8] = resp.as_bytes();
+    let response = EcdsaVerifyResponse::read_from(resp_bytes).unwrap();
+    assert_eq!(response.result, true as u32);
+}
+
+#[test]
+fn test_unimplemented_cmds() {
     let mut model = run_rom_test("mbox");
 
     model.step_until(|m| m.soc_mbox().status().read().mbox_fsm_ps().mbox_idle());
 
-    let cmd_buf = [0u32; 4];
-    let resp = send_mailbox_command(&mut model, 0x0, &cmd_buf);
+    let cmd = [0u8; 4];
+
+    let expected_err = Err(ModelError::MailboxCmdFailed(0xe0002));
+
+    let mut resp = model.mailbox_execute(u32::from(CommandId::GET_IDEV_CSR), &cmd);
+    assert_eq!(resp, expected_err);
+
+    resp = model.mailbox_execute(u32::from(CommandId::GET_LDEV_CERT), &cmd);
+    assert_eq!(resp, expected_err);
 
-    assert_eq!(resp, vec![0xFFFFFFFFu32; 4]);
+    resp = model.mailbox_execute(u32::from(CommandId::STASH_MEASUREMENT), &cmd);
+    assert_eq!(resp, expected_err);
 
-    assert!(model.soc_mbox().status().read().mbox_fsm_ps().mbox_idle());
+    resp = model.mailbox_execute(u32::from(CommandId::INVOKE_DPE), &cmd);
+    assert_eq!(resp, expected_err);
 }