Implement BlockSizeUser for ShaContexts (#5050)

* Implement BlockSizeUser for ShaContexts

* Use max CPU speed

* Only measure the computation

* Clean up
This commit is contained in:
Dániel Buga
2026-02-24 15:48:32 +01:00
committed by GitHub
parent 5d6da52e4d
commit c581d2590b
4 changed files with 68 additions and 32 deletions

View File

@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- C5: Add PARL_IO support (#5042)
- `esp_hal::interrupt::RunLevel` (#4996)
- MAC addresses for radio interfaces getter: `esp_hal::efuse::Efuse::interface_mac_address(InterfaceMacAddress::)`. (#5002)
- `ShaXContext` objects now implement `digest::core_api::BlockSizeUser` (and thus they can be used with the `hmac` crate) (#5050)
### Changed

View File

@@ -74,7 +74,7 @@ procmacros = { version = "0.21.0", package = "esp-hal-procmacros",
# Dependencies that are optional because they are used by unstable drivers.
# They are needed when using the `unstable` feature.
digest = { version = "0.10.7", default-features = false, optional = true }
digest = { version = "0.10.7", default-features = false, features = ["core-api"], optional = true }
embassy-usb-driver = { version = "0.2", optional = true }
embassy-usb-synopsys-otg = { version = "0.3", optional = true }
embedded-can = { version = "0.4.1", optional = true }

View File

@@ -1340,7 +1340,7 @@ pub enum FinalizeError {
// Now implement the actual public types.
// Helper macro to limit the scope of `paste`
macro_rules! impl_worker_context {
($name:ident, $full_name:literal, $algo:expr, $digest_len:literal ) => {
($name:ident, $full_name:literal, $algo:expr, $digest_len:literal, $block_size:literal ) => {
#[doc = concat!("A ", $full_name, " context.")]
#[cfg_attr(not(esp32), derive(Clone))]
pub struct $name(ShaContext<{ $algo.chunk_length() }, { $algo.digest_length() / 4 }>);
@@ -1414,13 +1414,21 @@ macro_rules! impl_worker_context {
Self::finalize(&mut self, out.as_mut()).wait_blocking();
}
}
impl digest::core_api::BlockSizeUser for $name {
type BlockSize = paste::paste!(digest::consts::[< U $block_size >]);
fn block_size() -> usize {
$block_size
}
}
};
}
for_each_sha_algorithm! {
( $name:ident, $full_name:literal (sizes: $block_size:literal, $digest_len:literal, $message_length_bytes:literal) $security:tt, $mode_bits:literal ) => {
paste::paste! {
impl_worker_context!( [<$name Context>], $full_name, ShaAlgorithmKind::$name, $digest_len );
impl_worker_context!( [<$name Context>], $full_name, ShaAlgorithmKind::$name, $digest_len, $block_size );
}
};
}

View File

@@ -1,5 +1,8 @@
//! Demonstrates the use of the HMAC peripheral and compares the speed of
//! hardware-accelerated and pure software hashing.
//! Demonstrates the two possible ways to use hardware acceleration for HMAC operations and compares
//! the speed of hardware-accelerated and pure software hashing.
//!
//! - Use of the SHA peripheral with a software HMAC implementation
//! - Use of the HMAC peripheral (optional if required setup is skipped)
//!
//! # Writing key
//! Before using the HMAC accelerator in upstream mode, you first need to
@@ -56,12 +59,15 @@
use esp_backtrace as _;
use esp_hal::{
clock::CpuClock,
hmac::{Hmac, HmacPurpose, KeyId},
main,
rng::Rng,
sha::{Sha256Context, ShaBackend},
time::Instant,
};
use esp_println::println;
use hmac::{Hmac as HmacSw, Mac};
use esp_println::{print, println};
use hmac::{Hmac as HmacSw, Mac, SimpleHmac};
use nb::block;
use sha2::Sha256;
@@ -72,50 +78,71 @@ type HmacSha256 = HmacSw<Sha256>;
#[main]
fn main() -> ! {
esp_println::logger::init_logger_from_env();
let peripherals = esp_hal::init(esp_hal::Config::default());
let mut peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max()));
let rng = Rng::new();
let mut sha_backend = ShaBackend::new(peripherals.SHA.reborrow());
// Set sw key
let key = [0_u8; 32].as_ref();
let mut hw_hmac = Hmac::new(peripherals.HMAC);
let mut src = [0_u8; 1024];
rng.read(src.as_mut_slice());
Rng::new().read(src.as_mut_slice());
let mut output = [0u8; 32];
println!("Beginning stress tests...");
println!("Testing length from 0 to {:?} bytes for HMAC...", src.len());
for i in 0..src.len() + 1 {
print!("Testing for length: {:>4}", i);
let (nsrc, _) = src.split_at(i);
let mut remaining = nsrc;
hw_hmac.init();
block!(hw_hmac.configure(HmacPurpose::ToUser, KeyId::Key0)).expect("Key purpose mismatch");
let pre_hw_hmac = esp_hal::time::Instant::now();
while remaining.len() > 0 {
remaining = block!(hw_hmac.update(remaining)).unwrap();
}
block!(hw_hmac.finalize(output.as_mut_slice())).unwrap();
let hw_time = pre_hw_hmac.elapsed();
// Use a software algorithm to generate the expected value, and timing baseline
let mut sw_hmac = HmacSha256::new_from_slice(key).expect("HMAC can take key of any size");
let pre_sw_hash = esp_hal::time::Instant::now();
let pre_sw_hash = Instant::now();
sw_hmac.update(nsrc);
let soft_result = sw_hmac.finalize().into_bytes();
let soft_time = pre_sw_hash.elapsed();
for (a, b) in output.iter().zip(soft_result) {
assert_eq!(*a, b);
}
println!(
"Testing for length: {:>4} | HW: {:>6} cycles, SW: {:>7} cycles (HW HMAC is {:>2}x faster)",
i,
hw_time,
soft_time,
soft_time / hw_time
print!(" SW: {:>7}", soft_time);
// Use the hardware to see if we can get the same result, and compare the timing
let mut hw_hmac = Hmac::new(peripherals.HMAC.reborrow());
hw_hmac.init();
if block!(hw_hmac.configure(HmacPurpose::ToUser, KeyId::Key0)).is_ok() {
let pre_hw_hmac = Instant::now();
let mut remaining = nsrc;
while !remaining.is_empty() {
remaining = block!(hw_hmac.update(remaining)).unwrap();
}
block!(hw_hmac.finalize(output.as_mut_slice())).unwrap();
let hw_time = pre_hw_hmac.elapsed();
assert_eq!(output, soft_result.as_slice());
print!(", HW: {:>6} ({:>2}x faster)", hw_time, soft_time / hw_time);
} else {
print!(", HW HMAC skipped (Key not burned)");
};
core::mem::drop(hw_hmac);
// Now let's try a hybrid approach using the SHA peripheral with a software HMAC
// implementation
let _sha_backend = sha_backend.start();
let mut hyb_hmac = SimpleHmac::<Sha256Context>::new_from_slice(key)
.expect("HMAC can take key of any size");
let pre_hyb_hash = Instant::now();
hyb_hmac.update(nsrc);
let hybrid_result = hyb_hmac.finalize().into_bytes();
let hybrid_time = pre_hyb_hash.elapsed();
print!(
", SW+HW: {:>7} ({:>2}x faster)",
hybrid_time,
soft_time / hybrid_time
);
assert_eq!(hybrid_result, soft_result);
println!();
}
println!("Finished stress tests!");