Skip to main content

minip2p_platform/
entropy.rs

1use alloc::rc::Rc;
2use core::cell::RefCell;
3use thiserror::Error;
4
5/// Why an [`EntropySource`] could not produce randomness.
6///
7/// Both variants are fatal for the operation that needed the bytes. Callers
8/// must fail that operation rather than fall back to a weaker source: minip2p
9/// uses entropy for key generation, Noise handshakes, and nonces, where a
10/// predictable substitute is a security bug.
11#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
12pub enum EntropyError {
13    /// The platform has no entropy source at all.
14    ///
15    /// Permanent — retrying will not help. Typically an embedded target built
16    /// without a hardware RNG or a configured seed.
17    #[error("no entropy source available: {reason}")]
18    Unavailable {
19        /// What the adapter expected to find.
20        reason: &'static str,
21    },
22    /// The entropy source exists but failed to produce bytes.
23    ///
24    /// May be transient, for example a hardware RNG reporting a health-check
25    /// failure or an exhausted file descriptor table.
26    #[error("entropy source failed: {reason}")]
27    Failed {
28        /// What went wrong.
29        reason: &'static str,
30        /// Platform error code, when the adapter has one.
31        code: Option<i32>,
32    },
33}
34
35impl EntropyError {
36    /// Creates an [`Unavailable`](Self::Unavailable) error.
37    pub const fn unavailable(reason: &'static str) -> Self {
38        Self::Unavailable { reason }
39    }
40
41    /// Creates a [`Failed`](Self::Failed) error without a platform code.
42    pub const fn failed(reason: &'static str) -> Self {
43        Self::Failed { reason, code: None }
44    }
45
46    /// Creates a [`Failed`](Self::Failed) error carrying a platform code.
47    pub const fn failed_with_code(reason: &'static str, code: i32) -> Self {
48        Self::Failed {
49            reason,
50            code: Some(code),
51        }
52    }
53}
54
55/// A source of cryptographically secure random bytes.
56///
57/// Implementations live in adapters and are injected into the components that
58/// need randomness, so protocol crates stay deterministic and testable.
59///
60/// # Contract
61///
62/// - Bytes must be suitable for cryptographic use: unpredictable to an attacker
63///   who has observed every previous output.
64/// - On `Ok`, the whole of `output` is filled.
65/// - On `Err`, `output` may have been partially written and must be treated as
66///   containing no entropy.
67pub trait EntropySource {
68    /// Fills `output` with random bytes.
69    fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError>;
70
71    /// Draws a random `u64`.
72    fn next_u64(&mut self) -> Result<u64, EntropyError> {
73        let mut bytes = [0u8; 8];
74        self.fill_bytes(&mut bytes)?;
75        Ok(u64::from_le_bytes(bytes))
76    }
77}
78
79/// Cloneable, single-threaded access to one entropy source.
80///
81/// Portable endpoint compositions use this when several independently owned
82/// protocol components must draw from the same hardware RNG. Calls remain
83/// serialized and no output is replayed or copied.
84pub struct SharedEntropy<E> {
85    inner: Rc<RefCell<E>>,
86}
87
88impl<E> SharedEntropy<E> {
89    /// Wraps an entropy source for shared ownership.
90    pub fn new(source: E) -> Self {
91        Self {
92            inner: Rc::new(RefCell::new(source)),
93        }
94    }
95}
96
97impl<E> Clone for SharedEntropy<E> {
98    fn clone(&self) -> Self {
99        Self {
100            inner: Rc::clone(&self.inner),
101        }
102    }
103}
104
105impl<E: EntropySource> EntropySource for SharedEntropy<E> {
106    fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
107        self.inner.borrow_mut().fill_bytes(output)
108    }
109
110    fn next_u64(&mut self) -> Result<u64, EntropyError> {
111        self.inner.borrow_mut().next_u64()
112    }
113}
114
115impl<E: EntropySource + ?Sized> EntropySource for &mut E {
116    fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
117        (**self).fill_bytes(output)
118    }
119
120    fn next_u64(&mut self) -> Result<u64, EntropyError> {
121        (**self).next_u64()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use alloc::boxed::Box;
129
130    /// Counter-based stand-in; deterministic so tests can assert on output.
131    struct Counter(u8);
132
133    impl EntropySource for Counter {
134        fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
135            for byte in output.iter_mut() {
136                *byte = self.0;
137                self.0 = self.0.wrapping_add(1);
138            }
139            Ok(())
140        }
141    }
142
143    struct Broken;
144
145    impl EntropySource for Broken {
146        fn fill_bytes(&mut self, output: &mut [u8]) -> Result<(), EntropyError> {
147            // Partial write before failing: callers must not trust `output`.
148            if let Some(first) = output.first_mut() {
149                *first = 0xff;
150            }
151            Err(EntropyError::failed_with_code("rng offline", 5))
152        }
153    }
154
155    #[test]
156    fn fill_bytes_fills_the_whole_slice() {
157        let mut source = Counter(1);
158        let mut buffer = [0u8; 4];
159        source.fill_bytes(&mut buffer).expect("fill");
160        assert_eq!(buffer, [1, 2, 3, 4]);
161    }
162
163    #[test]
164    fn next_u64_reads_eight_little_endian_bytes() {
165        let mut source = Counter(1);
166        let value = source.next_u64().expect("draw");
167        assert_eq!(value, u64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]));
168    }
169
170    #[test]
171    fn shared_handles_advance_one_underlying_stream() {
172        let mut first = SharedEntropy::new(Counter(1));
173        let mut second = first.clone();
174        let mut a = [0; 2];
175        let mut b = [0; 2];
176
177        first.fill_bytes(&mut a).expect("first draw");
178        second.fill_bytes(&mut b).expect("second draw");
179
180        assert_eq!(a, [1, 2]);
181        assert_eq!(b, [3, 4]);
182    }
183
184    #[test]
185    fn failures_propagate_through_next_u64() {
186        let mut source = Broken;
187        assert_eq!(
188            source.next_u64(),
189            Err(EntropyError::Failed {
190                reason: "rng offline",
191                code: Some(5)
192            })
193        );
194    }
195
196    /// Generic over `E: EntropySource`, so passing `&mut Counter` exercises the
197    /// blanket impl rather than auto-deref.
198    fn draw<E: EntropySource>(mut source: E, buffer: &mut [u8]) -> Result<u64, EntropyError> {
199        source.fill_bytes(buffer)?;
200        source.next_u64()
201    }
202
203    #[test]
204    fn mutable_reference_forwards_to_inner_source() {
205        let mut source = Counter(1);
206        let mut buffer = [0u8; 2];
207        let drawn = draw(&mut source, &mut buffer).expect("draw");
208        assert_eq!(buffer, [1, 2]);
209        assert_eq!(drawn, u64::from_le_bytes([3, 4, 5, 6, 7, 8, 9, 10]));
210        assert_eq!(source.0, 11);
211    }
212
213    #[test]
214    fn trait_is_object_safe() {
215        let mut source: Box<dyn EntropySource> = Box::new(Counter(9));
216        let mut buffer = [0u8; 2];
217        source.fill_bytes(&mut buffer).expect("fill");
218        assert_eq!(buffer, [9, 10]);
219    }
220}