Skip to main content

arcana/drbg/
hash.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4//! Hash_DRBG — SP 800-90A Rev.1 §10.1.1.
5//!
6//! Generic over any [`tessera::Digest`]; instantiated in practice over
7//! SHA-256 / SHA-384 / SHA-512. The derivation function is `Hash_df`
8//! (§10.3.1).
9//!
10//! # Parameters (SP 800-90A §10.1, Table 2)
11//!
12//! | Hash        | security_strength | seedlen (bits) | seedlen (octets) |
13//! |-------------|-------------------|----------------|------------------|
14//! | SHA-1       | 128               | 440            | 55               |
15//! | SHA-224     | 192               | 440            | 55               |
16//! | SHA-256     | 256               | 440            | 55               |
17//! | SHA-384     | 256               | 888            | 111              |
18//! | SHA-512     | 256               | 888            | 111              |
19//!
20//! `reseed_interval = 2^48`, `max_number_of_bits_per_request = 2^19`
21//! (= 65 536 octets), `max_additional_input_length = 2^35` bits.
22//!
23//! # Side-channel posture
24//!
25//! The state (`V`, `C`) is secret; see the module-level
26//! [`crate::drbg`] posture. The mod-`2^seedlen` additions of the
27//! Generate step go through the branch-free [`super::util::ct_add_into`] /
28//! [`super::util::ct_add_u64`]; the only lookups are into the (public)
29//! `Hash_df` counter byte and the digest itself, both data-oblivious.
30//! SHA-2 has no secret-dependent branch or table index.
31
32use crate::drbg::Error;
33use crate::drbg::util::{ct_add_into, ct_add_u64};
34use tessera::Digest;
35
36/// `reseed_interval` for Hash_DRBG (SP 800-90A §10.1, Table 2): 2^48.
37const RESEED_INTERVAL: u64 = 1 << 48;
38/// `max_number_of_bits_per_request` = 2^19 bits = 65 536 octets (Table 2).
39const MAX_REQUEST_OCTETS: usize = 1 << 16;
40/// Practical cap on optional input lengths (2^35 bits ≫ any real buffer);
41/// we bound at 2^32 octets to keep the 32-bit length prefixes of `Hash_df`
42/// well-defined.
43const MAX_OPT_OCTETS: usize = u32::MAX as usize;
44
45/// Largest seedlen among the crate's digests (SHA-384/512 → 111 octets).
46const MAX_SEEDLEN: usize = 111;
47/// Largest digest output among the crate's digests (SHA-512 → 64 octets).
48const MAX_OUTPUT: usize = 64;
49
50/// `seedlen` in octets for the digest `H` (SP 800-90A §10.1, Table 2):
51/// 440 bits (55 octets) for outputs ≤ 256 bits, else 888 bits (111 octets).
52const fn seedlen_octets<H: Digest>() -> usize {
53    if H::OUTPUT_LEN <= 32 { 55 } else { 111 }
54}
55
56/// The derivation function `Hash_df` (SP 800-90A §10.3.1).
57///
58/// `Hash_df(input_string, no_of_bits_to_return)`:
59/// ```text
60///   temp = ""
61///   len  = ceil(no_of_bits_to_return / outlen)
62///   counter = 0x01
63///   for i in 1..=len:
64///       temp = temp || Hash(counter || no_of_bits_to_return || input_string)
65///       counter += 1
66///   return leftmost no_of_bits_to_return bits of temp
67/// ```
68/// `input_parts` are concatenated as the `input_string`. `out.len()`
69/// octets are produced (`no_of_bits_to_return = 8·out.len()`).
70fn hash_df<H: Digest>(input_parts: &[&[u8]], out: &mut [u8]) {
71    let outlen = H::OUTPUT_LEN;
72    let no_of_bits = (out.len() as u32) * 8;
73    let no_of_bits_be = no_of_bits.to_be_bytes();
74    let mut counter: u8 = 1;
75    let mut off = 0;
76    let mut block = [0u8; MAX_OUTPUT];
77    while off < out.len() {
78        let mut h = H::new();
79        h.update(&[counter]);
80        h.update(&no_of_bits_be);
81        for p in input_parts {
82            h.update(p);
83        }
84        h.finalize(&mut block[..outlen]);
85        let take = core::cmp::min(outlen, out.len() - off);
86        out[off..off + take].copy_from_slice(&block[..take]);
87        off += take;
88        counter = counter.wrapping_add(1);
89    }
90}
91
92/// Hash_DRBG internal state (SP 800-90A §10.1.1.1): the value `V`, the
93/// constant `C` (both `seedlen` octets), and the `reseed_counter`.
94///
95/// `V` and `C` are secret. `H` fixes the hash and thereby `seedlen`.
96pub struct HashDrbg<H: Digest> {
97    v: [u8; MAX_SEEDLEN],
98    c: [u8; MAX_SEEDLEN],
99    reseed_counter: u64,
100    seedlen: usize,
101    _marker: core::marker::PhantomData<H>,
102}
103
104impl<H: Digest> HashDrbg<H> {
105    /// Instantiate a Hash_DRBG (SP 800-90A §10.1.1.2).
106    ///
107    /// * `entropy_input` — at least `security_strength` bits (§8.6.3); the
108    ///   caller owns the entropy source. Enforced here as
109    ///   `8·len ≥ security_strength`.
110    /// * `nonce` — at least `security_strength/2` bits (§8.6.7).
111    /// * `personalization` — optional, may be empty (§8.7.1).
112    ///
113    /// ```text
114    ///   seed_material = entropy_input || nonce || personalization_string
115    ///   V = Hash_df(seed_material, seedlen)
116    ///   C = Hash_df(0x00 || V, seedlen)
117    ///   reseed_counter = 1
118    /// ```
119    pub fn instantiate(entropy_input: &[u8], nonce: &[u8], personalization: &[u8]) -> Result<Self, Error> {
120        let sec_strength_bits = security_strength::<H>();
121        if entropy_input.len() * 8 < sec_strength_bits {
122            return Err(Error::InsufficientEntropy);
123        }
124        if personalization.len() > MAX_OPT_OCTETS {
125            return Err(Error::PersonalizationTooLong);
126        }
127        let seedlen = seedlen_octets::<H>();
128        let mut s = HashDrbg::<H> {
129            v: [0u8; MAX_SEEDLEN],
130            c: [0u8; MAX_SEEDLEN],
131            reseed_counter: 1,
132            seedlen,
133            _marker: core::marker::PhantomData,
134        };
135        // V = Hash_df(entropy || nonce || pers, seedlen)
136        hash_df::<H>(&[entropy_input, nonce, personalization], &mut s.v[..seedlen]);
137        // C = Hash_df(0x00 || V, seedlen)
138        let mut newc = [0u8; MAX_SEEDLEN];
139        hash_df::<H>(&[&[0x00u8], &s.v[..seedlen]], &mut newc[..seedlen]);
140        s.c[..seedlen].copy_from_slice(&newc[..seedlen]);
141        s.reseed_counter = 1;
142        Ok(s)
143    }
144
145    /// Reseed a Hash_DRBG (SP 800-90A §10.1.1.3).
146    ///
147    /// ```text
148    ///   seed_material = 0x01 || V || entropy_input || additional_input
149    ///   V = Hash_df(seed_material, seedlen)
150    ///   C = Hash_df(0x00 || V, seedlen)
151    ///   reseed_counter = 1
152    /// ```
153    pub fn reseed(&mut self, entropy_input: &[u8], additional_input: &[u8]) -> Result<(), Error> {
154        let sec_strength_bits = security_strength::<H>();
155        if entropy_input.len() * 8 < sec_strength_bits {
156            return Err(Error::InsufficientEntropy);
157        }
158        if additional_input.len() > MAX_OPT_OCTETS {
159            return Err(Error::AdditionalInputTooLong);
160        }
161        let seedlen = self.seedlen;
162        // seed_material = 0x01 || V || entropy || add_input
163        let old_v = self.v; // copy; V is overwritten below
164        hash_df::<H>(
165            &[&[0x01u8], &old_v[..seedlen], entropy_input, additional_input],
166            &mut self.v[..seedlen],
167        );
168        // C = Hash_df(0x00 || V, seedlen). Recompute into a scratch then store.
169        let mut newc = [0u8; MAX_SEEDLEN];
170        hash_df::<H>(&[&[0x00u8], &self.v[..seedlen]], &mut newc[..seedlen]);
171        self.c[..seedlen].copy_from_slice(&newc[..seedlen]);
172        self.reseed_counter = 1;
173        Ok(())
174    }
175
176    /// Generate pseudorandom bits (SP 800-90A §10.1.1.4).
177    ///
178    /// Fills `out` with `8·out.len()` pseudorandom bits.
179    ///
180    /// ```text
181    ///   if reseed_counter > reseed_interval: return "Reseed required"
182    ///   if additional_input != Null:
183    ///       w = Hash(0x02 || V || additional_input)
184    ///       V = (V + w) mod 2^seedlen
185    ///   returned_bits = Hashgen(requested_bits, V)
186    ///   H = Hash(0x03 || V)
187    ///   V = (V + H + C + reseed_counter) mod 2^seedlen
188    ///   reseed_counter += 1
189    /// ```
190    pub fn generate(&mut self, out: &mut [u8], additional_input: &[u8]) -> Result<(), Error> {
191        if out.len() > MAX_REQUEST_OCTETS {
192            return Err(Error::RequestTooLarge);
193        }
194        if additional_input.len() > MAX_OPT_OCTETS {
195            return Err(Error::AdditionalInputTooLong);
196        }
197        // §10.1.1.4 step 1: reseed check.
198        if self.reseed_counter > RESEED_INTERVAL {
199            return Err(Error::ReseedRequired);
200        }
201        let seedlen = self.seedlen;
202        let outlen = H::OUTPUT_LEN;
203
204        // Step 2: fold in additional_input (only when present).
205        if !additional_input.is_empty() {
206            let mut w = [0u8; MAX_OUTPUT];
207            let mut h = H::new();
208            h.update(&[0x02u8]);
209            h.update(&self.v[..seedlen]);
210            h.update(additional_input);
211            h.finalize(&mut w[..outlen]);
212            ct_add_into(&mut self.v[..seedlen], &w[..outlen]);
213        }
214
215        // Step 3: Hashgen(requested_no_of_bits, V) → returned_bits (§10.1.1.4).
216        self.hashgen(out);
217
218        // Step 4-5: H = Hash(0x03 || V); V = (V + H + C + reseed_counter) mod 2^seedlen.
219        let mut hbuf = [0u8; MAX_OUTPUT];
220        let mut h = H::new();
221        h.update(&[0x03u8]);
222        h.update(&self.v[..seedlen]);
223        h.finalize(&mut hbuf[..outlen]);
224        ct_add_into(&mut self.v[..seedlen], &hbuf[..outlen]);
225        let c_copy = self.c;
226        ct_add_into(&mut self.v[..seedlen], &c_copy[..seedlen]);
227        ct_add_u64(&mut self.v[..seedlen], self.reseed_counter);
228
229        // Step 6: reseed_counter += 1.
230        self.reseed_counter += 1;
231        Ok(())
232    }
233
234    /// `Hashgen` (SP 800-90A §10.1.1.4, the internal generation loop):
235    /// ```text
236    ///   data = V
237    ///   W = ""
238    ///   for i in 1..=ceil(requested_bits / outlen):
239    ///       w = Hash(data); W = W || w
240    ///       data = (data + 1) mod 2^seedlen
241    ///   return leftmost requested_bits of W
242    /// ```
243    /// `data` is a copy of `V`; the running counter add is data-oblivious.
244    fn hashgen(&self, out: &mut [u8]) {
245        let seedlen = self.seedlen;
246        let outlen = H::OUTPUT_LEN;
247        let mut data = [0u8; MAX_SEEDLEN];
248        data[..seedlen].copy_from_slice(&self.v[..seedlen]);
249        let mut off = 0;
250        let mut w = [0u8; MAX_OUTPUT];
251        while off < out.len() {
252            let mut h = H::new();
253            h.update(&data[..seedlen]);
254            h.finalize(&mut w[..outlen]);
255            let take = core::cmp::min(outlen, out.len() - off);
256            out[off..off + take].copy_from_slice(&w[..take]);
257            off += take;
258            // data = (data + 1) mod 2^seedlen
259            crate::drbg::util::ct_increment(&mut data[..seedlen]);
260        }
261    }
262}
263
264/// `security_strength` in bits for the digest (SP 800-90A §10.1, Table 2):
265/// min(output, 256), floored at 128 for SHA-1 / SHA-224 handling; for the
266/// SHA-2 sizes used here this is 256 for SHA-256/384/512, 192 for SHA-224,
267/// 128 for SHA-1. We map by output length.
268const fn security_strength<H: Digest>() -> usize {
269    match H::OUTPUT_LEN {
270        20 => 128, // SHA-1
271        28 => 192, // SHA-224
272        _ => 256,  // SHA-256 / 384 / 512
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::drbg::test_util::hx;
280    use tessera::{Sha256, Sha384, Sha512};
281
282    // Official NIST CAVP DRBGVS vectors (CAVS 14.3, "drbgtestvectors").
283    //
284    // The DRBGVS procedure for each COUNT is: instantiate, then TWO
285    // generate calls (each with its own AdditionalInput); the returned
286    // bits of the SECOND call are the KAT `ReturnedBits`. For the
287    // prediction-resistance-false files a single Reseed (with its own
288    // AdditionalInputReseed) is interposed between instantiate and the
289    // first generate.
290
291    // drbgvectors_no_reseed / Hash_DRBG.txt — [SHA-256], Pers=0, AddIn=0, COUNT=0.
292    #[test]
293    fn hash_sha256_no_reseed_pers0_add0() {
294        let entropy = hx("a65ad0f345db4e0effe875c3a2e71f42c7129d620ff5c119a9ef55f05185e0fb");
295        let nonce = hx("8581f9317517276e06e9607ddbcbcc2e");
296        let mut drbg = HashDrbg::<Sha256>::instantiate(&entropy, &nonce, &[]).unwrap();
297        let mut out = [0u8; 128];
298        drbg.generate(&mut out, &[]).unwrap(); // first call, discarded
299        drbg.generate(&mut out, &[]).unwrap(); // second call → ReturnedBits
300        assert_eq!(
301            out[..],
302            hx(
303                "d3e160c35b99f340b2628264d1751060e0045da383ff57a57d73a673d2b8d80daaf6a6c35a91bb4579d73fd0c8fed111b\
304                0391306828adfed528f018121b3febdc343e797b87dbb63db1333ded9d1ece177cfa6b71fe8ab1da46624ed6415e51ccd\
305                e2c7ca86e283990eeaeb91120415528b2295910281b02dd431f4c9f70427df"
306            )[..]
307        );
308    }
309
310    // drbgvectors_no_reseed / Hash_DRBG.txt — [SHA-256], Pers=256, AddIn=256, COUNT=0.
311    #[test]
312    fn hash_sha256_no_reseed_pers_add() {
313        let entropy = hx("68c43a008fe46a823d260a9d7fa388fb9e401f0197e7e758a744b4babb3f4651");
314        let nonce = hx("eb6825777856331884aaf3751b3e4006");
315        let pers = hx("23ce0d32cbf2d26467f0d62acff1a3acbaa6d2746dc3ee7aa9d32c880788afc8");
316        let add0 = hx("a31b9f13b58d4fa2f8d8ac42b62a207ff647339a146bd8b268b33d4aff57adbd");
317        let add1 = hx("d34fc6504eca4b568193c75357b0d3821a48c77ff80d6dbd21c6cf045ff489cf");
318        let mut drbg = HashDrbg::<Sha256>::instantiate(&entropy, &nonce, &pers).unwrap();
319        let mut out = [0u8; 128];
320        drbg.generate(&mut out, &add0).unwrap();
321        drbg.generate(&mut out, &add1).unwrap();
322        assert_eq!(
323            out[..],
324            hx(
325                "abb4ecbacd4e8fa943c7221aed433861c3b203232657ec4c417d021f905d911db1058ff1e11e272232482ec96bae7cb4\
326                efc135502dbe41724077077f6de79b713670c385d04644e1281c3e582e0016255abbe5f8c06d0de57160559f0c08f7fb5\
327                be3563c649966190f8d3261364447537de2c7371c6e8c308933d27145bf90ab"
328            )[..]
329        );
330    }
331
332    // drbgvectors_pr_false / Hash_DRBG.txt — [SHA-256], Pers=0, AddIn=0, COUNT=0.
333    // PredictionResistance=False with reseed: instantiate, reseed, gen, gen.
334    #[test]
335    fn hash_sha256_pr_false_reseed_pers0_add0() {
336        let entropy = hx("63363377e41e86468deb0ab4a8ed683f6a134e47e014c700454e81e95358a569");
337        let nonce = hx("808aa38f2a72a62359915a9f8a04ca68");
338        let reseed_entropy = hx("e62b8a8ee8f141b6980566e3bfe3c04903dad4ac2cdf9f2280010a6739bc83d3");
339        let mut drbg = HashDrbg::<Sha256>::instantiate(&entropy, &nonce, &[]).unwrap();
340        drbg.reseed(&reseed_entropy, &[]).unwrap();
341        let mut out = [0u8; 128];
342        drbg.generate(&mut out, &[]).unwrap();
343        drbg.generate(&mut out, &[]).unwrap();
344        assert_eq!(
345            out[..],
346            hx(
347                "04eec63bb231df2c630a1afbe724949d005a587851e1aa795e477347c8b056621c18bddcdd8d99fc5fc2b92053d8cfacf\
348                b0bb8831205fad1ddd6c071318a6018f03b73f5ede4d4d071f9de03fd7aea105d9299b8af99aa075bdb4db9aa28c18d17\
349                4b56ee2a014d098896ff2282c955a81969e069fa8ce007a180183a07dfae17"
350            )[..]
351        );
352    }
353
354    // drbgvectors_pr_false / Hash_DRBG.txt — [SHA-256], Pers=256, AddIn=256, COUNT=0.
355    #[test]
356    fn hash_sha256_pr_false_reseed_pers_add() {
357        let entropy = hx("6c623aea73bc8a59e28c6cd9c7c7ec8ca2e75190bd5dcae5978cf0c199c23f4f");
358        let nonce = hx("e55db067a0ed537e66886b7cda02f772");
359        let pers = hx("1e59d798810083d1ff848e90b25c9927e3dfb55a0888b0339566a9f9ca7542dc");
360        let reseed_entropy = hx("9ab40164744c7d00c78b4196f6f917ec33d70030a0812cd4606c5a25387568a9");
361        let reseed_add = hx("4e8bead7cbba7a7bc9ae1e1617222c4139661347599950e7225d1e2faa5d57f5");
362        let add0 = hx("dcb22a5d9f149858636f3ede2253e419816fb7b1103194451ed6a573a8fe6271");
363        let add1 = hx("8f9d5c78cdabc32e71ac3b3c49239caddf96053250f4fd92056efbd0be487d36");
364        let mut drbg = HashDrbg::<Sha256>::instantiate(&entropy, &nonce, &pers).unwrap();
365        drbg.reseed(&reseed_entropy, &reseed_add).unwrap();
366        let mut out = [0u8; 128];
367        drbg.generate(&mut out, &add0).unwrap();
368        drbg.generate(&mut out, &add1).unwrap();
369        assert_eq!(
370            out[..],
371            hx(
372                "6e98a3b1f686f6ffa79355c9d8a5ab7f93312159d52659a2298315f10007c71adabc0b5ccb4164c0949fbdb221b43acd\
373                b62bed3099596f2d7bd5d0048173dd2360a543b234ab61a441ddb9299af84ca45c6e618fd521366dbf509d4ec06174da9\
374                24361d642b107e5564ac1b32340dd2f3158bf4c00bcb4dcf12c6d67af4b74ee"
375            )[..]
376        );
377    }
378
379    // --- Per-width coverage: SHA-384 / SHA-512 (seedlen 111 octets). ---
380    // Same DRBGVS two-generate procedure; ReturnedBits = SECOND generate.
381
382    // drbgvectors_no_reseed / Hash_DRBG.txt — [SHA-384], Pers=0, AddIn=0, COUNT=0.
383    #[test]
384    fn hash_sha384_no_reseed_pers0_add0() {
385        let entropy = hx("9ef0b00381d6c8c54d08fcadc6f5ef331134bb986373f65c6a14f553bcb6c55d");
386        let nonce = hx("9fce26ada7b1de39590312bd9d81c4f5");
387        let mut drbg = HashDrbg::<Sha384>::instantiate(&entropy, &nonce, &[]).unwrap();
388        let mut out = [0u8; 192];
389        drbg.generate(&mut out, &[]).unwrap();
390        drbg.generate(&mut out, &[]).unwrap();
391        assert_eq!(
392            out[..],
393            hx(
394                "663ffb625e62c4eb67d7177a6abb808a9f68c2d5840f19992c11ea3a635d05b537fae1f1746c1314e1a75e141c2e0941\
395                87d17b9daae1442e41d3a0d1fea94d8ef9d840111379a52e6c7ffafa7ee83b244ced129613d5b8bb089e7ea25de1c298\
396                97735cf95695043a648a2ef6fd4aa74ce8328a5550da8ddb51f98adcdc108e455603f6f18f5a50016f3e8ebcb244a16b\
397                c6b6e554a7546153c12f522c75ca5f1017e01da36650e6203f30ed5c3da3b6078736465eecb400eeaaa2c876e37564d8"
398            )[..]
399        );
400    }
401
402    // drbgvectors_no_reseed / Hash_DRBG.txt — [SHA-384], Pers=256, AddIn=256, COUNT=0.
403    #[test]
404    fn hash_sha384_no_reseed_pers_add() {
405        let entropy = hx("2e154070573312bc5ea0fa00e589b85dd559797a47c7e5ac731fea50531771a1");
406        let nonce = hx("8443504d05af55a2c5091f077b1edf22");
407        let pers = hx("35679c78e75c78bd6eff67b7d62a9fef1fad39a049debd1efb09be9acc62260d");
408        let add0 = hx("f4bf0561b3e91f0eab5a801388b04b43eee57887d43516a1f332c7c503ab53d6");
409        let add1 = hx("77371fdee3485a12e55d5029d70b9536aabbb28ac1ef9ca761633bec9058bc04");
410        let mut drbg = HashDrbg::<Sha384>::instantiate(&entropy, &nonce, &pers).unwrap();
411        let mut out = [0u8; 192];
412        drbg.generate(&mut out, &add0).unwrap();
413        drbg.generate(&mut out, &add1).unwrap();
414        assert_eq!(
415            out[..],
416            hx(
417                "c6a7ee27596c28ed32d6d0a3dc5b7255448c873b2246d2bd5dbecb1a3d32de95ecd780e3e61974a84b374a1c0b32258e\
418                f8dd41371c5af762aaec6555fd1359efa5051f24bfdbadf9be8c101198fbf6fa97ef58c1ab2865a913f1b1e08019bb25\
419                f0e17ff5f7543cc1fd35e7baa042916d12d29ac6a52202082f4224a45932772dec149a34da4b3912c30903b7681e89e1\
420                530c7d8569eef281d165ea99da653df0728c74c809dc4a0ec8b754193ccfef2ab725db8890f84b1aa19bed96ed1c6927"
421            )[..]
422        );
423    }
424
425    // drbgvectors_no_reseed / Hash_DRBG.txt — [SHA-512], Pers=0, AddIn=0, COUNT=0.
426    #[test]
427    fn hash_sha512_no_reseed_pers0_add0() {
428        let entropy = hx("6b50a7d8f8a55d7a3df8bb40bcc3b722d8708de67fda010b03c4c84d72096f8c");
429        let nonce = hx("3ec649cc6256d9fa31db7a2904aaf025");
430        let mut drbg = HashDrbg::<Sha512>::instantiate(&entropy, &nonce, &[]).unwrap();
431        let mut out = [0u8; 256];
432        drbg.generate(&mut out, &[]).unwrap();
433        drbg.generate(&mut out, &[]).unwrap();
434        assert_eq!(
435            out[..],
436            hx(
437                "95b7f17e9802d3577392c6a9c08083b67dd1292265b5f42d237f1c55bb9b10bfcfd82c77a378b8266a0099143b3c2d64\
438                611eeeb69acdc055957c139e8b190c7a06955f2c797c2778de940396a501f40e91396acf8d7e45ebdbb53bbf8c975230\
439                d2f0ff9106c76119ae498e7fbc03d90f8e4c51627aed5c8d4263d5d2b978873a0de596ee6dc7f7c29e37eee8b34c90dd\
440                1cf6a9ddb22b4cbd086b14b35de93da2d5cb1806698cbd7bbb67bfe3d31fd2d1dbd2a1e058a3eb99d7e51f1a938eed5e\
441                1c1de23a6b4345d3191409f92f39b3670d8dbfb635d8e6a36932d81033d1448d63b403ddf88e121b6e819ac381226c13\
442                21e4b08644f6727c368c5a9f7a4b3ee2"
443            )[..]
444        );
445    }
446
447    // drbgvectors_no_reseed / Hash_DRBG.txt — [SHA-512], Pers=256, AddIn=256, COUNT=0.
448    #[test]
449    fn hash_sha512_no_reseed_pers_add() {
450        let entropy = hx("31e8d6fbdc9026b0708405c20b558fcc0a107f3fdc836fe056f020df30d9dc57");
451        let nonce = hx("2b8bbab9b486abb659c4ae8ff5978e22");
452        let pers = hx("949eb753762869aa5ea0ce725523595f9bc9b219735113e71feab228d0872c38");
453        let add0 = hx("88f1180d4ef564315280a9692f107ed9c0639d79bb7040dfc3b7d58bf24ef8f5");
454        let add1 = hx("f4fc8a26e0ad181838f1399fe5b8a4b86670e92ab92b2c4daf3913470724d3f2");
455        let mut drbg = HashDrbg::<Sha512>::instantiate(&entropy, &nonce, &pers).unwrap();
456        let mut out = [0u8; 256];
457        drbg.generate(&mut out, &add0).unwrap();
458        drbg.generate(&mut out, &add1).unwrap();
459        assert_eq!(
460            out[..],
461            hx(
462                "10509641332a4d72a3c5936512c37cb9ab9874693902ee4c76e963675627ef86aa2e7d7029a152b800072fc53eeb6b41\
463                d12f481cde99b467dac3486836f6e146e9a79d3fb90d9b26f213ddbfac590ca083ed83fde4924395d25b645b96a6983e\
464                65fd662cae66112ebfa990f09b86b01270b7f0ef35f183eb01ffcbd7d5ec6adc4839cf3814dac858e013c6d79528ef27\
465                3dd83724ccdc82b73dc63698fcf8ef0924f27b6a49d6d38f0ce261aa5a0a88779e47a413c29e1d7d20e4ab914bbabd5e\
466                6e0241cf53263a8efa321b4a632eb062b255c0ce5a0833114161dd073dd037967a1f03daf2dd7e927b801b40e62f26c08\
467                72ea100132807650232126aa8f29d70"
468            )[..]
469        );
470    }
471
472    // Length-check guards (SP 800-90A §8.6.3 / §10): insufficient entropy.
473    #[test]
474    fn hash_sha256_rejects_short_entropy() {
475        let short = [0u8; 16]; // 128 bits < 256-bit security strength
476        assert_eq!(
477            HashDrbg::<Sha256>::instantiate(&short, &[0u8; 16], &[]).err(),
478            Some(Error::InsufficientEntropy)
479        );
480    }
481}