fips205 0.5.0

FIPS 205: Stateless Hash-Based Digital Signature Standard
Documentation
use crate::types::Adrs;
use zeroize::Zeroize;


/// Overwrites the stack area that a hash call just used. sha2 0.10 cannot zeroize its state and
/// sha3 0.10 permutes on stack copies, so their frames can still hold secret-derived bytes
/// (FIPS 205 §3.1). It is called from the same frame as the hash call, so this frame overlays the
/// hash's.
#[inline(never)]
pub(crate) fn scrub_hash_stack() {
    // 2 KiB, wiped a word at a time, as in `fips203` and `fips204`
    let mut area = [0u64; 256];
    area.zeroize();
    let _ = core::hint::black_box(&area);
}


// Holds hasher function references; constructed by each security parameter set wrapper
#[allow(clippy::type_complexity)]
pub(crate) struct Hashers<const K: usize, const LEN: usize, const M: usize, const N: usize> {
    pub(crate) h_msg: fn(&[u8], &[u8], &[u8], &[&[u8]]) -> [u8; M],
    pub(crate) prf: fn(&[u8], &[u8], &Adrs) -> [u8; N],
    pub(crate) prf_msg: fn(&[u8], &[u8], &[&[u8]]) -> [u8; N],
    pub(crate) f: fn(&[u8], &Adrs, &[u8]) -> [u8; N],
    pub(crate) h: fn(&[u8], &Adrs, &[u8], &[u8]) -> [u8; N],
    pub(crate) t_l: fn(&[u8], &Adrs, &[[u8; N]; LEN]) -> [u8; N],
    pub(crate) t_len: fn(&[u8], &Adrs, &[[u8; N]; K]) -> [u8; N],
}


#[cfg(any(
    feature = "slh-dsa-shake-128f",
    feature = "slh-dsa-shake-128s",
    feature = "slh-dsa-shake-192f",
    feature = "slh-dsa-shake-192s",
    feature = "slh-dsa-shake-256f",
    feature = "slh-dsa-shake-256s"
))]
pub(crate) mod shake {
    use crate::types::Adrs;
    use sha3::digest::{ExtendableOutput, Update, XofReader};
    use sha3::Shake256;


    fn shake256(input: &[&[u8]], out: &mut [u8]) {
        let mut hasher = Shake256::default();
        for item in input {
            hasher.update(item);
        }
        let mut reader = hasher.finalize_xof();
        reader.read(out);
    }


    pub(crate) fn h_msg<const M: usize>(
        r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[&[u8]],
    ) -> [u8; M] {
        let mut digest = [0u8; M];
        let mut inp = [r, pk_seed, pk_root, &[], &[], &[], &[], &[]];
        inp[3..3 + m.len()].copy_from_slice(m); // m can have up to 5 elements
        shake256(&inp, &mut digest);
        digest
    }


    #[allow(clippy::similar_names)] // pk_seed and sk_seed
    pub(crate) fn prf<const N: usize>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs) -> [u8; N] {
        let mut digest = [0u8; N];
        shake256(&[pk_seed, &adrs.to_32_bytes(), sk_seed], &mut digest); // Spec swaps order of last two params 557/997/1005
        digest
    }


    pub(crate) fn prf_msg<const N: usize>(sk_prf: &[u8], opt_rand: &[u8], m: &[&[u8]]) -> [u8; N] {
        let mut digest = [0u8; N];
        let mut inp = [sk_prf, opt_rand, &[], &[], &[], &[], &[]];
        inp[2..2 + m.len()].copy_from_slice(m); // m can have up to 5 elements
        shake256(&inp, &mut digest);
        digest
    }


    pub(crate) fn f<const N: usize>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8]) -> [u8; N] {
        let mut digest = [0u8; N];
        shake256(&[pk_seed, &adrs.to_32_bytes(), m1], &mut digest);
        digest
    }


    pub(crate) fn h<const N: usize>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8]) -> [u8; N] {
        let mut digest = [0u8; N];
        shake256(&[pk_seed, &adrs.to_32_bytes(), m1, m2], &mut digest);
        digest
    }


    // Perhaps there is a more elegant way to convert ml into list of bytes
    pub(crate) fn t_l<const X: usize, const Y: usize>(
        pk_seed: &[u8], adrs: &Adrs, ml: &[[u8; Y]; X],
    ) -> [u8; Y] {
        let mut hasher = Shake256::default();
        hasher.update(pk_seed);
        hasher.update(&adrs.to_32_bytes());
        for item in ml {
            hasher.update(item);
        }
        let mut reader = hasher.finalize_xof();
        let mut result = [0u8; Y];
        reader.read(&mut result);
        result
    }
}


