From 1b57eaf1e196b9375a16329ea45ac06fdcabd957 Mon Sep 17 00:00:00 2001 From: Mike Ounsworth Date: Thu, 27 Aug 2026 16:38:30 -0500 Subject: [PATCH] sha3: add Memory Usage and Security Considerations docs, and a mem-usage bench QUALITY_AND_STYLE.md requires every primitive crate's docs to carry a "Memory Usage" section with a stack-usage table and, usually, a "Security Considerations" section; SHA-3 had neither. The Memory Usage table's figures come from a new mem_usage_benches/bench_sha3_mem_usage.rs, following the existing bench_mldsa_mem_usage / bench_mlkem_mem_usage: print_struct_sizes() reports size_of for SHA3_224..SHA3_512 and SHAKE128/256 (all 440 bytes, since every variant shares the same Keccak-f[1600] sponge) and SUSPENDED_SHA3_STATE_LEN (415). The remaining entry points -- one-shot hash, streaming, XOF squeeze, suspend/resume -- exist for valgrind --tool=massif peak-stack measurement, which criterion cannot do. Security Considerations cites FIPS 202 Appendix A.1 for the strengths and A.2 for the XOF prefix property (SHAKE128(m, 32) is a prefix of SHAKE128(m, 64)), and notes that the sponge state is held in Secret and zeroized on drop. Co-Authored-By: Claude Opus 5 (1M context) --- alpha_0.1.3_release_notes.md | 7 ++ crypto/sha3/src/lib.rs | 25 +++++ mem_usage_benches/Cargo.toml | 4 + mem_usage_benches/bench_sha3_mem_usage.rs | 122 ++++++++++++++++++++++ mem_usage_benches/lib.rs | 3 +- 5 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 mem_usage_benches/bench_sha3_mem_usage.rs diff --git a/alpha_0.1.3_release_notes.md b/alpha_0.1.3_release_notes.md index 210a5aeb..2b1f2475 100644 --- a/alpha_0.1.3_release_notes.md +++ b/alpha_0.1.3_release_notes.md @@ -3,3 +3,10 @@ ## Major features ## Minor features / bug fixes + +SHA-3 documentation: + +* Crate docs gained "Memory Usage" and "Security Considerations" sections. +* New `mem_usage_benches/bench_sha3_mem_usage.rs` reports `size_of` for the SHA-3 / SHAKE objects (440 bytes) and the + suspended state (415 bytes) -- the figures in the crate's Memory Usage table -- and provides valgrind massif entry + points for the hash, XOF and suspend/resume paths. diff --git a/crypto/sha3/src/lib.rs b/crypto/sha3/src/lib.rs index bc689b95..fc449e41 100644 --- a/crypto/sha3/src/lib.rs +++ b/crypto/sha3/src/lib.rs @@ -108,6 +108,31 @@ //! [`KeyType::CryptographicRandom`] since the input [`KeyMaterial`] is 16 bytes but [`SHA3_256`] needs at least 32 bytes of //! full-entropy input key material in order to be able to produce full entropy output key material. //! +//! # Memory Usage +//! +//! All SHA3 and SHAKE variants share the same Keccak-f\[1600\] sponge and so have identical memory +//! footprints. No heap memory is used by the algorithms themselves; the `Vec`-returning +//! convenience methods allocate only the output buffer, and the `*_out` variants allocate nothing. +//! +//! | Object | Size (bytes) | +//! |-----------------------------------------|--------------| +//! | `SHA3_224` .. `SHA3_512`, `SHAKE128/256` | 440 | +//! | Suspended state ([`Suspendable`]) | 415 | +//! +//! Sizes are `core::mem::size_of` values reported by `mem_usage_benches/bench_sha3_mem_usage.rs` +//! (`cargo run --release -p mem_usage_benches --bin bench_sha3_mem_usage`), which also has valgrind +//! massif entry points for measuring peak stack usage of the hash, XOF and suspend/resume paths. +//! +//! # Security Considerations +//! +//! * SHA3-224/256/384/512 offer 112/128/192/256 bits of collision resistance respectively; SHAKE128 +//! and SHAKE256 offer 128 and 256 bits of security for output lengths at least twice that size +//! (FIPS 202 Appendix A.1). +//! * SHAKE is an XOF, not a hash: `SHAKE128(m, 32)` is a prefix of `SHAKE128(m, 64)`. If the output +//! length must be bound to the digest, include it in the message (FIPS 202 Appendix A.2). +//! * The sponge state and queue are held in [`bouncycastle_utils::secret::Secret`] and zeroized on +//! drop. +//! //! # Suspending and resuming execution //! //! When hashing a large message, it can be advantageous to be able to suspend the operation diff --git a/mem_usage_benches/Cargo.toml b/mem_usage_benches/Cargo.toml index 53c35f9a..a3623aac 100644 --- a/mem_usage_benches/Cargo.toml +++ b/mem_usage_benches/Cargo.toml @@ -14,3 +14,7 @@ path = "bench_mldsa_mem_usage.rs" [[bin]] name = "bench_mlkem_mem_usage" path = "bench_mlkem_mem_usage.rs" + +[[bin]] +name = "bench_sha3_mem_usage" +path = "bench_sha3_mem_usage.rs" diff --git a/mem_usage_benches/bench_sha3_mem_usage.rs b/mem_usage_benches/bench_sha3_mem_usage.rs new file mode 100644 index 00000000..e4155b61 --- /dev/null +++ b/mem_usage_benches/bench_sha3_mem_usage.rs @@ -0,0 +1,122 @@ +//! The purpose of this binary is to perform a single run of the primitive under test so that +//! its peak memory usage can be measured with: +//! +//! valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_sha3_mem_usage > /dev/null +//! +//! ms_print massif.out.835000 +//! +//! or, shoved all into one line: +//! +//! clear; clear; valgrind --tool=massif --heap=no --stacks=yes -- target/release/bench_sha3_mem_usage > /dev/null; ms_print massif.out.*; rm massif.out.* +//! +//! Make sure you build in release mode! +//! +//! The code is using print!() to force the compiler not to optimize away the actual code. +//! It is printing important outputs for benchmarking to stderr so that the rest can be mapped to /dev/null +//! (this is because /usr/bin/time prints useful outputs to stderr as well) +//! +//! Main is at the bottom, controls which this was actually run. `print_struct_sizes()` is the source of +//! the numbers in the "Memory Usage" table in the bouncycastle-sha3 crate docs. + +#![allow(dead_code)] +#![allow(unused_imports)] + +use bouncycastle::core::traits::{Hash, Suspendable, XOF}; +use bouncycastle::sha3::{ + SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, SUSPENDED_SHA3_STATE_LEN, +}; + +/// A 1 KiB message so that the sponge is permuted several times. +const MSG: [u8; 1024] = [0xA5; 1024]; + +/// This prints the in-memory size of all the hash / XOF objects and the suspended state. +fn print_struct_sizes() { + use core::mem::size_of; + + println!("\nSHA3 / SHAKE"); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("size_of: {}", size_of::()); + println!("SUSPENDED_SHA3_STATE_LEN: {}", SUSPENDED_SHA3_STATE_LEN); +} + +fn bench_do_nothing() { + eprintln!("DoNothing"); + + print!("{}", 1 + 1); +} + +fn bench_sha3_256_hash() { + eprintln!("SHA3-256/hash"); + + let mut out = [0u8; 32]; + SHA3_256::new().hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_sha3_512_hash() { + eprintln!("SHA3-512/hash"); + + let mut out = [0u8; 64]; + SHA3_512::new().hash_out(&MSG, &mut out); + println!("{:x?}", out); +} + +fn bench_sha3_256_streaming() { + eprintln!("SHA3-256/do_update+do_final_out"); + + let mut h = SHA3_256::new(); + for chunk in MSG.chunks(100) { + h.do_update(chunk); + } + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn bench_shake128_xof() { + eprintln!("SHAKE128/absorb+squeeze_out"); + + let mut x = SHAKE128::new(); + x.absorb(&MSG).expect("absorb before squeeze is infallible"); + let mut out = [0u8; 512]; + x.squeeze_out(&mut out); + println!("{:x?}", out); +} + +fn bench_shake256_xof() { + eprintln!("SHAKE256/absorb+squeeze_out"); + + let mut x = SHAKE256::new(); + x.absorb(&MSG).expect("absorb before squeeze is infallible"); + let mut out = [0u8; 512]; + x.squeeze_out(&mut out); + println!("{:x?}", out); +} + +fn bench_sha3_256_suspend_resume() { + eprintln!("SHA3-256/suspend+from_suspended"); + + let mut h = SHA3_256::new(); + h.do_update(&MSG[..500]); + let state = h.suspend(); + let mut h = SHA3_256::from_suspended(state).expect("round-trip of a freshly suspended state"); + h.do_update(&MSG[500..]); + let mut out = [0u8; 32]; + h.do_final_out(&mut out); + println!("{:x?}", out); +} + +fn main() { + print_struct_sizes() + // bench_do_nothing() + // bench_sha3_256_hash() + // bench_sha3_512_hash() + // bench_sha3_256_streaming() + // bench_shake128_xof() + // bench_shake256_xof() + // bench_sha3_256_suspend_resume() +} diff --git a/mem_usage_benches/lib.rs b/mem_usage_benches/lib.rs index d296f2c1..a281a8b2 100644 --- a/mem_usage_benches/lib.rs +++ b/mem_usage_benches/lib.rs @@ -1,2 +1,3 @@ mod bench_mldsa_mem_usage; -mod bench_mlkem_mem_usage; \ No newline at end of file +mod bench_mlkem_mem_usage; +mod bench_sha3_mem_usage;