Skip to main content

relay_crypto/
rng.rs

1use rand_core::{CryptoRng, RngCore, SeedableRng as _};
2
3#[derive(Debug, Clone, thiserror::Error)]
4pub enum RngError {
5    #[error("Failed to get random bytes: {0}")]
6    OsRng(getrandom::Error),
7    #[error("HKDF error: {0}")]
8    Hkdf(hkdf::InvalidLength),
9}
10
11/// Hashes an os ranadom seed with the nonce provided. Provices a sense of security against OS RNG failures.
12pub fn os_rng_hkdf(
13    nonce: Option<&[u8]>,
14    context: &[u8],
15) -> Result<impl RngCore + CryptoRng, RngError> {
16    let mut ikm = [0u8; 32];
17    getrandom::fill(&mut ikm).map_err(RngError::OsRng)?;
18
19    let hk = hkdf::Hkdf::<sha2::Sha256>::new(nonce, &ikm);
20
21    let mut seed = [0u8; 32];
22    hk.expand(context, &mut seed).map_err(RngError::Hkdf)?;
23
24    Ok(rand_chacha::ChaCha20Rng::from_seed(seed))
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[test]
32    fn test_os_rng_hkdf() {
33        let mut rng1 = os_rng_hkdf(None, b"test-context").unwrap();
34        let mut rng2 = os_rng_hkdf(Some(b"hey there!"), b"test-context").unwrap();
35
36        let mut buf1 = [0u8; 256];
37        let mut buf2 = [0u8; 256];
38
39        rng1.fill_bytes(&mut buf1);
40        rng2.fill_bytes(&mut buf2);
41
42        // There is a very small chance this could fail if both RNGs produce the same output :DDD
43        assert_ne!(buf1, buf2);
44    }
45}