Skip to main content

ferritls_core/
sha3.rs

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