bc_components/
hkdf_rng.rs

1use bc_crypto::hash::hkdf_hmac_sha256;
2use rand_core::{ CryptoRng, RngCore };
3use zeroize::ZeroizeOnDrop;
4
5/// A deterministic random number generator based on HKDF-HMAC-SHA256.
6///
7/// `HKDFRng` uses the HMAC-based Key Derivation Function (HKDF) to generate deterministic
8/// random numbers from a combination of key material and salt. It serves as a key-stretching
9/// mechanism that can produce an arbitrary amount of random-looking bytes from a single seed.
10///
11/// Since it produces deterministic output based on the same inputs, it's useful for
12/// situations where repeatable randomness is required, such as in testing or when
13/// deterministically deriving keys from a master seed.
14///
15/// Security considerations:
16/// - The security of the generator depends on the entropy and secrecy of the key material
17/// - The same key material and salt will always produce the same sequence
18/// - Use a secure random seed for cryptographic applications
19/// - Never reuse the same HKDFRng instance for different purposes
20///
21/// The implementation automatically handles buffer management, fetching new data
22/// using HKDF as needed with an incrementing counter to ensure unique output
23/// for each request.
24#[derive(ZeroizeOnDrop)]
25pub struct HKDFRng {
26    /// Internal buffer of generated bytes
27    buffer: Vec<u8>,
28    /// Current position in the buffer
29    position: usize,
30    /// Source key material (seed)
31    key_material: Vec<u8>,
32    /// Salt value to combine with the key material
33    salt: String,
34    /// Length of each "page" of generated data
35    page_length: usize,
36    /// Current page index
37    page_index: usize,
38}
39
40impl HKDFRng {
41    /// Creates a new `HKDFRng` with a custom page length.
42    ///
43    /// # Parameters
44    ///
45    /// * `key_material` - The seed material to derive random numbers from
46    /// * `salt` - A salt value to mix with the key material
47    /// * `page_length` - The number of bytes to generate in each HKDF call
48    ///
49    /// # Returns
50    ///
51    /// A new `HKDFRng` instance configured with the specified parameters.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use bc_components::HKDFRng;
57    /// use rand_core::RngCore;
58    ///
59    /// // Create an HKDF-based RNG with a 64-byte page length
60    /// let mut rng = HKDFRng::new_with_page_length(b"my secure seed", "application-context", 64);
61    ///
62    /// // Generate some random bytes
63    /// let random_u32 = rng.next_u32();
64    /// ```
65    pub fn new_with_page_length(
66        key_material: impl AsRef<[u8]>,
67        salt: &str,
68        page_length: usize
69    ) -> Self {
70        Self {
71            buffer: Vec::new(),
72            position: 0,
73            key_material: key_material.as_ref().to_vec(),
74            salt: salt.to_string(),
75            page_length,
76            page_index: 0,
77        }
78    }
79
80    /// Creates a new `HKDFRng` with the default page length of 32 bytes.
81    ///
82    /// # Parameters
83    ///
84    /// * `key_material` - The seed material to derive random numbers from
85    /// * `salt` - A salt value to mix with the key material
86    ///
87    /// # Returns
88    ///
89    /// A new `HKDFRng` instance configured with the specified key material and salt.
90    ///
91    /// # Example
92    ///
93    /// ```
94    /// use bc_components::HKDFRng;
95    /// use rand_core::RngCore;
96    ///
97    /// // Create an HKDF-based RNG
98    /// let mut rng = HKDFRng::new(b"my secure seed", "wallet-derivation");
99    ///
100    /// // Generate two u32 values
101    /// let random1 = rng.next_u32();
102    /// let random2 = rng.next_u32();
103    ///
104    /// // The same seed and salt will always produce the same sequence
105    /// let mut rng2 = HKDFRng::new(b"my secure seed", "wallet-derivation");
106    /// assert_eq!(random1, rng2.next_u32());
107    /// assert_eq!(random2, rng2.next_u32());
108    /// ```
109    pub fn new(key_material: impl AsRef<[u8]>, salt: &str) -> Self {
110        Self::new_with_page_length(key_material, salt, 32)
111    }
112
113    /// Refills the internal buffer with new deterministic random bytes.
114    ///
115    /// This method is called automatically when the internal buffer is exhausted.
116    /// It uses HKDF-HMAC-SHA256 to generate a new page of random bytes using the
117    /// key material, salt, and current page index.
118    fn fill_buffer(&mut self) {
119        let salt_string = format!("{}-{}", self.salt, self.page_index);
120        let hkdf = hkdf_hmac_sha256(&self.key_material, salt_string, self.page_length);
121        self.buffer = hkdf;
122        self.position = 0;
123        self.page_index += 1;
124    }
125
126    /// Generates the specified number of deterministic random bytes.
127    ///
128    /// # Parameters
129    ///
130    /// * `length` - The number of bytes to generate
131    ///
132    /// # Returns
133    ///
134    /// A vector containing the requested number of deterministic random bytes.
135    fn next_bytes(&mut self, length: usize) -> Vec<u8> {
136        let mut result = Vec::new();
137        while result.len() < length {
138            if self.position >= self.buffer.len() {
139                self.fill_buffer();
140            }
141            let remaining = length - result.len();
142            let available = self.buffer.len() - self.position;
143            let take = remaining.min(available);
144            result.extend_from_slice(&self.buffer[self.position..self.position + take]);
145            self.position += take;
146        }
147        result
148    }
149}
150
151/// Implementation of the `RngCore` trait for `HKDFRng`.
152///
153/// This allows `HKDFRng` to be used with any code that accepts a random
154/// number generator implementing the standard Rust traits.
155impl RngCore for HKDFRng {
156    /// Generates a random `u32` value.
157    ///
158    /// # Returns
159    ///
160    /// A deterministic random 32-bit unsigned integer.
161    fn next_u32(&mut self) -> u32 {
162        let bytes = self.next_bytes(4);
163        u32::from_le_bytes(bytes.try_into().unwrap())
164    }
165
166    /// Generates a random `u64` value.
167    ///
168    /// # Returns
169    ///
170    /// A deterministic random 64-bit unsigned integer.
171    fn next_u64(&mut self) -> u64 {
172        let bytes = self.next_bytes(8);
173        u64::from_le_bytes(bytes.try_into().unwrap())
174    }
175
176    /// Fills the provided buffer with random bytes.
177    ///
178    /// # Parameters
179    ///
180    /// * `dest` - The buffer to fill with random bytes
181    fn fill_bytes(&mut self, dest: &mut [u8]) {
182        let bytes = self.next_bytes(dest.len());
183        dest.copy_from_slice(&bytes);
184    }
185
186    /// Attempts to fill the provided buffer with random bytes.
187    ///
188    /// This implementation never fails, so it simply calls `fill_bytes`.
189    ///
190    /// # Parameters
191    ///
192    /// * `dest` - The buffer to fill with random bytes
193    ///
194    /// # Returns
195    ///
196    /// Always returns `Ok(())` as this implementation cannot fail.
197    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
198        self.fill_bytes(dest);
199        Ok(())
200    }
201}
202
203/// Implementation of the `CryptoRng` marker trait for `HKDFRng`.
204///
205/// This marker indicates that `HKDFRng` is suitable for cryptographic use
206/// when seeded with appropriately secure key material.
207impl CryptoRng for HKDFRng {}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_hkdf_rng_new() {
215        let rng = HKDFRng::new(b"key_material", "salt");
216        assert_eq!(rng.key_material, b"key_material".to_vec());
217        assert_eq!(rng.salt, "salt");
218        assert_eq!(rng.page_length, 32);
219        assert_eq!(rng.page_index, 0);
220        assert!(rng.buffer.is_empty());
221        assert_eq!(rng.position, 0);
222    }
223
224    #[test]
225    fn test_hkdf_rng_fill_buffer() {
226        let mut rng = HKDFRng::new(b"key_material", "salt");
227        rng.fill_buffer();
228        assert!(!rng.buffer.is_empty()); // Buffer should be filled
229        assert_eq!(rng.position, 0); // Position should be reset
230        assert_eq!(rng.page_index, 1); // Page index should be incremented
231    }
232
233    #[test]
234    fn test_hkdf_rng_next_bytes() {
235        let mut rng = HKDFRng::new(b"key_material", "salt");
236        assert_eq!(hex::encode(rng.next_bytes(16)), "1032ac8ffea232a27c79fe381d7eb7e4");
237        assert_eq!(hex::encode(rng.next_bytes(16)), "aeaaf727d35b6f338218391f9f8fa1f3");
238        assert_eq!(hex::encode(rng.next_bytes(16)), "4348a59427711deb1e7d8a6959c6adb4");
239        assert_eq!(hex::encode(rng.next_bytes(16)), "5d937a42cb5fb090fe1a1ec88f56e32b");
240    }
241
242    #[test]
243    fn test_hkdf_rng_next_u32() {
244        let mut rng = HKDFRng::new(b"key_material", "salt");
245        let num = rng.next_u32();
246        assert_eq!(num, 2410426896);
247    }
248
249    #[test]
250    fn test_hkdf_rng_next_u64() {
251        let mut rng = HKDFRng::new(b"key_material", "salt");
252        let num = rng.next_u64();
253        assert_eq!(num, 11687583197195678224);
254    }
255
256    #[test]
257    fn test_hkdf_rng_fill_bytes() {
258        let mut rng = HKDFRng::new(b"key_material", "salt");
259        let mut dest = [0u8; 16];
260        rng.fill_bytes(&mut dest);
261        assert_eq!(hex::encode(dest), "1032ac8ffea232a27c79fe381d7eb7e4");
262    }
263
264    #[test]
265    fn test_hkdf_rng_try_fill_bytes() {
266        let mut rng = HKDFRng::new(b"key_material", "salt");
267        let mut dest = [0u8; 16];
268        assert!(rng.try_fill_bytes(&mut dest).is_ok()); // Should succeed without errors
269        assert_eq!(hex::encode(dest), "1032ac8ffea232a27c79fe381d7eb7e4");
270    }
271}