Skip to main content

waggle_core/
entropy.rs

1//! Entropy injection — randomness as a parameter, not an ambient source.
2
3use thiserror::Error;
4
5/// Failure of an entropy source.
6///
7/// Carries the host's own message; the core never inspects it beyond
8/// propagation (the caller chose the source, the caller understands its
9/// failures).
10#[derive(Debug, Error)]
11#[error("entropy source failed: {0}")]
12pub struct EntropyError(pub String);
13
14/// A source of cryptographically secure random bytes.
15///
16/// Blanket-implemented for closures so hosts pass a function, not a global:
17/// natively `|b| getrandom::getrandom(b).map_err(...)`, in Workers the JS
18/// crypto equivalent, in tests a counter. Upholds the sans-I/O law
19/// (design doc `03 §1`).
20pub trait Entropy {
21    /// Fill `buf` entirely with random bytes, or fail.
22    fn fill(&mut self, buf: &mut [u8]) -> Result<(), EntropyError>;
23}
24
25impl<F> Entropy for F
26where
27    F: FnMut(&mut [u8]) -> Result<(), EntropyError>,
28{
29    fn fill(&mut self, buf: &mut [u8]) -> Result<(), EntropyError> {
30        self(buf)
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn closures_are_entropy_sources() {
40        let mut calls = 0usize;
41        let mut src = |buf: &mut [u8]| {
42            calls += 1;
43            buf.fill(0xA5);
44            Ok(())
45        };
46        let mut buf = [0u8; 4];
47        src.fill(&mut buf).unwrap();
48        assert_eq!(buf, [0xA5; 4]);
49        assert_eq!(calls, 1);
50    }
51
52    #[test]
53    fn failures_propagate() {
54        let mut src = |_: &mut [u8]| Err(EntropyError("no randomness today".into()));
55        let mut buf = [0u8; 1];
56        let err = src.fill(&mut buf).unwrap_err();
57        assert!(err.to_string().contains("no randomness"));
58    }
59}