#[cfg(any(feature = "slh-dsa-sha2-128f", feature = "slh-dsa-sha2-128s"))]
pub(crate) mod sha2_cat_1 {
    use crate::types::Adrs;
    use core::cmp::min;
    use sha2::{Digest, Sha256};
    use zeroize::Zeroize;


    fn sha2_256(input: &[&[u8]], out: &mut [u8]) {
        let mut hasher = Sha256::new();
        for item in input {
            hasher.update(item);
        }
        let result = hasher.finalize();
        out.copy_from_slice(&result[0..out.len()]);
    }


    pub(crate) fn h_msg<const M: usize>(
        r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[&[u8]],
    ) -> [u8; M] {
        let mut digest1 = [0u8; 32];
        let mut inp = [r, pk_seed, pk_root, &[], &[], &[], &[], &[]];
        inp[3..3 + m.len()].copy_from_slice(m); // m can have up to 5 elements
        sha2_256(&inp, &mut digest1);
        let mut result = [0u8; M];
        let mut start = 0;
        let mut counter = 0u32;
        while start < M {
            let mut tmp = [0u8; 32];
            sha2_256(&[r, pk_seed, &digest1, &counter.to_be_bytes()], &mut tmp);
            let len = min(M - start, 32);
            result[start..start + len].copy_from_slice(&tmp[0..len]);
            start += 32;
            counter += 1;
        }
        result
    }


    #[allow(clippy::similar_names)] // pk_seed and sk_seed
    pub(crate) fn prf<const N: usize>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs) -> [u8; N] {
        let mut digest = [0u8; N];
        let zeros = [0u8; 48];
        sha2_256(&[pk_seed, &zeros[0..(64 - N)], &adrs.to_22_bytes(), sk_seed], &mut digest); // Spec swaps order of last two params 557/997/1005
        digest
    }


    fn hmac_sha_256(key: &[u8], a0: &[u8], m: &[&[u8]]) -> [u8; 32] {
        let mut padding = [0x36; 64];
        for (p, &k) in padding.iter_mut().zip(key.iter()) {
            *p ^= k;
        }
        let mut inner_hasher = Sha256::new();
        inner_hasher.update(&padding[..]);
        inner_hasher.update(a0);
        for item in m {
            inner_hasher.update(item);
        }
        for p in &mut padding {
            *p ^= 0x6a;
        }
        let mut outer_hasher = Sha256::new();
        outer_hasher.update(&padding[..]);
        padding.zeroize(); // it holds SK.prf XOR opad (FIPS 205 §3.1)
        outer_hasher.update(inner_hasher.finalize());
        outer_hasher.finalize().into()
    }


    pub(crate) fn prf_msg<const N: usize>(sk_prf: &[u8], opt_rand: &[u8], m: &[&[u8]]) -> [u8; N] {
        let mut digest = [0u8; N];
        let full_digest = hmac_sha_256(sk_prf, opt_rand, m);
        digest.copy_from_slice(&full_digest[0..N]);
        digest
    }


    pub(crate) fn f<const N: usize>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8]) -> [u8; N] {
        let mut digest = [0u8; N];
        let zeros = [0u8; 48];
        sha2_256(&[pk_seed, &zeros[0..(64 - N)], &adrs.to_22_bytes(), m1], &mut digest);
        digest
    }


    pub(crate) fn h<const N: usize>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8]) -> [u8; N] {
        let mut digest = [0u8; N];
        let zeros = [0u8; 48];
        sha2_256(&[pk_seed, &zeros[0..(64 - N)], &adrs.to_22_bytes(), m1, m2], &mut digest);
        digest
    }


    pub(crate) fn t_l<const LEN: usize, const N: usize>(
        pk_seed: &[u8], adrs: &Adrs, ml: &[[u8; N]; LEN],
    ) -> [u8; N] {
        let mut result = [0u8; N];
        let zeros = [0u8; 48];
        let mut hasher = Sha256::new();
        hasher.update(pk_seed);
        hasher.update(&zeros[0..(64 - N)]);
        hasher.update(adrs.to_22_bytes());
        for item in ml {
            hasher.update(item);
        }
        let digest = hasher.finalize();
        result.copy_from_slice(&digest[0..N]);
        result
    }
}


#[cfg(any(
    feature = "slh-dsa-sha2-192f",
    feature = "slh-dsa-sha2-192s",
    feature = "slh-dsa-sha2-256f",
    feature = "slh-dsa-sha2-256s"
))]
pub(crate) mod sha2_cat_3_5 {
    use crate::types::Adrs;
    use core::cmp::min;
    use sha2::{Digest, Sha256, Sha512};
    use zeroize::Zeroize;


