Skip to main content

quantica/slh_dsa/
hash.rs

1//! SHAKE-based tweakable hash function wrappers for SLH-DSA (FIPS 205, Section 11.1).
2//!
3//! All hash functions in the SHAKE instantiation of SLH-DSA are built on top of SHAKE256.
4//! The address structure (`Adrs`) is included in each hash call to provide domain separation.
5//!
6//! The `_into` variants write output to a caller-provided buffer (zero heap allocation).
7//! The standard variants return `Vec<u8>` for convenience.
8
9use super::address::Adrs;
10use super::params::Params;
11use super::sha3::{KeccakState, SHAKE256_RATE};
12use alloc::vec::Vec;
13
14/// Internal: create a SHAKE256 context, absorb pk_seed and ADRS, return it.
15/// This prefix is common to PRF, T_l, H, and F.
16#[inline(always)]
17fn shake256_pk_adrs(pk_seed: &[u8], adrs: &Adrs) -> KeccakState {
18    let mut ctx = KeccakState::new(SHAKE256_RATE, 0x1f);
19    ctx.absorb(pk_seed);
20    ctx.absorb(adrs.as_bytes());
21    ctx
22}
23
24/// `H_msg(R, PK.seed, PK.root, M)` — message hash. Output written to `out` (m bytes).
25pub fn h_msg_into<P: Params>(r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[u8], out: &mut [u8]) {
26    let mut ctx = KeccakState::new(SHAKE256_RATE, 0x1f);
27    ctx.absorb(r);
28    ctx.absorb(pk_seed);
29    ctx.absorb(pk_root);
30    ctx.absorb(m);
31    ctx.squeeze(out);
32}
33
34/// `H_msg` — convenience wrapper returning Vec.
35pub fn h_msg<P: Params>(r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[u8]) -> Vec<u8> {
36    let mut out = vec![0u8; P::M];
37    h_msg_into::<P>(r, pk_seed, pk_root, m, &mut out);
38    out
39}
40
41/// `PRF(PK.seed, SK.seed, ADRS)` — secret value derivation. Output to `out` (n bytes).
42pub fn prf_into<P: Params>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs, out: &mut [u8]) {
43    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
44    ctx.absorb(sk_seed);
45    ctx.squeeze(out);
46}
47
48/// `PRF` — convenience wrapper returning Vec.
49pub fn prf<P: Params>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs) -> Vec<u8> {
50    let mut out = vec![0u8; P::N];
51    prf_into::<P>(pk_seed, sk_seed, adrs, &mut out);
52    out
53}
54
55/// `PRF_msg(SK.prf, opt_rand, M)` — message randomizer. Output to `out` (n bytes).
56pub fn prf_msg_into<P: Params>(sk_prf: &[u8], opt_rand: &[u8], m: &[u8], out: &mut [u8]) {
57    let mut ctx = KeccakState::new(SHAKE256_RATE, 0x1f);
58    ctx.absorb(sk_prf);
59    ctx.absorb(opt_rand);
60    ctx.absorb(m);
61    ctx.squeeze(out);
62}
63
64/// `PRF_msg` — convenience wrapper returning Vec.
65pub fn prf_msg<P: Params>(sk_prf: &[u8], opt_rand: &[u8], m: &[u8]) -> Vec<u8> {
66    let mut out = vec![0u8; P::N];
67    prf_msg_into::<P>(sk_prf, opt_rand, m, &mut out);
68    out
69}
70
71/// `T_l(PK.seed, ADRS, M)` — multi-input compression. Output to `out` (n bytes).
72pub fn t_l_into<P: Params>(pk_seed: &[u8], adrs: &Adrs, m: &[u8], out: &mut [u8]) {
73    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
74    ctx.absorb(m);
75    ctx.squeeze(out);
76}
77
78/// `T_l` — convenience wrapper returning Vec.
79pub fn t_l<P: Params>(pk_seed: &[u8], adrs: &Adrs, m: &[u8]) -> Vec<u8> {
80    let mut out = vec![0u8; P::N];
81    t_l_into::<P>(pk_seed, adrs, m, &mut out);
82    out
83}
84
85/// `H(PK.seed, ADRS, M1 || M2)` — two-input Merkle node hash. Output to `out` (n bytes).
86pub fn hash_h_into<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8], out: &mut [u8]) {
87    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
88    ctx.absorb(m1);
89    ctx.absorb(m2);
90    ctx.squeeze(out);
91}
92
93/// `H` — convenience wrapper returning Vec.
94pub fn hash_h<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8]) -> Vec<u8> {
95    let mut out = vec![0u8; P::N];
96    hash_h_into::<P>(pk_seed, adrs, m1, m2, &mut out);
97    out
98}
99
100/// `F(PK.seed, ADRS, M1)` — single-input chain hash. Output to `out` (n bytes).
101///
102/// This is the most frequently called hash function in SLH-DSA (WOTS+ chains).
103pub fn f_hash_into<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], out: &mut [u8]) {
104    let mut ctx = shake256_pk_adrs(pk_seed, adrs);
105    ctx.absorb(m1);
106    ctx.squeeze(out);
107}
108
109/// `F` — convenience wrapper returning Vec.
110pub fn f_hash<P: Params>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8]) -> Vec<u8> {
111    let mut out = vec![0u8; P::N];
112    f_hash_into::<P>(pk_seed, adrs, m1, &mut out);
113    out
114}