ast10x0 : drain stale bootloader hash op
diff --git a/hal/blocking/src/cipher.rs b/hal/blocking/src/cipher.rs
index 55d8875..d1aeced 100644
--- a/hal/blocking/src/cipher.rs
+++ b/hal/blocking/src/cipher.rs
@@ -40,6 +40,12 @@
 
     /// Key or IV is invalid or missing.
     KeyError,
+
+    /// The hardware accelerator is busy and cannot process the cipher operation.
+    Busy,
+
+    /// The operation did not complete within the expected time.
+    Timeout,
 }
 
 /// Trait for converting implementation-specific errors into a generic [`ErrorKind`].
@@ -183,7 +189,6 @@
     ///
     /// - `key`: A reference to the key used for the cipher.
     /// - `nonce`: A reference to the nonce or IV used for the cipher.
-    /// - `mode`: The cipher mode to use.
     ///
     /// # Returns
     ///
@@ -192,7 +197,6 @@
         &'a mut self,
         key: &Self::Key,
         nonce: &Self::Nonce,
-        mode: M,
     ) -> Result<Self::CipherContext<'a>, Self::Error>;
 }
 
@@ -232,7 +236,7 @@
 }
 
 /// Optional trait for cipher contexts that support rekeying.
-pub trait CipherRekey<K>: ErrorType {
+pub trait CipherRekey: SymmetricCipher {
     /// Rekeys the cipher context with a new key.
     ///
     /// # Parameters
@@ -242,7 +246,7 @@
     /// # Returns
     ///
     /// A result indicating success or failure.
-    fn rekey(&mut self, new_key: &K) -> Result<(), Self::Error>;
+    fn rekey(&mut self, new_key: &Self::Key) -> Result<(), Self::Error>;
 }
 
 /// Error type for block-aligned container operations.
@@ -410,7 +414,7 @@
 /// - Security managers that don't perform encryption/decryption
 /// - Key stores and vaults with secure cleanup
 /// - Flexible composition with other cipher traits
-pub trait SecureCipherOp: ErrorType {
+pub trait SecureCipherOp {
     /// Securely clear internal state and zeroize sensitive data.
     ///
     /// This method performs a secure cleanup of all internal state, including:
@@ -441,9 +445,9 @@
     /// ```ignore
     /// let mut cipher = SecureAesCipher::new();
     /// // ... perform cipher operations ...
-    /// cipher.clear_state()?; // Secure cleanup before dropping
+    /// cipher.clear_state(); // Secure cleanup before dropping
     /// ```
-    fn clear_state(&mut self) -> Result<(), Self::Error>;
+    fn clear_state(&mut self);
 }
 
 /// Trait for querying cipher status and hardware state.
@@ -586,11 +590,6 @@
     ///
     /// # Common Types
     ///
-    /// - `&[u8]` for read-only associated data
-    /// - `[u8; N]` for fixed-size owned associated data
-    /// - `()` or empty slice if no associated data is needed
-    type AssociatedData: FromBytes + IntoBytes;
-
     /// The authentication tag type for AEAD operations.
     ///
     /// The authentication tag is a cryptographic checksum that provides
@@ -634,7 +633,7 @@
     fn encrypt_aead(
         &mut self,
         plaintext: Self::PlainText,
-        associated_data: Self::AssociatedData,
+        associated_data: &[u8],
     ) -> Result<(Self::CipherText, Self::Tag), Self::Error>;
 
     /// Decrypts the given ciphertext with associated data and authentication tag.
@@ -651,7 +650,7 @@
     fn decrypt_aead(
         &mut self,
         ciphertext: Self::CipherText,
-        associated_data: Self::AssociatedData,
+        associated_data: &[u8],
         tag: Self::Tag,
     ) -> Result<Self::PlainText, Self::Error>;
 }
diff --git a/hal/blocking/src/digest.rs b/hal/blocking/src/digest.rs
index 178bd18..7a62152 100644
--- a/hal/blocking/src/digest.rs
+++ b/hal/blocking/src/digest.rs
@@ -54,7 +54,6 @@
 //! # impl ErrorType for MyDigestImpl { type Error = core::convert::Infallible; }
 //! # impl DigestInit<Sha2_256> for MyDigestImpl {
 //! #     type OpContext<'a> = MyContext<'a> where Self: 'a;
-//! #     type Output = Digest<8>;
 //! #     fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { todo!() }
 //! # }
 //! # struct MyContext<'a>(&'a mut MyDigestImpl);
@@ -81,7 +80,6 @@
 //! # impl ErrorType for MyDigestController { type Error = core::convert::Infallible; }
 //! # impl DigestInit<Sha2_256> for MyDigestController {
 //! #     type Context = MyOwnedContext;
-//! #     type Output = Digest<8>;
 //! #     fn init(self, _: Sha2_256) -> Result<Self::Context, Self::Error> { todo!() }
 //! # }
 //! # struct MyOwnedContext;
@@ -417,9 +415,6 @@
     /// The specified hash algorithm is not supported by the hardware or software implementation.
     UnsupportedAlgorithm,
 
-    /// Failed to allocate memory for the hash computation.
-    MemoryAllocationFailure,
-
     /// Failed to initialize the hash computation context.
     InitializationError,
 
@@ -435,9 +430,6 @@
     /// General hardware failure during hash computation.
     HardwareFailure,
 
-    /// The specified output size is not valid for the hash function.
-    InvalidOutputSize,
-
     /// Insufficient permissions to access the hardware or perform the hash computation.
     PermissionDenied,
 
@@ -450,13 +442,11 @@
         match self {
             Self::InvalidInputLength => write!(f, "invalid input data length"),
             Self::UnsupportedAlgorithm => write!(f, "unsupported hash algorithm"),
-            Self::MemoryAllocationFailure => write!(f, "memory allocation failed"),
             Self::InitializationError => write!(f, "failed to initialize hash computation"),
             Self::UpdateError => write!(f, "error updating hash computation"),
             Self::FinalizationError => write!(f, "error finalizing hash computation"),
             Self::Busy => write!(f, "hardware accelerator is busy"),
             Self::HardwareFailure => write!(f, "hardware failure during hash computation"),
-            Self::InvalidOutputSize => write!(f, "invalid output size for hash function"),
             Self::PermissionDenied => write!(f, "insufficient permissions to access hardware"),
             Self::NotInitialized => write!(f, "hash computation context not initialized"),
         }
@@ -549,7 +539,6 @@
 /// # impl ErrorType for MyDigestImpl { type Error = core::convert::Infallible; }
 /// # impl DigestInit<Sha2_256> for MyDigestImpl {
 /// #     type OpContext<'a> = MyContext<'a> where Self: 'a;
-/// #     type Output = Digest<8>;
 /// #     fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { todo!() }
 /// # }
 /// # struct MyContext<'a>(&'a mut MyDigestImpl);
@@ -571,26 +560,20 @@
     /// This associated type represents the stateful context returned by [`init`](Self::init)
     /// that can be used to perform the actual digest operations via [`DigestOp`].
     /// The lifetime parameter ensures the context cannot outlive the device that created it.
-    type OpContext<'a>: DigestOp<Output = Self::Output>
+    type OpContext<'a>: DigestOp<Output = T::Digest>
     where
         Self: 'a;
 
-    /// The output type produced by this digest implementation.
-    ///
-    /// This type must implement [`IntoBytes`] to allow conversion to byte arrays
-    /// for interoperability with other systems and zero-copy operations.
-    type Output: IntoBytes;
-
     /// Init instance of the crypto function with the given context.
     ///
     /// # Parameters
     ///
-    /// - `init_params`: The context or configuration parameters for the crypto function.
+    /// - `algorithm`: The zero-sized algorithm marker type specifying which hash function to use.
     ///
     /// # Returns
     ///
     /// A new instance of the hash function.
-    fn init(&mut self, init_params: T) -> Result<Self::OpContext<'_>, Self::Error>;
+    fn init(&mut self, algorithm: T) -> Result<Self::OpContext<'_>, Self::Error>;
 }
 
 /// Trait for resetting digest computation contexts.