    fn sha2_256(input: &[&[u8]], out: &mut [u8]) {
        let mut hasher = Sha256::new();
        for item in input {
            hasher.update(item);
        }
        let result = hasher.finalize();
        out.copy_from_slice(&result[0..out.len()]);
    }


    fn sha2_512(input: &[&[u8]], out: &mut [u8]) {
        let mut hasher = Sha512::new();
        for item in input {
            hasher.update(item);
        }
        let result = hasher.finalize();
        out.copy_from_slice(&result[0..out.len()]);
    }


    pub(crate) fn h_msg<const M: usize>(
        r: &[u8], pk_seed: &[u8], pk_root: &[u8], m: &[&[u8]],
    ) -> [u8; M] {
        let mut digest1 = [0u8; 64];
        let mut inp = [r, pk_seed, pk_root, &[], &[], &[], &[], &[]];
        inp[3..3 + m.len()].copy_from_slice(m); // m can have up to 5 elements
        sha2_512(&inp, &mut digest1);
        let mut result = [0u8; M];
        let mut start = 0;
        let mut counter = 0u32;
        while start < M {
            let mut tmp = [0u8; 64];
            sha2_512(&[r, pk_seed, &digest1, &counter.to_be_bytes()], &mut tmp);
            let len = min(M - start, 64);
            result[start..start + len].copy_from_slice(&tmp[0..len]);
            start += 64;
            counter += 1;
        }
        result
    }


    #[allow(clippy::similar_names)] // pk_seed and sk_seed
    pub(crate) fn prf<const N: usize>(pk_seed: &[u8], sk_seed: &[u8], adrs: &Adrs) -> [u8; N] {
        let mut digest = [0u8; N];
        let zeros = [0u8; 40];
        sha2_256(&[pk_seed, &zeros[0..(64 - N)], &adrs.to_22_bytes(), sk_seed], &mut digest); // Spec swaps order of last two params 557/997/1005
        digest
    }


    fn hmac_sha_512(key: &[u8], a0: &[u8], m: &[&[u8]]) -> [u8; 64] {
        let mut padding = [0x36; 128];
        for (p, &k) in padding.iter_mut().zip(key.iter()) {
            *p ^= k;
        }
        let mut inner_hasher = Sha512::new();
        inner_hasher.update(&padding[..]);
        inner_hasher.update(a0);
        for item in m {
            inner_hasher.update(item);
        }
        for p in &mut padding {
            *p ^= 0x6a;
        }
        let mut outer_hasher = Sha512::new();
        outer_hasher.update(&padding[..]);
        padding.zeroize(); // it holds SK.prf XOR opad (FIPS 205 §3.1)
        outer_hasher.update(inner_hasher.finalize());
        outer_hasher.finalize().into()
    }


    pub(crate) fn prf_msg<const N: usize>(sk_prf: &[u8], opt_rand: &[u8], m: &[&[u8]]) -> [u8; N] {
        let mut digest = [0u8; N];
        let full_digest = hmac_sha_512(sk_prf, opt_rand, m);
        digest.copy_from_slice(&full_digest[0..N]);
        digest
    }


    pub(crate) fn f<const N: usize>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8]) -> [u8; N] {
        let mut digest = [0u8; N];
        let zeros = [0u8; 40];
        sha2_256(&[pk_seed, &zeros[0..(64 - N)], &adrs.to_22_bytes(), m1], &mut digest);
        digest
    }


    pub(crate) fn h<const N: usize>(pk_seed: &[u8], adrs: &Adrs, m1: &[u8], m2: &[u8]) -> [u8; N] {
        let mut digest = [0u8; N];
        let zeros = [0u8; 104];
        sha2_512(&[pk_seed, &zeros[0..(128 - N)], &adrs.to_22_bytes(), m1, m2], &mut digest);
        digest
    }


    pub(crate) fn t_l<const LEN: usize, const N: usize>(
        pk_seed: &[u8], adrs: &Adrs, ml: &[[u8; N]; LEN],
    ) -> [u8; N] {
        let mut result = [0u8; N];
        let zeros = [0u8; 104];
        let mut hasher = Sha512::new();
        hasher.update(pk_seed);
        hasher.update(&zeros[0..(128 - N)]);
        hasher.update(adrs.to_22_bytes());
        for item in ml {
            hasher.update(item);
        }
        let digest = hasher.finalize();
        result.copy_from_slice(&digest[0..N]);
        result
    }
}