Skip to main content

ferritls_core/
sha3.rs

1//! FIPS 202:Keccak 海绵族——SHA3-256/512 与 SHAKE-128/256(M8.3)。
2//!
3//! 纯软件标量实现(Keccak-f\[1600\],24 轮 θ/ρ/π/χ/ι),全部运算为
4//! 数据无关固定延迟,无侧信道敏感面(无以秘密为条件的分支或访存;
5//! 状态零化不适用——海绵状态承载公开数据)。SHA3 与 SHAKE 共用同一
6//! 海绵,仅域分隔填充字节不同(0x06 / 0x1F),由类型系统分开。
7//!
8//! ML-KEM(FIPS 203,[`crate::mlkem`])按标准使用四个映射:
9//! `H` = SHA3-256、`G` = SHA3-512、`J`/`PRF` = SHAKE-256、
10//! `XOF` = SHAKE-128;本模块同时作为 FIPS 202 的公开边界内 API。
11//!
12//! 向量:FIPS 202 官方示例 + FIPS 203 附录 A 示例值,
13//! 见 `tests/sha3.rs` 与 docs/VECTOR-PROVENANCE.md。
14
15/// Keccak-f\[1600\] 置换的 24 个轮常量。
16const RC: [u64; 24] = [
17    0x0000_0000_0000_0001,
18    0x0000_0000_0000_8082,
19    0x8000_0000_0000_808a,
20    0x8000_0000_8000_8000,
21    0x0000_0000_0000_808b,
22    0x0000_0000_8000_0001,
23    0x8000_0000_8000_8081,
24    0x8000_0000_0000_8009,
25    0x0000_0000_0000_008a,
26    0x0000_0000_0000_0088,
27    0x0000_0000_8000_8009,
28    0x0000_0000_8000_000a,
29    0x0000_0000_8000_808b,
30    0x8000_0000_0000_008b,
31    0x8000_0000_0000_8089,
32    0x8000_0000_0000_8003,
33    0x8000_0000_0000_8002,
34    0x8000_0000_0000_0080,
35    0x0000_0000_0000_800a,
36    0x8000_0000_8000_000a,
37    0x8000_0000_8000_8081,
38    0x8000_0000_0000_8080,
39    0x0000_0000_8000_0001,
40    0x8000_0000_8000_8008,
41];
42
43/// ρ 旋转偏移,按 `a[x + 5y]` 平铺(标准 5×5 矩阵的行主序平铺)。
44const RHO: [u32; 25] = [
45    0, 1, 62, 28, 27, //
46    36, 44, 6, 55, 20, //
47    3, 10, 43, 25, 39, //
48    41, 45, 15, 21, 8, //
49    18, 2, 61, 56, 14,
50];
51
52/// Keccak-f\[1600\]:对 25×64 位状态原地执行 24 轮置换。
53fn keccak_f1600(a: &mut [u64; 25]) {
54    for &rc in &RC {
55        // θ:列奇偶 → 与相邻列异或
56        let mut c = [0u64; 5];
57        for (x, cx) in c.iter_mut().enumerate() {
58            *cx = a[x] ^ a[x + 5] ^ a[x + 10] ^ a[x + 15] ^ a[x + 20];
59        }
60        for x in 0..5 {
61            let d = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
62            for a in a.iter_mut().skip(x).step_by(5) {
63                *a ^= d;
64            }
65        }
66        // ρ + π:旋转并按 (x, y) → (y, 2x+3y) 重排
67        let mut b = [0u64; 25];
68        for x in 0..5 {
69            for y in 0..5 {
70                b[y + 5 * ((2 * x + 3 * y) % 5)] = a[x + 5 * y].rotate_left(RHO[x + 5 * y]);
71            }
72        }
73        // χ:行内非线性组合
74        for y in 0..5 {
75            for x in 0..5 {
76                a[x + 5 * y] = b[x + 5 * y] ^ (!b[(x + 1) % 5 + 5 * y] & b[(x + 2) % 5 + 5 * y]);
77            }
78        }
79        // ι:轮常量注入
80        a[0] ^= rc;
81    }
82}
83
84/// Keccak 海绵核:`rate` 为字节率(SHA3-256/SHAKE128 = 136,
85/// SHA3-512 = 72,SHAKE256 = 64),全部 ≤ 200 = 25 lane × 8 B。
86#[derive(Clone, Debug)]
87struct Keccak {
88    state: [u64; 25],
89    /// 当前块内已吸收字节数(挤出阶段为已挤出字节数),恒 < rate。
90    pos: usize,
91    rate: usize,
92}
93
94impl Keccak {
95    fn new(rate: usize) -> Self {
96        debug_assert!(rate <= 200 && rate.is_multiple_of(8));
97        Keccak {
98            state: [0; 25],
99            pos: 0,
100            rate,
101        }
102    }
103
104    fn xor_into(state: &mut [u64; 25], start: usize, data: &[u8]) {
105        for (j, &b) in data.iter().enumerate() {
106            let i = start + j;
107            state[i / 8] ^= (b as u64) << (8 * (i % 8));
108        }
109    }
110
111    fn absorb(&mut self, mut data: &[u8]) {
112        if self.pos > 0 {
113            let take = core::cmp::min(self.rate - self.pos, data.len());
114            Self::xor_into(&mut self.state, self.pos, &data[..take]);
115            self.pos += take;
116            data = &data[take..];
117            if self.pos == self.rate {
118                keccak_f1600(&mut self.state);
119                self.pos = 0;
120            }
121        }
122        while data.len() >= self.rate {
123            Self::xor_into(&mut self.state, 0, &data[..self.rate]);
124            keccak_f1600(&mut self.state);
125            data = &data[self.rate..];
126        }
127        if !data.is_empty() {
128            Self::xor_into(&mut self.state, 0, data);
129            self.pos = data.len();
130        }
131    }
132
133    /// 追加域分隔填充(`dom` 的两高位恒 0,`0x80` 置于块末字节;
134    /// 两者落点重合时按位异或叠加)并执行一次置换,进入挤出阶段。
135    fn pad(&mut self, dom: u8) {
136        debug_assert!(self.pos < self.rate);
137        Self::xor_into(&mut self.state, self.pos, &[dom]);
138        Self::xor_into(&mut self.state, self.rate - 1, &[0x80]);
139        keccak_f1600(&mut self.state);
140        self.pos = 0;
141    }
142
143    fn squeeze(&mut self, out: &mut [u8]) {
144        let mut out = out;
145        while !out.is_empty() {
146            let take = core::cmp::min(self.rate - self.pos, out.len());
147            for (j, b) in out[..take].iter_mut().enumerate() {
148                let i = self.pos + j;
149                *b = (self.state[i / 8] >> (8 * (i % 8))) as u8;
150            }
151            self.pos += take;
152            out = &mut out[take..];
153            if self.pos == self.rate {
154                keccak_f1600(&mut self.state);
155                self.pos = 0;
156            }
157        }
158    }
159}
160
161macro_rules! xof_type {
162    ($name:ident, $xof:ident, $rate:expr, $dom:expr, $doc:expr) => {
163        #[doc = $doc]
164        #[derive(Debug, Clone)]
165        pub struct $name {
166            k: Keccak,
167        }
168
169        impl $name {
170            /// 新建吸收态实例。
171            pub fn new() -> Self {
172                $name {
173                    k: Keccak::new($rate),
174                }
175            }
176
177            /// 吸收一段输入(可多次调用,顺序敏感)。
178            pub fn update(&mut self, data: &[u8]) {
179                self.k.absorb(data);
180            }
181
182            /// 结束吸收(追加域分隔填充),返回可无限挤出的 XOF 读取端。
183            pub fn finalize_xof(self) -> $xof {
184                let mut k = self.k;
185                k.pad($dom);
186                $xof(k)
187            }
188        }
189
190        impl Default for $name {
191            fn default() -> Self {
192                Self::new()
193            }
194        }
195
196        #[doc = concat!("`", stringify!($name), "` 的挤出读取端(可重复调用 [`", stringify!($xof), "::fill`])。")]
197        #[derive(Debug, Clone)]
198        pub struct $xof(Keccak);
199
200        impl $xof {
201            /// 挤出任意长度输出;可连续调用,输出流与一次性大缓冲一致。
202            pub fn fill(&mut self, out: &mut [u8]) {
203                self.0.squeeze(out);
204            }
205        }
206    };
207}
208
209xof_type!(
210    Shake128,
211    Shake128Xof,
212    168,
213    0x1f,
214    "SHAKE-128 可扩展输出函数(FIPS 202):rate = 168 字节(c = 256)。"
215);
216xof_type!(
217    Shake256,
218    Shake256Xof,
219    136,
220    0x1f,
221    "SHAKE-256 可扩展输出函数(FIPS 202):rate = 136 字节(c = 512)。"
222);
223
224/// SHA3-256 一次性摘要(FIPS 202)。
225pub fn sha3_256(data: &[u8]) -> [u8; 32] {
226    let mut k = Keccak::new(136);
227    k.absorb(data);
228    k.pad(0x06);
229    let mut out = [0u8; 32];
230    k.squeeze(&mut out);
231    out
232}
233
234/// SHA3-512 一次性摘要(FIPS 202)。
235pub fn sha3_512(data: &[u8]) -> [u8; 64] {
236    let mut k = Keccak::new(72);
237    k.absorb(data);
238    k.pad(0x06);
239    let mut out = [0u8; 64];
240    k.squeeze(&mut out);
241    out
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    /// 分块吸收 + 分块挤出必须与一次性等价(海绵边界回归)。
249    #[test]
250    fn streaming_matches_one_shot() {
251        let data: Vec<u8> = (0..700u32).map(|i| i as u8).collect();
252
253        for split in [0usize, 1, 63, 64, 65, 135, 136, 137, 699, 700] {
254            let (a, b) = data.split_at(split);
255            let mut s = Shake256::new();
256            s.update(a);
257            s.update(b);
258            let mut x = s.finalize_xof();
259            let mut chunked = [0u8; 100];
260            x.fill(&mut chunked[..37]);
261            x.fill(&mut chunked[37..]);
262            let mut once = Shake256::new();
263            once.update(&data);
264            let mut direct = once.finalize_xof();
265            let mut whole = [0u8; 100];
266            direct.fill(&mut whole);
267            assert_eq!(chunked, whole, "Shake256 split at {split}");
268        }
269
270        for split in [0usize, 1, 71, 72, 73, 135, 136, 137, 699] {
271            let (a, b) = data.split_at(split);
272            let mut s = Shake128::new();
273            s.update(a);
274            s.update(b);
275            let mut x = s.finalize_xof();
276            let mut chunked = [0u8; 500];
277            x.fill(&mut chunked[..300]);
278            x.fill(&mut chunked[300..]);
279            let mut once = Shake128::new();
280            once.update(&data);
281            let mut direct = once.finalize_xof();
282            let mut whole = [0u8; 500];
283            direct.fill(&mut whole);
284            assert_eq!(chunked, whole, "Shake128 split at {split}");
285        }
286    }
287
288    /// FIPS 203 附录 A 的 ML-KEM 域分隔示例值(官方锚,
289    /// 另经 python hashlib 与 RustCrypto ml-kem 测试常量交叉核对)。
290    #[test]
291    fn fips203_appendix_a_anchors() {
292        let mut s = Shake128::new();
293        s.update(b"Input rho, to an XOF invocation!");
294        s.update(b"i");
295        s.update(b"j");
296        let mut x = s.finalize_xof();
297        let mut out = [0u8; 32];
298        x.fill(&mut out);
299        assert_eq!(
300            out.as_slice(),
301            hex32("0d2c3e65f754d074cb366cf1b099ae105cc40f018342509f15f1ba8a1a4144cb")
302        );
303
304        let mut s = Shake256::new();
305        s.update(b"Input s to an invocation of PRF2");
306        s.update(b"b");
307        let mut x = s.finalize_xof();
308        let mut prf = [0u8; 128];
309        x.fill(&mut prf);
310        assert_eq!(&prf[..16], &hex32("54c002415c2219b564d5c17b0df0c82f")[..]);
311
312        let mut s = Shake256::new();
313        s.update(b"Input to an invocation of J");
314        let mut x = s.finalize_xof();
315        let mut j = [0u8; 32];
316        x.fill(&mut j);
317        assert_eq!(
318            j.as_slice(),
319            hex32("a5292293d70c8eca049cbb475c48fabd625ed2b20785a18248504d3741196b52")
320        );
321    }
322
323    fn hex32(s: &str) -> Vec<u8> {
324        (0..s.len())
325            .step_by(2)
326            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("hex"))
327            .collect()
328    }
329}