@@ -748,7 +731,6 @@
     /// # }
     /// # impl DigestInit<Sha2_256> for MyController {
     /// #     type Context = MyContext;
-    /// #     type Output = Digest<8>;
     /// #     fn init(self, _: Sha2_256) -> Result<Self::Context, Self::Error> { todo!() }
     /// # }
     /// let controller = MyController;
@@ -761,13 +743,7 @@
         ///
         /// This context has no lifetime constraints and can be stored in structs,
         /// moved between functions, and persisted across IPC boundaries.
-        type Context: DigestOp<Output = Self::Output, Controller = Self>;
-
-        /// The output type produced by this digest implementation.
-        ///
-        /// This type must implement [`IntoBytes`] to allow conversion to byte arrays
-        /// for interoperability with other systems and zero-copy operations.
-        type Output: IntoBytes;
+        type Context: DigestOp<Output = T::Digest, Controller = Self>;
 
         /// Initialize a new digest computation context.
         ///
@@ -776,12 +752,12 @@
         ///
         /// # Parameters
         ///
-        /// - `init_params`: Algorithm-specific initialization parameters
+        /// - `algorithm`: The zero-sized algorithm marker type specifying which hash function to use.
         ///
         /// # Returns
         ///
         /// An owned context that can be used for digest operations.
-        fn init(self, init_params: T) -> Result<Self::Context, Self::Error>;
+        fn init(self, algorithm: T) -> Result<Self::Context, Self::Error>;
     }
 
     /// Trait for performing digest operations with owned contexts.
