quantica/ml_kem/sha3.rs
1//! SHA-3 / SHAKE high-level wrappers used by ML-KEM (FIPS 203).
2//!
3//! This module no longer carries its own copy of the Keccak permutation:
4//! it builds on top of the shared sponge core in `crate::sha3`. The
5//! ML-KEM-specific surface is unchanged:
6//!
7//! | Function | Primitive | Usage in FIPS 203 |
8//! |-----------|-------------|---------------------------------------------------------|
9//! | `h` | SHA3-256 | H — hash encapsulation key, dk integrity |
10//! | `g` | SHA3-512 | G — derive shared key and encryption randomness |
11//! | `j` | SHAKE-256 | J — implicit rejection key derivation |
12//! | `prf` | SHAKE-256 | PRF — CBD sampling randomness |
13//! | `Xof` | SHAKE-128 | XOF — matrix sampling via `super::sample::sample_ntt` |
14
15use crate::sha3::{KeccakState, SHA3_256_RATE, SHA3_512_RATE, SHAKE128_RATE, SHAKE256_RATE};
16
17/// H(s) — SHA3-256 hash returning 32 bytes.
18///
19/// Used in FIPS 203 for hashing the encapsulation key (`H(ek)`) and
20/// for the dk integrity check.
21pub fn h(input: &[u8]) -> [u8; 32] {
22 let mut state = KeccakState::new(SHA3_256_RATE, 0x06);
23 state.absorb(input);
24 let mut out = [0u8; 32];
25 state.squeeze(&mut out);
26 out
27}
28
29/// G(c) — SHA3-512 hash returning two 32-byte values.
30///
31/// Computes `SHA3-512(c)` and splits the 64-byte output into two halves.
32/// Used in FIPS 203 to derive both the shared secret and the encryption
33/// randomness: `(K, r) = G(m || H(ek))`.
34pub fn g(input: &[u8]) -> ([u8; 32], [u8; 32]) {
35 let mut state = KeccakState::new(SHA3_512_RATE, 0x06);
36 state.absorb(input);
37 let mut out = [0u8; 64];
38 state.squeeze(&mut out);
39 let mut a = [0u8; 32];
40 let mut b = [0u8; 32];
41 a.copy_from_slice(&out[..32]);
42 b.copy_from_slice(&out[32..]);
43 (a, b)
44}
45
46/// J(s) — SHAKE-256 producing 32 bytes of output.
47///
48/// Used in FIPS 203 for implicit rejection: `K_bar = J(z || c)` provides
49/// the fallback shared secret when ciphertext re-encryption does not match.
50pub fn j(input: &[u8]) -> [u8; 32] {
51 let mut state = KeccakState::new(SHAKE256_RATE, 0x1f);
52 state.absorb(input);
53 let mut out = [0u8; 32];
54 state.squeeze(&mut out);
55 out
56}
57
58/// PRF_eta(s, b) — SHAKE-256 pseudo-random function producing `64 * eta` bytes.
59///
60/// Used in FIPS 203 to generate the random bytes consumed by
61/// [`super::sample::sample_poly_cbd`] for secret and error polynomial
62/// sampling.
63pub fn prf(eta: usize, s: &[u8; 32], b: u8, out: &mut [u8]) {
64 debug_assert!(out.len() >= 64 * eta);
65 let mut state = KeccakState::new(SHAKE256_RATE, 0x1f);
66 state.absorb(s);
67 state.absorb(&[b]);
68 let len = 64 * eta;
69 state.squeeze(&mut out[..len]);
70}
71
72/// Extendable Output Function (XOF) context wrapping SHAKE-128.
73///
74/// Provides an incremental absorb/squeeze interface for generating
75/// arbitrary-length pseudo-random output. Used by
76/// [`super::sample::sample_ntt`] to sample the public matrix A.
77pub struct Xof {
78 state: KeccakState,
79}
80
81impl Xof {
82 /// Create a new SHAKE-128 XOF context with an empty state.
83 pub fn new() -> Self {
84 Self {
85 state: KeccakState::new(SHAKE128_RATE, 0x1f),
86 }
87 }
88
89 /// Absorb input data into the XOF.
90 pub fn absorb(&mut self, data: &[u8]) {
91 self.state.absorb(data);
92 }
93
94 /// Squeeze output bytes from the XOF.
95 pub fn squeeze(&mut self, out: &mut [u8]) {
96 self.state.squeeze(out);
97 }
98}