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