diff --git a/platform/impls/baremetal/mock/src/hash.rs b/platform/impls/baremetal/mock/src/hash.rs
index 640f1ea..b0b9914 100644
--- a/platform/impls/baremetal/mock/src/hash.rs
+++ b/platform/impls/baremetal/mock/src/hash.rs
@@ -83,13 +83,12 @@
     ($algo:ident) => {
         impl DigestInit<$algo> for MockDigestDevice {
             type OpContext<'a> = MockHasher<'a, $algo>;
-            type Output = <$algo as DigestAlgorithm>::Digest;
 
-            fn init(&mut self, init_params: $algo) -> Result<Self::OpContext<'_>, Self::Error> {
+            fn init(&mut self, algorithm: $algo) -> Result<Self::OpContext<'_>, Self::Error> {
                 // In a real implementation, we'd configure the hardware here
                 Ok(Self::OpContext {
                     hw: self,
-                    _alg: init_params,
+                    _alg: algorithm,
                     data_processed: 0,
                 })
             }
@@ -183,14 +182,13 @@
         ($algo:ident) => {
             impl DigestInit<$algo> for MockDigestController {
                 type Context = MockOwnedContext<$algo>;
-                type Output = <$algo as DigestAlgorithm>::Digest;
 
-                fn init(self, init_params: $algo) -> Result<Self::Context, Self::Error> {
+                fn init(self, algorithm: $algo) -> Result<Self::Context, Self::Error> {
                     // Controller moves into the context
                     // In hardware implementation, this might claim hardware resources
                     Ok(MockOwnedContext {
                         controller: self,
-                        algorithm: init_params,
+                        algorithm,
                         data_processed: 0,
                     })
                 }
diff --git a/platform/impls/rustcrypto/src/cipher.rs b/platform/impls/rustcrypto/src/cipher.rs
index cc0fc70..15664cc 100644
--- a/platform/impls/rustcrypto/src/cipher.rs
+++ b/platform/impls/rustcrypto/src/cipher.rs
@@ -233,7 +233,6 @@
         &'a mut self,
         key: &Self::Key,
         nonce: &Self::Nonce,
-        _mode: Aes256CtrMode,
     ) -> Result<Self::CipherContext<'a>, Self::Error> {
         // Validate key and nonce lengths (compile-time guaranteed by types)
         // Create new context with the provided key and IV
@@ -409,7 +408,7 @@
     #[test]
     fn test_cipher_init_trait() {
         let mut cipher = Aes256CtrCipher;
-        let result = cipher.init(&TEST_KEY, &TEST_IV, Aes256CtrMode);
+        let result = cipher.init(&TEST_KEY, &TEST_IV);
         assert!(
             result.is_ok(),
             "Failed to create context via CipherInit trait"
diff --git a/platform/impls/rustcrypto/src/controller.rs b/platform/impls/rustcrypto/src/controller.rs
index e04265e..12540cd 100644
--- a/platform/impls/rustcrypto/src/controller.rs
+++ b/platform/impls/rustcrypto/src/controller.rs
@@ -69,7 +69,7 @@
     fn kind(&self) -> DigestErrorKind {
         match self {
             CryptoError::InvalidKeyLength => DigestErrorKind::InvalidInputLength,
-            CryptoError::InvalidOutputLength => DigestErrorKind::InvalidOutputSize,
+            CryptoError::InvalidOutputLength => DigestErrorKind::FinalizationError,
             CryptoError::OperationFailed => DigestErrorKind::HardwareFailure,
         }
     }
@@ -218,7 +218,6 @@
 // Digest initialization - creates SHA-256 context
 impl DigestInit<Sha2_256> for RustCryptoController {
     type Context = DigestContext256;
-    type Output = Digest<8>; // SHA-256 output as 8 words of 32 bits
 
     fn init(self, _algorithm: Sha2_256) -> Result<Self::Context, Self::Error> {
         Ok(DigestContext256(Sha256::new()))
@@ -228,7 +227,6 @@
 // Digest initialization - creates SHA-384 context
 impl DigestInit<Sha2_384> for RustCryptoController {
     type Context = DigestContext384;
-    type Output = Digest<12>; // SHA-384 output as 12 words of 32 bits
 
     fn init(self, _algorithm: Sha2_384) -> Result<Self::Context, Self::Error> {
         Ok(DigestContext384(Sha384::new()))
@@ -238,7 +236,6 @@
 // Digest initialization - creates SHA-512 context
 impl DigestInit<Sha2_512> for RustCryptoController {
     type Context = DigestContext512;
-    type Output = Digest<16>; // SHA-512 output as 16 words of 32 bits
 
     fn init(self, _algorithm: Sha2_512) -> Result<Self::Context, Self::Error> {
         Ok(DigestContext512(Sha512::new()))
diff --git a/target/ast10x0/peripherals/hace/aes.rs b/target/ast10x0/peripherals/hace/aes.rs
index 5c2d92e..19b5221 100644
--- a/target/ast10x0/peripherals/hace/aes.rs
+++ b/target/ast10x0/peripherals/hace/aes.rs
@@ -61,9 +61,7 @@
     ///
     /// # Safety
     /// No concurrent or reentrant HACE access for the returned lifetime.
-    pub unsafe fn from_device<Y: FnMut(u32)>(
-        device: &'a mut super::device::HaceDevice<Y>,
-    ) -> Self {
+    pub unsafe fn from_device<Y: FnMut(u32)>(device: &'a mut super::device::HaceDevice<Y>) -> Self {
         // Borrow split; retained `yield_fn` keeps the device exclusively borrowed.
         let regs = device.regs;
         let poll_budget = device.poll_budget;
@@ -96,10 +94,7 @@
         output: &mut [u8],
     ) -> Result<(), HaceError> {
         // Enforce block-aligned sizing before programming the engine.
-        if input.is_empty()
-            || input.len() % AES_BLOCK != 0
-            || output.len() < input.len()
-        {
+        if input.is_empty() || input.len() % AES_BLOCK != 0 || output.len() < input.len() {
             return Err(HaceError::InvalidInput);
         }
         let kbits = Self::keylen_bits(key)?;
@@ -120,10 +115,7 @@
         self.ctx.dst.addr = out_ptr;
         self.ctx.dst.len = len | HACE_SG_LAST;
 
-        let cmd = AES_CMD_BASE
-            | kbits
-            | mode_bits
-            | if encrypt { HACE_CMD_ENCRYPT } else { 0 };
+        let cmd = AES_CMD_BASE | kbits | mode_bits | if encrypt { HACE_CMD_ENCRYPT } else { 0 };
         self.ctx.cmd = cmd;
 
         // Program descriptor addresses, ctx base, and data length.
@@ -198,11 +190,11 @@
     }
 }
 
-    // ===== Optional openprot cipher-trait skin (ADR-A1) =====================
-    //
-    // Thin fixed-`N` wrapper over `AesCipher`. Kept separate because
-    // `SymmetricCipher` uses fixed associated buffer types and cannot express
-    // large streaming DMA paths.
+// ===== Optional openprot cipher-trait skin (ADR-A1) =====================
+//
+// Thin fixed-`N` wrapper over `AesCipher`. Kept separate because
+// `SymmetricCipher` uses fixed associated buffer types and cannot express
+// large streaming DMA paths.
 
 /// AES-ECB mode marker (port-defined; the hal declares no concrete modes).
 #[derive(Debug, Clone, Copy)]
@@ -219,6 +211,8 @@
 /// Owned AES key for the trait skin (raw-key path only).
 ///
 /// Size selects variant: 16 => AES-128, 32 => AES-256.
+///
+/// The key bytes are zeroized when this value is dropped.
 #[derive(Clone)]
 pub enum AesKey {
     Aes128([u8; 16]),
@@ -233,6 +227,19 @@
             AesKey::Aes256(k) => k,
         }
     }
+
+    fn zeroize(&mut self) {
+        match self {
+            AesKey::Aes128(k) => k.fill(0),
+            AesKey::Aes256(k) => k.fill(0),
+        }
+    }
+}
+
+impl Drop for AesKey {
+    fn drop(&mut self) {
+        self.zeroize();
+    }
 }
 
 /// Fixed-`N` openprot cipher-trait skin bound to one [`HaceDevice`].
@@ -295,7 +302,6 @@
                 &'a mut self,
                 key: &Self::Key,
                 nonce: &Self::Nonce,
-                _mode: $mode,
             ) -> Result<Self::CipherContext<'a>, Self::Error> {
                 // SAFETY: `AesSkin::new` guarantees non-reentrancy; reborrow is exclusive.
                 let core = unsafe { AesCipher::from_device(&mut *self.dev) };
@@ -314,6 +320,7 @@
 
 impl<'a, const N: usize> CipherOp<Ecb> for AesOp<'a, N, Ecb> {
     fn encrypt(&mut self, plaintext: [u8; N]) -> Result<[u8; N], HaceError> {
+        const { assert!(N % AES_BLOCK == 0, "AesSkin<N>: N must be a multiple of 16") };
         let mut ct = [0u8; N];
         self.core
             .ecb_encrypt(self.key.as_slice(), &plaintext, &mut ct)?;
@@ -321,6 +328,7 @@
     }
 
     fn decrypt(&mut self, ciphertext: [u8; N]) -> Result<[u8; N], HaceError> {
+        const { assert!(N % AES_BLOCK == 0, "AesSkin<N>: N must be a multiple of 16") };
         let mut pt = [0u8; N];
         self.core
             .ecb_decrypt(self.key.as_slice(), &ciphertext, &mut pt)?;
@@ -330,16 +338,27 @@
 
 impl<'a, const N: usize> CipherOp<Cbc> for AesOp<'a, N, Cbc> {
     fn encrypt(&mut self, plaintext: [u8; N]) -> Result<[u8; N], HaceError> {
+        const { assert!(N % AES_BLOCK == 0, "AesSkin<N>: N must be a multiple of 16") };
         let mut ct = [0u8; N];
         self.core
             .cbc_encrypt(self.key.as_slice(), &self.iv, &plaintext, &mut ct)?;
+        // Advance IV to last ciphertext block so sequential encrypt() calls
+        // form a correct CBC chain instead of reusing the original IV.
+        if let Some(last) = ct.get(N - AES_BLOCK..) {
+            self.iv.copy_from_slice(last);
+        }
         Ok(ct)
     }
 
     fn decrypt(&mut self, ciphertext: [u8; N]) -> Result<[u8; N], HaceError> {
+        const { assert!(N % AES_BLOCK == 0, "AesSkin<N>: N must be a multiple of 16") };
         let mut pt = [0u8; N];
         self.core
             .cbc_decrypt(self.key.as_slice(), &self.iv, &ciphertext, &mut pt)?;
+        // Advance IV to last ciphertext block (CBC decrypt chaining).
+        if let Some(last) = ciphertext.get(N - AES_BLOCK..) {
+            self.iv.copy_from_slice(last);
+        }
         Ok(pt)
     }
 }
diff --git a/target/ast10x0/peripherals/hace/device.rs b/target/ast10x0/peripherals/hace/device.rs
index 72b8eba..8c6df49 100644
--- a/target/ast10x0/peripherals/hace/device.rs
+++ b/target/ast10x0/peripherals/hace/device.rs
@@ -4,8 +4,9 @@
 //! HACE device binding with cooperative yield.
 
 use super::constants::DEFAULT_POLL_BUDGET;
-use super::context::{CryptoContext, HashContext, acquire_crypto_ctx, acquire_shared_ctx};
+use super::context::{acquire_crypto_ctx, acquire_shared_ctx, CryptoContext, HashContext};
 use super::registers::HaceRegisters;
+use crate::scu::{ClockRegisterHalf, ScuRegisters};
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum HashAlgo {
@@ -95,9 +96,31 @@
     /// Caller must coordinate singleton access globally.
     /// This type is non-reentrant: only one `HaceDevice` may be active at a time.
     pub unsafe fn new_global_with_yield(yield_fn: Y) -> Self {
+        // SCU080 bit 13 = StopYCLKForHACE. Write bit 13 to SCU084 to clear the
+        // clock-stop bit and enable the HACE YCLK before touching any HACE registers.
+        // SAFETY: SCU is a singleton; this is called under the same single-instance
+        // contract as HaceRegisters::new_global — the caller guarantees exclusivity.
+        let scu = unsafe { ScuRegisters::new_global_unlocked() };
+        scu.ungate_clock_mask(ClockRegisterHalf::Lower, 1 << 13);
+
+        // SAFETY: Caller coordinates singleton access.
+        let regs = unsafe { HaceRegisters::new_global() };
+
+        // Drain any in-progress hash operation left by the bootloader.
+        // On real hardware the engine may be busy when firmware starts; the
+        // HACE W1C clear of `hash_intflag` is ignored while `HashEngStsFlag`
+        // (bit 0) is set, causing the first poll to return a stale flag and the
+        // digest buffer to read back as the IV (never written by the engine).
+        // We stop the engine, then spin until it goes idle, then clear the
+        // stale flag before handing the device to callers.
+        regs.stop_hash_operation();
+        while regs.hash_engine_is_busy() {
+            core::hint::spin_loop();
+        }
+        regs.clear_hash_intflag();
+
         Self {
-            // SAFETY: Caller coordinates singleton access.
-            regs: unsafe { HaceRegisters::new_global() },
+            regs,
             // SAFETY: the `unsafe fn new*` single-instance contract makes this
             // the sole live device, hence the sole holder of these pointers.
             ctx: unsafe { acquire_shared_ctx() },
diff --git a/target/ast10x0/peripherals/hace/digest.rs b/target/ast10x0/peripherals/hace/digest.rs
index 303aaf8..0ea53b4 100644
--- a/target/ast10x0/peripherals/hace/digest.rs
+++ b/target/ast10x0/peripherals/hace/digest.rs
@@ -3,18 +3,22 @@
 
 //! Generic HACE Digest HAL adapter for OpenPRoT
 
+use super::constants::{
+    HACE_SG_LAST, POLL_YIELD_NS, SHA256_HASH_CMD, SHA384_HASH_CMD, SHA512_HASH_CMD,
+};
 use super::context::{
-    HACE_BLOCK_SIZE, HACE_BLOCK_SIZE_128, HashContext, SHA256_DIGEST_SIZE, SHA256_IV,
+    HashContext, HACE_BLOCK_SIZE, HACE_BLOCK_SIZE_128, SHA256_DIGEST_SIZE, SHA256_IV,
     SHA384_DIGEST_SIZE, SHA384_IV, SHA512_DIGEST_SIZE, SHA512_IV,
 };
 use super::error::HaceError;
 use super::helpers::{fill_padding, load_iv, ptr_to_u32};
-use super::constants::{HACE_SG_LAST, POLL_YIELD_NS, SHA256_HASH_CMD, SHA384_HASH_CMD, SHA512_HASH_CMD};
 use super::registers::HaceRegisters;
-use openprot_hal_blocking::digest::{Digest, DigestAlgorithm, ErrorType, Sha2_256, Sha2_384, Sha2_512};
-use openprot_hal_blocking::digest::scoped::{DigestCtrlReset, DigestInit, DigestOp};
-use zerocopy::IntoBytes;
 use core::marker::PhantomData;
+use openprot_hal_blocking::digest::scoped::{DigestCtrlReset, DigestInit, DigestOp};
+use openprot_hal_blocking::digest::{
+    Digest, DigestAlgorithm, ErrorType, Sha2_256, Sha2_384, Sha2_512,
+};
+use zerocopy::IntoBytes;
 
 /// Per-algorithm constants required by the HACE driver.
 ///
@@ -116,9 +120,7 @@
     /// # Safety
     /// Caller must ensure no concurrent or reentrant HACE access for the
     /// lifetime of the returned [`HaceDigest`].
-    pub unsafe fn from_device<Y: FnMut(u32)>(
-        device: &'a mut super::device::HaceDevice<Y>,
-    ) -> Self {
+    pub unsafe fn from_device<Y: FnMut(u32)>(device: &'a mut super::device::HaceDevice<Y>) -> Self {
         // Borrow split. `regs`/`poll_budget`/`ctx` are `Copy`d out; the
         // retained `&'a mut device.yield_fn` reborrow pins `&'a mut HaceDevice`
         // for the whole life of the returned op — that is the arbiter: a
@@ -140,7 +142,6 @@
     }
 }
 
-
 impl<'a, T: DigestAlgorithm> ErrorType for HaceDigest<'a, T> {
     type Error = HaceError;
 }
@@ -149,8 +150,10 @@
 where
     T::Digest: IntoBytes,
 {
-    type OpContext<'b> = HaceDigest<'b, T> where Self: 'b;
-    type Output = T::Digest;
+    type OpContext<'b>
+        = HaceDigest<'b, T>
+    where
+        Self: 'b;
 
     fn init(&mut self, _algo: T) -> Result<Self::OpContext<'_>, Self::Error> {
         // Mirror aspeed-rust init sequence exactly:
@@ -229,7 +232,8 @@
         let method = self.ctx.method;
 
         self.regs.clear_hash_intflag();
-        self.regs.program_hash_operation(sg_addr, digest_addr, total_len, method);
+        self.regs
+            .program_hash_operation(sg_addr, digest_addr, total_len, method);
 
         let mut done = false;
         for _ in 0..self.poll_budget {
@@ -272,7 +276,8 @@
         let method = this.ctx.method;
 
         this.regs.clear_hash_intflag();
-        this.regs.program_hash_operation(sg_addr, digest_addr, bufcnt, method);
+        this.regs
+            .program_hash_operation(sg_addr, digest_addr, bufcnt, method);
 
         for _ in 0..this.poll_budget {
             if this.regs.hash_intflag_is_set() {
@@ -303,4 +308,3 @@
         Ok(())
     }
 }
-
diff --git a/target/ast10x0/peripherals/hace/error.rs b/target/ast10x0/peripherals/hace/error.rs
index 8a43297..19bcb5f 100644
--- a/target/ast10x0/peripherals/hace/error.rs
+++ b/target/ast10x0/peripherals/hace/error.rs
@@ -44,8 +44,8 @@
 impl CipherError for HaceError {
     fn kind(&self) -> CipherErrorKind {
         match self {
-            HaceError::Busy => CipherErrorKind::HardwareFailure,
-            HaceError::Timeout => CipherErrorKind::HardwareFailure,
+            HaceError::Busy => CipherErrorKind::Busy,
+            HaceError::Timeout => CipherErrorKind::Timeout,
             HaceError::InvalidInput => CipherErrorKind::InvalidInput,
             HaceError::Internal => CipherErrorKind::HardwareFailure,
         }
diff --git a/target/ast10x0/peripherals/hace/helpers.rs b/target/ast10x0/peripherals/hace/helpers.rs
index f198b87..3e38631 100644
--- a/target/ast10x0/peripherals/hace/helpers.rs
+++ b/target/ast10x0/peripherals/hace/helpers.rs
@@ -22,7 +22,11 @@
 
     let index = (bufcnt + remaining) & (block_size - 1);
     let padlen = if block_size == 64 {
-        if index < 56 { 56 - index } else { 64 + 56 - index }
+        if index < 56 {
+            56 - index
+        } else {
+            64 + 56 - index
+        }
     } else if index < 112 {
         112 - index
     } else {
diff --git a/target/ast10x0/peripherals/hace/hmac.rs b/target/ast10x0/peripherals/hace/hmac.rs
index 095b9c4..3a7f736 100644
--- a/target/ast10x0/peripherals/hace/hmac.rs
+++ b/target/ast10x0/peripherals/hace/hmac.rs
@@ -77,7 +77,10 @@
         }
         let mut bytes = [0u8; HMAC_KEY_CAP];
         bytes[..key.len()].copy_from_slice(key);
-        Ok(Self { bytes, len: key.len() })
+        Ok(Self {
+            bytes,
+            len: key.len(),
+        })
     }
 
     #[inline]
@@ -104,9 +107,7 @@
     /// # Safety
     /// Caller must ensure no concurrent or reentrant HACE access for the
     /// lifetime of HMAC operations created from this controller.
-    pub unsafe fn from_device<Y: FnMut(u32)>(
-        device: &mut super::device::HaceDevice<Y>,
-    ) -> Self {
+    pub unsafe fn from_device<Y: FnMut(u32)>(device: &mut super::device::HaceDevice<Y>) -> Self {
         Self::new(device.poll_budget)
     }
 }
diff --git a/target/ast10x0/peripherals/hace/mod.rs b/target/ast10x0/peripherals/hace/mod.rs
index 8f28307..a892337 100644
--- a/target/ast10x0/peripherals/hace/mod.rs
+++ b/target/ast10x0/peripherals/hace/mod.rs
@@ -6,16 +6,16 @@
 mod aes;
 mod constants;
 mod context;
+mod device;
 mod digest;
 mod error;
-mod device;
 mod helpers;
 mod hmac;
 mod registers;
 
-pub use aes::{AES_BLOCK, AesCipher, AesKey, AesOp, AesSkin, Cbc, Ecb};
+pub use aes::{AesCipher, AesKey, AesOp, AesSkin, Cbc, Ecb, AES_BLOCK};
+pub use device::{HaceDevice, HashAlgo};
 pub use digest::HaceDigest;
 pub use error::HaceError;
-pub use device::{HaceDevice, HashAlgo};
 pub use hmac::{HaceHmac, HaceHmacCtx, HmacKey, HMAC_KEY_CAP};
 pub use registers::HaceRegisters;
diff --git a/target/ast10x0/peripherals/hace/registers.rs b/target/ast10x0/peripherals/hace/registers.rs
index 23000a3..049673e 100644
--- a/target/ast10x0/peripherals/hace/registers.rs
+++ b/target/ast10x0/peripherals/hace/registers.rs
@@ -53,6 +53,13 @@
         self.regs().hace1c().read().hash_intflag().bit_is_set()
     }
 
+    /// Returns `true` while the hash engine is actively processing (HACE1C bit 0).
+    /// Used to drain any in-progress bootloader operation before starting our own.
+    #[inline]
+    pub(crate) fn hash_engine_is_busy(&self) -> bool {
+        self.regs().hace1c().read().hash_eng_sts_flag().bit_is_set()
+    }
+
     #[inline]
     pub(crate) fn program_hash_operation(
         &self,
@@ -63,8 +70,12 @@
     ) {
         // SAFETY: Callers provide HACE-usable physical addresses and a valid command.
         self.regs().hace20().write(|w| unsafe { w.bits(src_addr) });
-        self.regs().hace24().write(|w| unsafe { w.bits(digest_addr) });
-        self.regs().hace28().write(|w| unsafe { w.bits(digest_addr) });
+        self.regs()
+            .hace24()
+            .write(|w| unsafe { w.bits(digest_addr) });
+        self.regs()
+            .hace28()
+            .write(|w| unsafe { w.bits(digest_addr) });
         self.regs().hace2c().write(|w| unsafe { w.bits(data_len) });
         self.regs().hace30().write(|w| unsafe { w.bits(cmd) });
     }
diff --git a/target/ast10x0/tests/peripherals/hace/hace_aes/target.rs b/target/ast10x0/tests/peripherals/hace/hace_aes/target.rs
index 6be2d68..60fc8d8 100644
--- a/target/ast10x0/tests/peripherals/hace/hace_aes/target.rs
+++ b/target/ast10x0/tests/peripherals/hace/hace_aes/target.rs
@@ -15,7 +15,7 @@
 use codegen as _;
 use console_backend::console_backend_write_all;
 use entry as _;
-use target_common::{TargetInterface, declare_target};
+use target_common::{declare_target, TargetInterface};
 
 pub struct Target {}
 
@@ -93,19 +93,78 @@
 
     let board = Ast10x0Board::new(Ast10x0BoardDescriptor {
         pinctrl_groups: &[],
+        i2c_buses: &[],
     });
     // SAFETY: test runs once at boot with exclusive access to the board.
-    unsafe { board.init() };
+    unsafe {
+        let _ = board.init();
+    };
 
     // NIST SP 800-38A KATs.
-    kat!("ecb-128 encrypt", ecb_encrypt, &AES128_KEY, &CBC_IV, &PT64, &ECB128_CT);
-    kat!("ecb-128 decrypt", ecb_decrypt, &AES128_KEY, &CBC_IV, &ECB128_CT, &PT64);
-    kat!("ecb-256 encrypt", ecb_encrypt, &AES256_KEY, &CBC_IV, &PT64, &ECB256_CT);
-    kat!("ecb-256 decrypt", ecb_decrypt, &AES256_KEY, &CBC_IV, &ECB256_CT, &PT64);
-    kat!("cbc-128 encrypt", cbc_encrypt, &AES128_KEY, &CBC_IV, &PT64, &CBC128_CT);
-    kat!("cbc-128 decrypt", cbc_decrypt, &AES128_KEY, &CBC_IV, &CBC128_CT, &PT64);
-    kat!("cbc-256 encrypt", cbc_encrypt, &AES256_KEY, &CBC_IV, &PT64, &CBC256_CT);
-    kat!("cbc-256 decrypt", cbc_decrypt, &AES256_KEY, &CBC_IV, &CBC256_CT, &PT64);
+    kat!(
+        "ecb-128 encrypt",
+        ecb_encrypt,
+        &AES128_KEY,
+        &CBC_IV,
+        &PT64,
+        &ECB128_CT
+    );
+    kat!(
+        "ecb-128 decrypt",
+        ecb_decrypt,
+        &AES128_KEY,
+        &CBC_IV,
+        &ECB128_CT,
+        &PT64
+    );
+    kat!(
+        "ecb-256 encrypt",
+        ecb_encrypt,
+        &AES256_KEY,
+        &CBC_IV,
+        &PT64,
+        &ECB256_CT
+    );
+    kat!(
+        "ecb-256 decrypt",
+        ecb_decrypt,
+        &AES256_KEY,
+        &CBC_IV,
+        &ECB256_CT,
+        &PT64
+    );
+    kat!(
+        "cbc-128 encrypt",
+        cbc_encrypt,
+        &AES128_KEY,
+        &CBC_IV,
+        &PT64,
+        &CBC128_CT
+    );
+    kat!(
+        "cbc-128 decrypt",
+        cbc_decrypt,
+        &AES128_KEY,
+        &CBC_IV,
+        &CBC128_CT,
+        &PT64
+    );
+    kat!(
+        "cbc-256 encrypt",
+        cbc_encrypt,
+        &AES256_KEY,
+        &CBC_IV,
+        &PT64,
+        &CBC256_CT
+    );
+    kat!(
+        "cbc-256 decrypt",
+        cbc_decrypt,
+        &AES256_KEY,
+        &CBC_IV,
+        &CBC256_CT,
+        &PT64
+    );
 
     // Non-block-size input must return InvalidInput.
     {
diff --git a/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs b/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs
index 2752209..b20e921 100644
--- a/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs
+++ b/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs
@@ -13,7 +13,7 @@
 use openprot_hal_blocking::digest::{Sha2_256, Sha2_384, Sha2_512};
 use openprot_hal_blocking::mac::scoped::{MacInit, MacOp};
 use openprot_hal_blocking::mac::{HmacSha2_256, HmacSha2_384, HmacSha2_512};
-use target_common::{TargetInterface, declare_target};
+use target_common::{declare_target, TargetInterface};
 
 pub struct Target {}
 
@@ -138,9 +138,12 @@
 
     let board = Ast10x0Board::new(Ast10x0BoardDescriptor {
         pinctrl_groups: &[],
+        i2c_buses: &[],
     });
     // SAFETY: test runs once at boot with exclusive access to the board.
-    unsafe { board.init() };
+    unsafe {
+        let _ = board.init();
+    };
 
     // --- SHA-256 ---
     oneshot_case!("sha256 empty", Sha2_256, Sha2_256, b"", EMPTY_256);
@@ -158,9 +161,30 @@
     // --- Production streaming path: 9000 B fed as 4096 + 4096 + 808 ---
     // 4096 is a multiple of both 64 and 128, so every full chunk lands on an
     // exact block boundary: this is the dominant PFR workload (goal.md §3.5).
-    stream_case!("sha256 stream-9000", Sha2_256, Sha2_256, 9000usize, 4096usize, STREAM9000_256);
-    stream_case!("sha384 stream-9000", Sha2_384, Sha2_384, 9000usize, 4096usize, STREAM9000_384);
-    stream_case!("sha512 stream-9000", Sha2_512, Sha2_512, 9000usize, 4096usize, STREAM9000_512);
+    stream_case!(
+        "sha256 stream-9000",
+        Sha2_256,
+        Sha2_256,
+        9000usize,
+        4096usize,
+        STREAM9000_256
+    );
+    stream_case!(
+        "sha384 stream-9000",
+        Sha2_384,
+        Sha2_384,
+        9000usize,
+        4096usize,
+        STREAM9000_384
+    );
+    stream_case!(
+        "sha512 stream-9000",
+        Sha2_512,
+        Sha2_512,
+        9000usize,
+        4096usize,
+        STREAM9000_512
+    );
 
     // --- D2 delta case (goal.md D2) ---
     // SHA-256, block 64. update(100): buffers a 36-byte remainder. update(28):
@@ -242,30 +266,126 @@
     // --- HMAC (software RFC-2104 over the HACE hasher), RFC-4231 vectors ---
     // Cases 1-4,6,7. Case 6/7 use a 131-byte key (> block size) exercising the
     // RFC-2104-correct `key_len > block_size` reduction path.
-    hmac_case!("hmac-sha256 rfc4231-1", HmacSha2_256, &HMAC_K1, b"Hi There", HMAC_C1_256);
-    hmac_case!("hmac-sha256 rfc4231-2", HmacSha2_256, b"Jefe", b"what do ya want for nothing?", HMAC_C2_256);
-    hmac_case!("hmac-sha256 rfc4231-3", HmacSha2_256, &HMAC_K3, &HMAC_D3, HMAC_C3_256);
-    hmac_case!("hmac-sha256 rfc4231-4", HmacSha2_256, &HMAC_K4, &HMAC_D4, HMAC_C4_256);
-    hmac_case!("hmac-sha256 rfc4231-6", HmacSha2_256, &HMAC_K6, b"Test Using Larger Than Block-Size Key - Hash Key First", HMAC_C6_256);
+    hmac_case!(
+        "hmac-sha256 rfc4231-1",
+        HmacSha2_256,
+        &HMAC_K1,
+        b"Hi There",
+        HMAC_C1_256
+    );
+    hmac_case!(
+        "hmac-sha256 rfc4231-2",
+        HmacSha2_256,
+        b"Jefe",
+        b"what do ya want for nothing?",
+        HMAC_C2_256
+    );
+    hmac_case!(
+        "hmac-sha256 rfc4231-3",
+        HmacSha2_256,
+        &HMAC_K3,
+        &HMAC_D3,
+        HMAC_C3_256
+    );
+    hmac_case!(
+        "hmac-sha256 rfc4231-4",
+        HmacSha2_256,
+        &HMAC_K4,
+        &HMAC_D4,
+        HMAC_C4_256
+    );
+    hmac_case!(
+        "hmac-sha256 rfc4231-6",
+        HmacSha2_256,
+        &HMAC_K6,
+        b"Test Using Larger Than Block-Size Key - Hash Key First",
+        HMAC_C6_256
+    );
     hmac_case!("hmac-sha256 rfc4231-7", HmacSha2_256, &HMAC_K7, b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", HMAC_C7_256);
 
-    hmac_case!("hmac-sha384 rfc4231-1", HmacSha2_384, &HMAC_K1, b"Hi There", HMAC_C1_384);
-    hmac_case!("hmac-sha384 rfc4231-2", HmacSha2_384, b"Jefe", b"what do ya want for nothing?", HMAC_C2_384);
-    hmac_case!("hmac-sha384 rfc4231-3", HmacSha2_384, &HMAC_K3, &HMAC_D3, HMAC_C3_384);
-    hmac_case!("hmac-sha384 rfc4231-4", HmacSha2_384, &HMAC_K4, &HMAC_D4, HMAC_C4_384);
-    hmac_case!("hmac-sha384 rfc4231-6", HmacSha2_384, &HMAC_K6, b"Test Using Larger Than Block-Size Key - Hash Key First", HMAC_C6_384);
+    hmac_case!(
+        "hmac-sha384 rfc4231-1",
+        HmacSha2_384,
+        &HMAC_K1,
+        b"Hi There",
+        HMAC_C1_384
+    );
+    hmac_case!(
+        "hmac-sha384 rfc4231-2",
+        HmacSha2_384,
+        b"Jefe",
+        b"what do ya want for nothing?",
+        HMAC_C2_384
+    );
+    hmac_case!(
+        "hmac-sha384 rfc4231-3",
+        HmacSha2_384,
+        &HMAC_K3,
+        &HMAC_D3,
+        HMAC_C3_384
+    );
+    hmac_case!(
+        "hmac-sha384 rfc4231-4",
+        HmacSha2_384,
+        &HMAC_K4,
+        &HMAC_D4,
+        HMAC_C4_384
+    );
+    hmac_case!(
+        "hmac-sha384 rfc4231-6",
+        HmacSha2_384,
+        &HMAC_K6,
+        b"Test Using Larger Than Block-Size Key - Hash Key First",
+        HMAC_C6_384
+    );
     hmac_case!("hmac-sha384 rfc4231-7", HmacSha2_384, &HMAC_K7, b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", HMAC_C7_384);
 
     // Isolation: K6_512 == SHA512(K6). Feeding it as a 64-byte (<=block) key
     // takes hmac.rs's NON-reduce branch but yields the identical K0, so it must
     // equal HMAC(K6,msg). If this PASSES, the in-init kd reduction is the bug.
-    hmac_case!("hmac-sha512 prereduced6", HmacSha2_512, &K6_512, b"Test Using Larger Than Block-Size Key - Hash Key First", HMAC_C6_512);
+    hmac_case!(
+        "hmac-sha512 prereduced6",
+        HmacSha2_512,
+        &K6_512,
+        b"Test Using Larger Than Block-Size Key - Hash Key First",
+        HMAC_C6_512
+    );
 
-    hmac_case!("hmac-sha512 rfc4231-1", HmacSha2_512, &HMAC_K1, b"Hi There", HMAC_C1_512);
-    hmac_case!("hmac-sha512 rfc4231-2", HmacSha2_512, b"Jefe", b"what do ya want for nothing?", HMAC_C2_512);
-    hmac_case!("hmac-sha512 rfc4231-3", HmacSha2_512, &HMAC_K3, &HMAC_D3, HMAC_C3_512);
-    hmac_case!("hmac-sha512 rfc4231-4", HmacSha2_512, &HMAC_K4, &HMAC_D4, HMAC_C4_512);
-    hmac_case!("hmac-sha512 rfc4231-6", HmacSha2_512, &HMAC_K6, b"Test Using Larger Than Block-Size Key - Hash Key First", HMAC_C6_512);
+    hmac_case!(
+        "hmac-sha512 rfc4231-1",
+        HmacSha2_512,
+        &HMAC_K1,
+        b"Hi There",
+        HMAC_C1_512
+    );
+    hmac_case!(
+        "hmac-sha512 rfc4231-2",
+        HmacSha2_512,
+        b"Jefe",
+        b"what do ya want for nothing?",
+        HMAC_C2_512
+    );
+    hmac_case!(
+        "hmac-sha512 rfc4231-3",
+        HmacSha2_512,
+        &HMAC_K3,
+        &HMAC_D3,
+        HMAC_C3_512
+    );
+    hmac_case!(
+        "hmac-sha512 rfc4231-4",
+        HmacSha2_512,
+        &HMAC_K4,
+        &HMAC_D4,
+        HMAC_C4_512
+    );
+    hmac_case!(
+        "hmac-sha512 rfc4231-6",
+        HmacSha2_512,
+        &HMAC_K6,
+        b"Test Using Larger Than Block-Size Key - Hash Key First",
+        HMAC_C6_512
+    );
     hmac_case!("hmac-sha512 rfc4231-7", HmacSha2_512, &HMAC_K7, b"This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.", HMAC_C7_512);
 
     // Streaming HMAC: split RFC-4231 case 7 data across multiple `update`s;