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}