Skip to main content

nula_core/util/
rng.rs

1//! Random byte generation helpers.
2//!
3//! The Nostr protocol needs cryptographically random bytes for fresh secret
4//! keys and unique subscription IDs. This module wraps [`getrandom`] and
5//! exposes a tiny stable API: it returns errors instead of panicking when the
6//! operating system fails to provide entropy, which is critical for relays
7//! and signers running in constrained environments (containers, jails, kernel
8//! lockdown, …).
9
10use thiserror::Error;
11
12/// Error returned when the operating system fails to provide entropy.
13#[derive(Debug, Clone, Copy, Error)]
14#[error("operating system failed to provide entropy: {0}")]
15#[non_exhaustive]
16pub struct RngError(#[from] pub(crate) getrandom::Error);
17
18/// Fill `buf` with cryptographically secure random bytes from the OS.
19///
20/// # Errors
21///
22/// Propagates errors from the OS entropy source.
23pub fn fill_bytes(buf: &mut [u8]) -> Result<(), RngError> {
24    getrandom::fill(buf)?;
25    Ok(())
26}
27
28/// Return `N` cryptographically secure random bytes from the OS RNG.
29///
30/// # Errors
31///
32/// Propagates errors from the OS entropy source.
33pub fn random_bytes<const N: usize>() -> Result<[u8; N], RngError> {
34    let mut out = [0_u8; N];
35    fill_bytes(&mut out)?;
36    Ok(out)
37}
38
39/// Generate a lowercase hex string built from `N` random bytes.
40///
41/// The output is always `2 * N` characters long. This is the canonical format
42/// used by Nostr subscription IDs and other opaque identifiers carried over
43/// the wire.
44///
45/// # Errors
46///
47/// Propagates errors from the OS entropy source.
48pub fn random_hex_string<const N: usize>() -> Result<String, RngError> {
49    let bytes = random_bytes::<N>()?;
50    Ok(crate::util::hex::encode(bytes))
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn random_bytes_distinct() {
59        let lhs: [u8; 32] = random_bytes().unwrap();
60        let rhs: [u8; 32] = random_bytes().unwrap();
61        assert_ne!(lhs, rhs);
62    }
63
64    #[test]
65    fn random_hex_string_length() {
66        let value = random_hex_string::<16>().unwrap();
67        assert_eq!(value.len(), 32);
68        assert!(value.chars().all(|c| c.is_ascii_hexdigit()));
69    }
70}