Date: February 14, 2026
Status: Approved
Reference: crypto-service-design-review.md
This plan implements the proposed architecture from the design review:
OneShot<A> trait for backend abstractionCryptoServer<B> that works with any backendStreaming<A> traitRustCryptoController?The existing platform/impls/rustcrypto/src/controller.rs (640 lines) implements the openprot-hal-blocking traits. We considered reusing it but chose not to for long-term maintainability:
// hal/blocking/src/digest.rs pub trait DigestInit<A> { type Context: DigestOp; fn init(self, algo: A) -> Result<Self::Context, Self::Error>; } // platform/impls/rustcrypto impl DigestInit<Sha2_256> for RustCryptoController { ... } impl DigestInit<Sha2_384> for RustCryptoController { ... } impl DigestInit<Sha2_512> for RustCryptoController { ... } impl DigestOp for DigestContext256 { ... } impl DigestOp for DigestContext384 { ... } impl DigestOp for DigestContext512 { ... } // 6 impls just for digest, 6 more for MAC = 12+ total
Problem: Adding SHA3-384 requires:
DigestInit trait (or add new marker type)DigestContext3_384 in RustCryptoController// crypto-traits/src/lib.rs pub trait OneShot<A: Algorithm> { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError>; } // backend-rustcrypto/src/lib.rs impl OneShot<Sha256> for RustCryptoBackend { ... } impl OneShot<Sha384> for RustCryptoBackend { ... } impl OneShot<Sha3_384> for RustCryptoBackend { ... } // ← Adding algorithm = adding impl // backend-hace/src/lib.rs (HACE only supports SHA-2) impl OneShot<Sha256> for HaceBackend { ... } impl OneShot<Sha384> for HaceBackend { ... } // No Sha3_384 impl needed if HACE doesn't support it
Benefit: Adding SHA3-384 requires:
pub struct Sha3_384; impl Algorithm for Sha3_384 { ... } to traitsimpl OneShot<Sha3_384> for RustCryptoBackend { ... } to backendThe SPDM spec requires 10-15 additional algorithms. Each algorithm addition:
| Approach | Files Changed | Risk |
|---|---|---|
| Current God trait | 3-4 per algorithm | High (touches shared traits) |
| Composable traits | 1-2 per algorithm | Low (additive only) |
Decision: Take the upfront refactor cost now for clean extensibility later. The existing RustCryptoController code will be referenced for implementation details but not directly reused.
During implementation review, we identified an important layering distinction:
┌─────────────────────────────────────────────────────────────────┐
│ Crypto Client │
│ (services/crypto/client) │
└───────────────────────────┬─────────────────────────────────────┘
│ IPC (channel_call)
┌───────────────────────────▼─────────────────────────────────────┐
│ Crypto Server │
│ (services/crypto/server) │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Service Layer Traits │ │
│ │ OneShot<A> — one-shot operations via &self │ │
│ │ Streaming<A> — session-based via session handle │ │
│ │ (services/crypto/api/src/backend.rs) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ │ impl │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ RustCryptoBackend│ │ HaceBackend │ ... (future) │
│ │ (software) │ │ (hardware accel) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
└───────────┼─────────────────────┼───────────────────────────────┘
│ │
│ (may use) │ (uses)
▼ ▼
┌───────────────────────────────────────────────────────────────────┐
│ HAL Layer Traits │
│ owned::DigestInit — typestate pattern (moves self) │
│ owned::DigestOp — resource recovery via fn cancel(self) │
│ (hal/blocking/src/digest.rs) │
└───────────────────────────────────────────────────────────────────┘
│
│ impl
▼
┌───────────────────────────────────────────────────────────────────┐
│ RustCryptoController │ HaceController │ ... (platform impls) │
│ (platform/impls/) │ │ │
└───────────────────────────────────────────────────────────────────┘
| Layer | Location | Purpose | Pattern |
|---|---|---|---|
| Service | services/crypto/api/src/backend.rs | Abstract IPC protocol | &self + session handle |
| HAL | hal/blocking/src/digest.rs | Abstract hardware controllers | Typestate (moves self) |
Service Layer: Designed for IPC semantics where state lives across multiple requests. The server owns a StreamingSession and clients reference it via session ID.
HAL Layer: Designed for baremetal use where resource recovery is critical. The typestate pattern ensures hardware controllers are properly released on success, failure, or cancellation.
HaceBackend would wrap a HaceController (HAL) to implement OneShot<A> (Service)The HAL traits in hal/blocking/src/digest.rs bundle two concerns:
// Current HAL: both concerns bundled pub mod owned { pub trait DigestOp { type Output; type Controller; type Error; fn update(self, data: &[u8]) -> Result<Self, Self::Error>; fn finalize(self) -> Result<(Self::Output, Self::Controller), Self::Error>; fn cancel(self) -> Self::Controller; // ← Resource recovery } }
For the crypto server:
Decouple into two orthogonal patterns:
// 1. Core operation trait (minimal, &mut self) pub trait DigestOp { type Output; type Error; fn update(&mut self, data: &[u8]) -> Result<(), Self::Error>; fn finalize(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>; } // 2. Optional wrapper for resource recovery (typestate) pub struct Owned<T, C> { op: T, controller: C, } impl<T: DigestOp, C> Owned<T, C> { pub fn update(self, data: &[u8]) -> Result<Self, (C, T::Error)> { ... } pub fn finalize(self) -> Result<(T::Output, C), (C, T::Error)> { ... } pub fn cancel(self) -> C { ... } // ← Resource recovery here }
| Concern | Baremetal | Server |
|---|---|---|
| Operation semantics | Uses core DigestOp | Uses core DigestOp |
| Resource recovery | Wraps in Owned<T, C> | Not needed (session table) |
This decoupling is not required for the current refactor—the Service layer traits are already designed without typestate. However, this insight informs future HAL evolution.
Pigweed userspace processes are single-threaded event loops. The crypto server:
This simplifies session management—no need for thread-safe session tables or locks. The single-threaded model also means &mut self on Streaming<A> methods is safe without additional synchronization.
crypto-traits CrateGoal: Define algorithm marker types and backend traits in a minimal, dependency-free crate.
services/crypto/traits/
├── Cargo.toml
├── BUILD.bazel
└── src/
└── lib.rs
Cargo.toml[package] name = "crypto-traits" version = "0.1.0" edition = "2021" license = "Apache-2.0" [dependencies] # Intentionally minimal — no_std, no dependencies
src/lib.rsContents:
#![no_std] //! Crypto traits for backend abstraction. //! //! This crate defines: //! - Algorithm marker types (Sha256, HmacSha256, etc.) //! - OneShot<A> trait for one-shot crypto operations //! - Streaming<A> trait for session-based streaming //! - CryptoInput enum for semantically typed inputs // --- Algorithm trait --- pub trait Algorithm { const OUTPUT_SIZE: usize; const OP_CODE: u8; } // --- Digest algorithms --- pub struct Sha256; impl Algorithm for Sha256 { const OUTPUT_SIZE: usize = 32; const OP_CODE: u8 = 0x01; } pub struct Sha384; impl Algorithm for Sha384 { const OUTPUT_SIZE: usize = 48; const OP_CODE: u8 = 0x02; } pub struct Sha512; impl Algorithm for Sha512 { const OUTPUT_SIZE: usize = 64; const OP_CODE: u8 = 0x03; } // --- MAC algorithms --- pub struct HmacSha256; impl Algorithm for HmacSha256 { const OUTPUT_SIZE: usize = 32; const OP_CODE: u8 = 0x10; } pub struct HmacSha384; impl Algorithm for HmacSha384 { const OUTPUT_SIZE: usize = 48; const OP_CODE: u8 = 0x11; } pub struct HmacSha512; impl Algorithm for HmacSha512 { const OUTPUT_SIZE: usize = 64; const OP_CODE: u8 = 0x12; } // --- AEAD algorithms --- pub struct Aes256GcmEncrypt; impl Algorithm for Aes256GcmEncrypt { const OUTPUT_SIZE: usize = 0; const OP_CODE: u8 = 0x20; } pub struct Aes256GcmDecrypt; impl Algorithm for Aes256GcmDecrypt { const OUTPUT_SIZE: usize = 0; const OP_CODE: u8 = 0x21; } // --- Signature algorithms (gated) --- #[cfg(feature = "ecdsa")] pub struct EcdsaP256Sign; #[cfg(feature = "ecdsa")] impl Algorithm for EcdsaP256Sign { const OUTPUT_SIZE: usize = 64; const OP_CODE: u8 = 0x40; } #[cfg(feature = "ecdsa")] pub struct EcdsaP256Verify; #[cfg(feature = "ecdsa")] impl Algorithm for EcdsaP256Verify { const OUTPUT_SIZE: usize = 1; const OP_CODE: u8 = 0x41; } #[cfg(feature = "ecdsa")] pub struct EcdsaP384Sign; #[cfg(feature = "ecdsa")] impl Algorithm for EcdsaP384Sign { const OUTPUT_SIZE: usize = 96; const OP_CODE: u8 = 0x42; } #[cfg(feature = "ecdsa")] pub struct EcdsaP384Verify; #[cfg(feature = "ecdsa")] impl Algorithm for EcdsaP384Verify { const OUTPUT_SIZE: usize = 1; const OP_CODE: u8 = 0x43; } // --- Structured input types --- pub enum CryptoInput<'a> { Digest { data: &'a [u8] }, Mac { key: &'a [u8], data: &'a [u8] }, Aead { key: &'a [u8], nonce: &'a [u8], data: &'a [u8] }, Sign { private_key: &'a [u8], message: &'a [u8] }, Verify { public_key: &'a [u8], message: &'a [u8], signature: &'a [u8] }, } // --- Error type --- #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CryptoError { InvalidOperation, InvalidKeyLength, InvalidNonceLength, InvalidDataLength, InvalidSignature, BufferTooSmall, AuthenticationFailed, HardwareFailure, } // --- OneShot trait --- pub trait OneShot<A: Algorithm> { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError>; } // --- Streaming trait --- pub trait Streaming<A: Algorithm> { type Session; fn begin(&mut self) -> Result<Self::Session, CryptoError>; fn update(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), CryptoError>; fn finalize(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, CryptoError>; fn cancel(&mut self, session: Self::Session); }
BUILD.bazelload("@rules_rust//rust:defs.bzl", "rust_library") rust_library( name = "crypto-traits", srcs = ["src/lib.rs"], crate_features = select({ "//target:ecdsa": ["ecdsa"], "//conditions:default": [], }), visibility = ["//visibility:public"], )
bazel build //services/crypto/traits:crypto-traits
backend-rustcrypto CrateGoal: Implement OneShot<A> for all algorithms using RustCrypto.
services/crypto/backend-rustcrypto/
├── Cargo.toml
├── BUILD.bazel
└── src/
└── lib.rs
Cargo.toml[package] name = "crypto-backend-rustcrypto" version = "0.1.0" edition = "2021" license = "Apache-2.0" [features] default = [] ecdsa = ["crypto-traits/ecdsa", "p256", "p384"] [dependencies] crypto-traits = { path = "../traits" } # RustCrypto sha2 = { version = "0.10", default-features = false } hmac = { version = "0.12", default-features = false } aes-gcm = { version = "0.10", default-features = false, features = ["aes"] } # ECDSA (optional) p256 = { version = "0.13", default-features = false, features = ["ecdsa"], optional = true } p384 = { version = "0.13", default-features = false, features = ["ecdsa"], optional = true }
src/lib.rs#![no_std] use crypto_traits::{ Algorithm, CryptoError, CryptoInput, OneShot, Sha256, Sha384, Sha512, HmacSha256, HmacSha384, HmacSha512, Aes256GcmEncrypt, Aes256GcmDecrypt, }; #[cfg(feature = "ecdsa")] use crypto_traits::{EcdsaP256Sign, EcdsaP256Verify, EcdsaP384Sign, EcdsaP384Verify}; use sha2::Digest as Sha2Digest; use hmac::{Hmac, Mac}; use aes_gcm::{Aes256Gcm, KeyInit, Nonce as GcmNonce, aead::AeadInPlace}; /// RustCrypto-based backend. Stateless — can be freely copied. #[derive(Clone, Copy, Default)] pub struct RustCryptoBackend; impl RustCryptoBackend { pub const fn new() -> Self { Self } } // --- Generic digest helper --- fn do_digest<D: Sha2Digest>(data: &[u8], output: &mut [u8]) -> Result<usize, CryptoError> { let mut hasher = D::new(); hasher.update(data); let result = hasher.finalize(); let size = result.len(); if output.len() < size { return Err(CryptoError::BufferTooSmall); } output[..size].copy_from_slice(&result); Ok(size) } impl OneShot<Sha256> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Digest { data } = input else { return Err(CryptoError::InvalidOperation) }; do_digest::<sha2::Sha256>(data, output) } } impl OneShot<Sha384> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Digest { data } = input else { return Err(CryptoError::InvalidOperation) }; do_digest::<sha2::Sha384>(data, output) } } impl OneShot<Sha512> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Digest { data } = input else { return Err(CryptoError::InvalidOperation) }; do_digest::<sha2::Sha512>(data, output) } } // --- Generic HMAC helper --- fn do_hmac<D>(key: &[u8], data: &[u8], output: &mut [u8]) -> Result<usize, CryptoError> where D: sha2::Digest + hmac::digest::core_api::BlockSizeUser + Clone, Hmac<D>: Mac, { let mut mac = <Hmac<D> as Mac>::new_from_slice(key) .map_err(|_| CryptoError::InvalidKeyLength)?; mac.update(data); let result = mac.finalize().into_bytes(); let size = result.len(); if output.len() < size { return Err(CryptoError::BufferTooSmall); } output[..size].copy_from_slice(&result); Ok(size) } impl OneShot<HmacSha256> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Mac { key, data } = input else { return Err(CryptoError::InvalidOperation) }; do_hmac::<sha2::Sha256>(key, data, output) } } impl OneShot<HmacSha384> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Mac { key, data } = input else { return Err(CryptoError::InvalidOperation) }; do_hmac::<sha2::Sha384>(key, data, output) } } impl OneShot<HmacSha512> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Mac { key, data } = input else { return Err(CryptoError::InvalidOperation) }; do_hmac::<sha2::Sha512>(key, data, output) } } // --- AES-GCM --- const AES_KEY_SIZE: usize = 32; const GCM_NONCE_SIZE: usize = 12; const GCM_TAG_SIZE: usize = 16; impl OneShot<Aes256GcmEncrypt> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Aead { key, nonce, data } = input else { return Err(CryptoError::InvalidOperation) }; if key.len() != AES_KEY_SIZE { return Err(CryptoError::InvalidKeyLength); } if nonce.len() != GCM_NONCE_SIZE { return Err(CryptoError::InvalidNonceLength); } let output_len = data.len() + GCM_TAG_SIZE; if output.len() < output_len { return Err(CryptoError::BufferTooSmall); } let key_array: [u8; 32] = key.try_into().map_err(|_| CryptoError::InvalidKeyLength)?; let cipher = Aes256Gcm::new(&key_array.into()); let gcm_nonce = GcmNonce::from_slice(nonce); output[..data.len()].copy_from_slice(data); let tag = cipher.encrypt_in_place_detached(gcm_nonce, &[], &mut output[..data.len()]) .map_err(|_| CryptoError::HardwareFailure)?; output[data.len()..output_len].copy_from_slice(&tag); Ok(output_len) } } impl OneShot<Aes256GcmDecrypt> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Aead { key, nonce, data } = input else { return Err(CryptoError::InvalidOperation) }; if key.len() != AES_KEY_SIZE { return Err(CryptoError::InvalidKeyLength); } if nonce.len() != GCM_NONCE_SIZE { return Err(CryptoError::InvalidNonceLength); } if data.len() < GCM_TAG_SIZE { return Err(CryptoError::InvalidDataLength); } let ciphertext_len = data.len() - GCM_TAG_SIZE; if output.len() < ciphertext_len { return Err(CryptoError::BufferTooSmall); } let key_array: [u8; 32] = key.try_into().map_err(|_| CryptoError::InvalidKeyLength)?; let cipher = Aes256Gcm::new(&key_array.into()); let gcm_nonce = GcmNonce::from_slice(nonce); let ciphertext = &data[..ciphertext_len]; let tag = &data[ciphertext_len..]; output[..ciphertext_len].copy_from_slice(ciphertext); cipher.decrypt_in_place_detached( gcm_nonce, &[], &mut output[..ciphertext_len], tag.into(), ).map_err(|_| CryptoError::AuthenticationFailed)?; Ok(ciphertext_len) } } // --- ECDSA (feature-gated) --- #[cfg(feature = "ecdsa")] mod ecdsa_impl { use super::*; use p256::ecdsa::{SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey, Signature as P256Signature, signature::{Signer, Verifier}}; use p384::ecdsa::{SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey, Signature as P384Signature}; impl OneShot<EcdsaP256Sign> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Sign { private_key, message } = input else { return Err(CryptoError::InvalidOperation); }; if private_key.len() != 32 { return Err(CryptoError::InvalidKeyLength); } if output.len() < 64 { return Err(CryptoError::BufferTooSmall); } let key = P256SigningKey::from_slice(private_key) .map_err(|_| CryptoError::InvalidKeyLength)?; let sig: P256Signature = key.sign(message); output[..64].copy_from_slice(&sig.to_bytes()); Ok(64) } } impl OneShot<EcdsaP256Verify> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Verify { public_key, message, signature } = input else { return Err(CryptoError::InvalidOperation); }; if signature.len() != 64 { return Err(CryptoError::InvalidSignature); } if output.is_empty() { return Err(CryptoError::BufferTooSmall); } let key = P256VerifyingKey::from_sec1_bytes(public_key) .map_err(|_| CryptoError::InvalidKeyLength)?; let sig = P256Signature::from_slice(signature) .map_err(|_| CryptoError::InvalidSignature)?; match key.verify(message, &sig) { Ok(()) => { output[0] = 1; // verified Ok(1) } Err(_) => { output[0] = 0; // failed Ok(1) } } } } impl OneShot<EcdsaP384Sign> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Sign { private_key, message } = input else { return Err(CryptoError::InvalidOperation); }; if private_key.len() != 48 { return Err(CryptoError::InvalidKeyLength); } if output.len() < 96 { return Err(CryptoError::BufferTooSmall); } let key = P384SigningKey::from_slice(private_key) .map_err(|_| CryptoError::InvalidKeyLength)?; let sig: P384Signature = Signer::sign(&key, message); output[..96].copy_from_slice(&sig.to_bytes()); Ok(96) } } impl OneShot<EcdsaP384Verify> for RustCryptoBackend { fn compute(&self, input: &CryptoInput, output: &mut [u8]) -> Result<usize, CryptoError> { let CryptoInput::Verify { public_key, message, signature } = input else { return Err(CryptoError::InvalidOperation); }; if signature.len() != 96 { return Err(CryptoError::InvalidSignature); } if output.is_empty() { return Err(CryptoError::BufferTooSmall); } let key = P384VerifyingKey::from_sec1_bytes(public_key) .map_err(|_| CryptoError::InvalidKeyLength)?; let sig = P384Signature::from_slice(signature) .map_err(|_| CryptoError::InvalidSignature)?; match Verifier::verify(&key, message, &sig) { Ok(()) => { output[0] = 1; Ok(1) } Err(_) => { output[0] = 0; Ok(1) } } } } }
bazel build //services/crypto/backend-rustcrypto:crypto-backend-rustcrypto
CryptoServer<B>Goal: Replace the 496-line server with a ~150-line generic implementation.
services/crypto/server/Cargo.tomlReplace direct RustCrypto dependencies with trait crate:
[dependencies] crypto-api = { path = "../api" } crypto-traits = { path = "../traits" } crypto-backend-rustcrypto = { path = "../backend-rustcrypto" } zerocopy = { version = "0.8", features = ["derive"] } # Remove: sha2, hmac, aes-gcm, p256, p384
services/crypto/server/src/main.rsNew structure:
#![no_main] #![no_std] use crypto_api::{CryptoError as WireError, CryptoOp, CryptoRequestHeader, CryptoResponseHeader}; use crypto_traits::{ Algorithm, CryptoError, CryptoInput, OneShot, Streaming, Sha256, Sha384, Sha512, HmacSha256, HmacSha384, HmacSha512, Aes256GcmEncrypt, Aes256GcmDecrypt, }; #[cfg(feature = "ecdsa")] use crypto_traits::{EcdsaP256Sign, EcdsaP256Verify, EcdsaP384Sign, EcdsaP384Verify}; use crypto_backend_rustcrypto::RustCryptoBackend; use pw_status::Result; use userspace::entry; use userspace::syscall::{self, Signals}; use userspace::time::Instant; use app_crypto_server::handle; const MAX_REQUEST_SIZE: usize = 1024; const MAX_RESPONSE_SIZE: usize = 1024; // Type alias for backend — change this one line to swap backends type Backend = RustCryptoBackend; /// Generic crypto server. struct CryptoServer<B> { backend: B, // Streaming session state (single session at a time) session: StreamingSession, } enum StreamingSession { None, Sha256(Sha256State), Sha384(Sha384State), Sha512(Sha512State), } // Session state types (backend-specific, but hidden behind Streaming<A> trait) struct Sha256State { /* backend session handle */ } struct Sha384State { /* backend session handle */ } struct Sha512State { /* backend session handle */ } impl<B> CryptoServer<B> where B: OneShot<Sha256> + OneShot<Sha384> + OneShot<Sha512> + OneShot<HmacSha256> + OneShot<HmacSha384> + OneShot<HmacSha512> + OneShot<Aes256GcmEncrypt> + OneShot<Aes256GcmDecrypt> #[cfg(feature = "ecdsa")] + OneShot<EcdsaP256Sign> + OneShot<EcdsaP256Verify> + OneShot<EcdsaP384Sign> + OneShot<EcdsaP384Verify> { fn new(backend: B) -> Self { Self { backend, session: StreamingSession::None, } } fn dispatch(&self, request: &[u8], response: &mut [u8]) -> usize { // Parse header if request.len() < CryptoRequestHeader::SIZE { return encode_error(response, WireError::InvalidDataLength); } let header_bytes = &request[..CryptoRequestHeader::SIZE]; let Some(header) = zerocopy::Ref::<_, CryptoRequestHeader>::from_bytes(header_bytes).ok() else { return encode_error(response, WireError::InvalidDataLength); }; let header: &CryptoRequestHeader = &*header; let op = match header.operation() { Ok(op) => op, Err(e) => return encode_error(response, e), }; // Extract fields let payload = &request[CryptoRequestHeader::SIZE..]; let key_len = header.key_length(); let nonce_len = header.nonce_length(); let data_len = header.data_length(); if payload.len() < key_len + nonce_len + data_len { return encode_error(response, WireError::InvalidDataLength); } let key = &payload[..key_len]; let nonce = &payload[key_len..key_len + nonce_len]; let data = &payload[key_len + nonce_len..key_len + nonce_len + data_len]; // Build semantic input let input = Self::build_input(op, key, nonce, data); // Dispatch by op code match op { CryptoOp::Sha256Hash => self.run::<Sha256>(&input, response), CryptoOp::Sha384Hash => self.run::<Sha384>(&input, response), CryptoOp::Sha512Hash => self.run::<Sha512>(&input, response), CryptoOp::HmacSha256 => self.run::<HmacSha256>(&input, response), CryptoOp::HmacSha384 => self.run::<HmacSha384>(&input, response), CryptoOp::HmacSha512 => self.run::<HmacSha512>(&input, response), CryptoOp::Aes256GcmEncrypt => self.run::<Aes256GcmEncrypt>(&input, response), CryptoOp::Aes256GcmDecrypt => self.run::<Aes256GcmDecrypt>(&input, response), #[cfg(feature = "ecdsa")] CryptoOp::EcdsaP256Sign => self.run::<EcdsaP256Sign>(&input, response), #[cfg(feature = "ecdsa")] CryptoOp::EcdsaP256Verify => self.run::<EcdsaP256Verify>(&input, response), #[cfg(feature = "ecdsa")] CryptoOp::EcdsaP384Sign => self.run::<EcdsaP384Sign>(&input, response), #[cfg(feature = "ecdsa")] CryptoOp::EcdsaP384Verify => self.run::<EcdsaP384Verify>(&input, response), // Streaming ops (Phase 4) CryptoOp::Sha256Begin | CryptoOp::Sha256Update | CryptoOp::Sha256Finish | CryptoOp::Sha384Begin | CryptoOp::Sha384Update | CryptoOp::Sha384Finish | CryptoOp::Sha512Begin | CryptoOp::Sha512Update | CryptoOp::Sha512Finish => { encode_error(response, WireError::InvalidOperation) // TODO Phase 4 } #[cfg(not(feature = "ecdsa"))] CryptoOp::EcdsaP256Sign | CryptoOp::EcdsaP256Verify | CryptoOp::EcdsaP384Sign | CryptoOp::EcdsaP384Verify => { encode_error(response, WireError::InvalidOperation) } } } fn build_input<'a>(op: CryptoOp, key: &'a [u8], nonce: &'a [u8], data: &'a [u8]) -> CryptoInput<'a> { match op { CryptoOp::Sha256Hash | CryptoOp::Sha384Hash | CryptoOp::Sha512Hash => CryptoInput::Digest { data }, CryptoOp::HmacSha256 | CryptoOp::HmacSha384 | CryptoOp::HmacSha512 => CryptoInput::Mac { key, data }, CryptoOp::Aes256GcmEncrypt | CryptoOp::Aes256GcmDecrypt => CryptoInput::Aead { key, nonce, data }, CryptoOp::EcdsaP256Sign | CryptoOp::EcdsaP384Sign => CryptoInput::Sign { private_key: key, message: data }, CryptoOp::EcdsaP256Verify | CryptoOp::EcdsaP384Verify => CryptoInput::Verify { public_key: key, message: data, signature: nonce }, _ => CryptoInput::Digest { data: &[] }, // streaming handled separately } } fn run<A: Algorithm>(&self, input: &CryptoInput, response: &mut [u8]) -> usize where B: OneShot<A>, { let result_start = CryptoResponseHeader::SIZE; match self.backend.compute(input, &mut response[result_start..]) { Ok(len) => encode_success(response, len), Err(e) => encode_error(response, map_error(e)), } } } fn encode_error(response: &mut [u8], err: WireError) -> usize { let header = CryptoResponseHeader::error(err); let header_bytes = zerocopy::IntoBytes::as_bytes(&header); response[..CryptoResponseHeader::SIZE].copy_from_slice(header_bytes); CryptoResponseHeader::SIZE } fn encode_success(response: &mut [u8], result_len: usize) -> usize { let header = CryptoResponseHeader::success(result_len as u16); let header_bytes = zerocopy::IntoBytes::as_bytes(&header); response[..CryptoResponseHeader::SIZE].copy_from_slice(header_bytes); CryptoResponseHeader::SIZE + result_len } fn map_error(e: CryptoError) -> WireError { match e { CryptoError::InvalidOperation => WireError::InvalidOperation, CryptoError::InvalidKeyLength => WireError::InvalidKeyLength, CryptoError::InvalidNonceLength => WireError::InvalidNonceLength, CryptoError::InvalidDataLength => WireError::InvalidDataLength, CryptoError::InvalidSignature => WireError::InvalidSignature, CryptoError::BufferTooSmall => WireError::BufferTooSmall, CryptoError::AuthenticationFailed => WireError::AuthenticationFailed, CryptoError::HardwareFailure => WireError::HardwareFailure, } } #[entry] fn main() -> ! { match crypto_server_loop() { Ok(()) => unreachable!(), Err(e) => { pw_log::error!("Crypto server error: {:?}", e); loop { cortex_m::asm::wfi(); } } } } fn crypto_server_loop() -> Result<()> { pw_log::info!("Crypto server starting"); let server = CryptoServer::new(RustCryptoBackend::new()); let mut request_buf = [0u8; MAX_REQUEST_SIZE]; let mut response_buf = [0u8; MAX_RESPONSE_SIZE]; loop { syscall::object_wait(handle::CRYPTO, Signals::READABLE, Instant::MAX)?; let len = syscall::channel_read(handle::CRYPTO, 0, &mut request_buf)?; let response_len = server.dispatch(&request_buf[..len], &mut response_buf); syscall::channel_respond(handle::CRYPTO, &response_buf[..response_len])?; } }
bazel test --config=virt_ast1060_evb //target/ast1060/crypto:crypto_test --test_output=all
All existing tests must pass unchanged.
Streaming<A> TraitGoal: Implement streaming hash using the trait-based design.
crypto-traits/src/lib.rsAlready defined in Phase 1. Add session state types.
backend-rustcrypto/src/lib.rsuse sha2::{Sha256 as Sha2_256, Sha384 as Sha2_384, Sha512 as Sha2_512}; pub struct Sha256Session(Sha2_256); pub struct Sha384Session(Sha2_384); pub struct Sha512Session(Sha2_512); impl Streaming<Sha256> for RustCryptoBackend { type Session = Sha256Session; fn begin(&mut self) -> Result<Self::Session, CryptoError> { Ok(Sha256Session(Sha2_256::new())) } fn update(&mut self, session: &mut Self::Session, data: &[u8]) -> Result<(), CryptoError> { session.0.update(data); Ok(()) } fn finalize(&mut self, session: Self::Session, output: &mut [u8]) -> Result<usize, CryptoError> { if output.len() < 32 { return Err(CryptoError::BufferTooSmall); } let result = session.0.finalize(); output[..32].copy_from_slice(&result); Ok(32) } fn cancel(&mut self, _session: Self::Session) { // Session dropped, nothing to clean up for software impl } } // Similar for Sha384, Sha512
Add streaming op handling in CryptoServer::dispatch().
bazel test --config=virt_ast1060_evb //target/ast1060/crypto:crypto_test --test_output=all
Streaming test (test_sha256_streaming) must pass.
Goal: Add hardware-accelerated backend for AST1060.
backend-hace crateservices/crypto/backend-hace/
├── Cargo.toml
├── BUILD.bazel
└── src/
└── lib.rs
OneShot<A> and Streaming<A>Wrap the HaceController from aspeed-rust to implement the traits.
#[cfg(feature = "hace")] type Backend = HaceBackend; #[cfg(not(feature = "hace"))] type Backend = RustCryptoBackend;
| Phase | Task | New Files | Modified | LOC | Effort |
|---|---|---|---|---|---|
| 1 | Create crypto-traits crate | 3 | 0 | ~150 | 0.5 day |
| 2 | Create backend-rustcrypto crate | 3 | 0 | ~300 | 1 day |
| 3 | Rewrite server with CryptoServer<B> | 0 | 2 | ~150 (was 496) | 1 day |
| 4 | Add streaming via Streaming<A> | 0 | 3 | ~100 | 0.5 day |
| 5 | HACE backend (future) | 3 | 1 | ~300 | 3 days |
| Total | 9 | 6 | ~1000 | ~6 days |
| Component | Action |
|---|---|
hal/blocking/src/digest.rs | Keep — still used by non-crypto-server code |
hal/blocking/src/mac.rs | Keep — still used by non-crypto-server code |
platform/impls/rustcrypto/src/controller.rs | Reference only — mine for implementation patterns |
services/crypto/server/src/main.rs | Rewrite — becomes generic CryptoServer<B> |
services/crypto/api/ | Keep — wire protocol unchanged |
services/crypto/client/ | Keep — client API unchanged |
services/crypto/tests/ | Keep — tests pass without modification |
services/crypto/
├── traits/ # NEW: Algorithm markers + OneShot/Streaming traits
│ └── src/lib.rs # ~150 lines, no dependencies
├── backend-rustcrypto/ # NEW: OneShot<A> impls using RustCrypto
│ └── src/lib.rs # ~300 lines
├── backend-hace/ # FUTURE: OneShot<A> impls using HACE
│ └── src/lib.rs # ~300 lines
├── api/ # UNCHANGED: Wire protocol
│ └── src/protocol.rs
├── server/ # REWRITTEN: Generic CryptoServer<B>
│ └── src/main.rs # ~150 lines (was 496)
├── client/ # UNCHANGED: Client library
│ └── src/lib.rs
└── tests/ # UNCHANGED: Integration tests
└── src/main.rs
After each phase:
bazel build //services/crypto/...bazel build //services/crypto/... --aspects=@aspect_rules_lint//lint:lint.bzl%clippy_lintsbazel test --config=virt_ast1060_evb //target/ast1060/crypto:crypto_test --test_output=allEach phase is independently deployable:
main.rs.bak until tests passDate: February 13, 2026
Decision: Implement new composable OneShot<A> / Streaming<A> traits rather than reusing existing openprot-hal-blocking + RustCryptoController.
Rationale:
Tradeoff: ~6 days upfront work vs. ongoing maintenance cost for 2+ years of algorithm additions.
Accepted by: User (February 13, 2026)
Updated: February 14, 2026 — Added architectural insights on trait layering and resource recovery decoupling
Ready to execute.