Skip to main content

arcana/drbg/
ctr.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4//! CTR_DRBG — SP 800-90A Rev.1 §10.2.1, over AES-128 / 192 / 256.
5//!
6//! Uses the existing [`crate::cipher::aes`] block cipher (see the T1-A
7//! cache-leak note in [`crate::drbg`]). Both operating modes of §10.2.1
8//! are provided:
9//!
10//! * **with a derivation function** ([`CtrDrbg::instantiate`], `use_df =
11//!   true`) — `Block_Cipher_df` (§10.3.2) conditions the entropy input,
12//!   so the entropy/nonce/personalization/additional-input lengths are
13//!   free (only min-entropy applies).
14//! * **without a derivation function** ([`CtrDrbg::instantiate_no_df`]) —
15//!   the entropy input must be *full-entropy* and exactly `seedlen` octets;
16//!   personalization / additional input, if present, must also be exactly
17//!   `seedlen` octets (§10.2.1.3.2 / §10.2.1.5.1).
18//!
19//! # Parameters (SP 800-90A §10.2.1, Table 3)
20//!
21//! | Cipher   | keylen | blocklen | seedlen (= keylen+blocklen) | security_strength |
22//! |----------|--------|----------|-----------------------------|-------------------|
23//! | AES-128  | 16     | 16       | 32                          | 128               |
24//! | AES-192  | 24     | 16       | 40                          | 192               |
25//! | AES-256  | 32     | 16       | 48                          | 256               |
26//!
27//! `reseed_interval = 2^48`, `max_number_of_bits_per_request = 2^19`.
28//!
29//! # Side-channel posture
30//!
31//! `Key` and `V` are secret; see [`crate::drbg`]. Every routine here —
32//! the Update, the df, the counter increment, the XOR folds — is
33//! branch-free and index-oblivious in the state. The one residual leak is
34//! the table-based AES core (roadmap `T1-A`), inherited unchanged.
35
36use crate::BlockCipher;
37use crate::cipher::aes::Aes;
38use crate::drbg::Error;
39use crate::drbg::util::ct_increment;
40
41/// `reseed_interval` (SP 800-90A §10.2.1, Table 3): 2^48.
42const RESEED_INTERVAL: u64 = 1 << 48;
43/// `max_number_of_bits_per_request` = 2^19 bits = 65 536 octets.
44const MAX_REQUEST_OCTETS: usize = 1 << 16;
45
46/// AES block length in octets.
47const BLOCKLEN: usize = 16;
48/// Largest AES key length (AES-256).
49const MAX_KEYLEN: usize = 32;
50/// Largest seedlen (AES-256 → 48 octets).
51const MAX_SEEDLEN: usize = MAX_KEYLEN + BLOCKLEN;
52/// Stack scratch for the `Block_Cipher_df` `S = L || N || input || 0x80 || pad`
53/// buffer. 512 octets comfortably holds `L(4) + N(4) + (entropy ‖ nonce ‖
54/// personalization / additional-input) + 0x80 + padding` for every
55/// SP 800-90A parameter set and every CAVP DRBGVS vector (whose df inputs
56/// top out well under this). Out-of-scratch input is rejected with
57/// [`Error::InvalidLength`] (no silent cap), not a panic.
58const DF_MAX_S: usize = 512;
59
60/// CTR_DRBG internal state (SP 800-90A §10.2.1.1): `Key` (`keylen` octets)
61/// and `V` (`blocklen` octets), plus `reseed_counter`. Both are secret.
62///
63/// `keylen` (and thus `seedlen`) is fixed at instantiation by the entropy
64/// / key width; AES width is selected by key length exactly as the
65/// existing [`Aes`] core does.
66pub struct CtrDrbg {
67    key: [u8; MAX_KEYLEN],
68    v: [u8; BLOCKLEN],
69    keylen: usize,
70    reseed_counter: u64,
71    use_df: bool,
72}
73
74/// `security_strength` in bits for a CTR_DRBG key length (SP 800-90A
75/// §10.2.1, Table 3).
76const fn security_strength(keylen: usize) -> usize {
77    keylen * 8
78}
79
80impl CtrDrbg {
81    #[inline]
82    fn seedlen(&self) -> usize {
83        self.keylen + BLOCKLEN
84    }
85
86    /// One AES-ECB block encryption under the current `Key` (§10.2.1: the
87    /// `Block_Encrypt(Key, V)` primitive). Data-oblivious apart from the
88    /// inherited AES table lookups (T1-A).
89    fn block_encrypt(key: &[u8], input: &[u8; BLOCKLEN], out: &mut [u8; BLOCKLEN]) {
90        let cipher = Aes::new(key);
91        out.copy_from_slice(input);
92        cipher.encrypt_block(out);
93    }
94
95    /// CTR_DRBG_Update (SP 800-90A §10.2.1.2).
96    ///
97    /// ```text
98    ///   temp = ""
99    ///   while len(temp) < seedlen:
100    ///       V = (V + 1) mod 2^blocklen
101    ///       output_block = Block_Encrypt(Key, V)
102    ///       temp = temp || output_block
103    ///   temp = leftmost seedlen octets of temp
104    ///   temp = temp XOR provided_data
105    ///   Key = leftmost keylen octets of temp
106    ///   V   = rightmost blocklen octets of temp
107    /// ```
108    /// `provided_data` must be exactly `seedlen` octets (the callers pad it
109    /// to `seedlen` before calling, as required by §10.2.1.3/.4/.5).
110    fn ctr_drbg_update(&mut self, provided_data: &[u8]) {
111        let seedlen = self.seedlen();
112        debug_assert_eq!(provided_data.len(), seedlen);
113        let mut temp = [0u8; MAX_SEEDLEN];
114        let mut off = 0;
115        let mut block = [0u8; BLOCKLEN];
116        while off < seedlen {
117            ct_increment(&mut self.v);
118            Self::block_encrypt(&self.key[..self.keylen], &self.v, &mut block);
119            let take = core::cmp::min(BLOCKLEN, seedlen - off);
120            temp[off..off + take].copy_from_slice(&block[..take]);
121            off += take;
122        }
123        // temp = temp XOR provided_data
124        for i in 0..seedlen {
125            temp[i] ^= provided_data[i];
126        }
127        // Key = leftmost keylen ; V = rightmost blocklen.
128        self.key[..self.keylen].copy_from_slice(&temp[..self.keylen]);
129        self.v.copy_from_slice(&temp[self.keylen..seedlen]);
130    }
131
132    /// Block_Cipher_df (SP 800-90A §10.3.2) — the CTR_DRBG derivation
133    /// function. Conditions arbitrary-length `input_parts` down to
134    /// `out.len()` octets using AES in a CBC-MAC-based BCC + CTR expansion.
135    ///
136    /// The df always uses AES-`keylen`/`out.len()`-sized state where
137    /// `keylen`/`seedlen` match the DRBG being instantiated. The fixed df
138    /// key `0x00010203…1F` (§10.3.2 step 8) is public.
139    fn block_cipher_df(keylen: usize, input_parts: &[&[u8]], out: &mut [u8]) -> Result<(), Error> {
140        let seedlen = keylen + BLOCKLEN;
141        let outlen = out.len();
142        debug_assert!(outlen <= seedlen);
143
144        // L = len(input_string) in octets ; N = number_of_bits_to_return / 8.
145        let l: u32 = input_parts.iter().map(|p| p.len() as u32).sum();
146        let n: u32 = outlen as u32;
147        // The df conditions its input into a fixed stack scratch (allocation-free,
148        // no_std). Reject out-of-scratch input with a graceful error rather than
149        // panicking on the copy below (no silent cap).
150        if 8 + l as usize + 1 + BLOCKLEN > DF_MAX_S {
151            return Err(Error::InvalidLength);
152        }
153
154        // S = L || N || input_string || 0x80, then zero-pad to a block multiple.
155        // Built into a fixed stack buffer (allocation-free, no_std-friendly):
156        // df inputs are bounded — entropy + nonce + personalization / additional
157        // input, well under DF_MAX_INPUT for every SP 800-90A parameter set and
158        // every CAVP DRBGVS vector.
159        let mut sbuf = [0u8; DF_MAX_S];
160        let mut slen = 0;
161        sbuf[slen..slen + 4].copy_from_slice(&l.to_be_bytes());
162        slen += 4;
163        sbuf[slen..slen + 4].copy_from_slice(&n.to_be_bytes());
164        slen += 4;
165        for p in input_parts {
166            sbuf[slen..slen + p.len()].copy_from_slice(p);
167            slen += p.len();
168        }
169        sbuf[slen] = 0x80;
170        slen += 1;
171        while slen % BLOCKLEN != 0 {
172            sbuf[slen] = 0x00;
173            slen += 1;
174        }
175        let s = &sbuf[..slen];
176
177        // The fixed df key: 0x00 01 02 … (keylen octets), §10.3.2 step 8.
178        let mut df_key = [0u8; MAX_KEYLEN];
179        for (i, b) in df_key.iter_mut().enumerate().take(keylen) {
180            *b = i as u8;
181        }
182        let df_key = &df_key[..keylen];
183
184        // Step 9: temp = "" ; i = 0 ; while len(temp) < keylen+blocklen (= seedlen):
185        //   IV = i (as a blocklen-octet integer, big-endian, left-justified)
186        //   temp = temp || BCC(df_key, IV || S)
187        //   i += 1
188        let mut temp = [0u8; MAX_SEEDLEN];
189        let mut i: u32 = 0;
190        let mut off = 0;
191        while off < seedlen {
192            let mut iv = [0u8; BLOCKLEN];
193            iv[..4].copy_from_slice(&i.to_be_bytes());
194            let mut chain = [0u8; BLOCKLEN];
195            Self::bcc(df_key, &iv, s, &mut chain);
196            let take = core::cmp::min(BLOCKLEN, seedlen - off);
197            temp[off..off + take].copy_from_slice(&chain[..take]);
198            off += take;
199            i += 1;
200        }
201
202        // Step 10-15: K = leftmost keylen of temp ; X = next blocklen of temp ;
203        //   then CTR-expand: temp2 = "" ; while len(temp2) < outlen:
204        //     X = Block_Encrypt(K, X) ; temp2 = temp2 || X
205        //   return leftmost outlen of temp2.
206        let k = {
207            let mut kk = [0u8; MAX_KEYLEN];
208            kk[..keylen].copy_from_slice(&temp[..keylen]);
209            kk
210        };
211        let mut x = [0u8; BLOCKLEN];
212        x.copy_from_slice(&temp[keylen..keylen + BLOCKLEN]);
213
214        let mut off = 0;
215        let mut blk = [0u8; BLOCKLEN];
216        while off < outlen {
217            Self::block_encrypt(&k[..keylen], &x, &mut blk);
218            x.copy_from_slice(&blk);
219            let take = core::cmp::min(BLOCKLEN, outlen - off);
220            out[off..off + take].copy_from_slice(&x[..take]);
221            off += take;
222        }
223        Ok(())
224    }
225
226    /// BCC (SP 800-90A §10.3.3) — the CBC-MAC over `IV || data` under `key`.
227    fn bcc(key: &[u8], iv: &[u8; BLOCKLEN], data: &[u8], out: &mut [u8; BLOCKLEN]) {
228        let mut chaining = [0u8; BLOCKLEN];
229        // Step: chaining_value = 0^blocklen ; block_0 = IV.
230        // BCC processes IV first, then each data block, CBC-chaining.
231        let mut input = [0u8; BLOCKLEN];
232        // Process IV.
233        for j in 0..BLOCKLEN {
234            input[j] = chaining[j] ^ iv[j];
235        }
236        Self::block_encrypt(key, &input, &mut chaining);
237        // Process each data block (data is a multiple of blocklen).
238        let mut off = 0;
239        while off < data.len() {
240            for j in 0..BLOCKLEN {
241                input[j] = chaining[j] ^ data[off + j];
242            }
243            Self::block_encrypt(key, &input, &mut chaining);
244            off += BLOCKLEN;
245        }
246        out.copy_from_slice(&chaining);
247    }
248
249    // ------------------------------------------------------------------
250    // Public API — with derivation function
251    // ------------------------------------------------------------------
252
253    /// Instantiate a CTR_DRBG **with** a derivation function
254    /// (SP 800-90A §10.2.1.3.2).
255    ///
256    /// `key_len` selects the AES width (16 / 24 / 32). Entropy / nonce /
257    /// personalization lengths are unconstrained beyond min-entropy
258    /// (`8·entropy_input.len() ≥ security_strength`, §8.6.3).
259    ///
260    /// ```text
261    ///   seed_material = entropy_input || nonce || personalization_string
262    ///   seed_material = Block_Cipher_df(seed_material, seedlen)
263    ///   Key = 0^keylen ; V = 0^blocklen
264    ///   (Key, V) = CTR_DRBG_Update(seed_material, Key, V)
265    ///   reseed_counter = 1
266    /// ```
267    pub fn instantiate(
268        key_len: usize,
269        entropy_input: &[u8],
270        nonce: &[u8],
271        personalization: &[u8],
272    ) -> Result<Self, Error> {
273        if !matches!(key_len, 16 | 24 | 32) {
274            return Err(Error::InvalidLength);
275        }
276        if entropy_input.len() * 8 < security_strength(key_len) {
277            return Err(Error::InsufficientEntropy);
278        }
279        let seedlen = key_len + BLOCKLEN;
280        let mut s = CtrDrbg {
281            key: [0u8; MAX_KEYLEN],
282            v: [0u8; BLOCKLEN],
283            keylen: key_len,
284            reseed_counter: 1,
285            use_df: true,
286        };
287        let mut seed_material = [0u8; MAX_SEEDLEN];
288        Self::block_cipher_df(
289            key_len,
290            &[entropy_input, nonce, personalization],
291            &mut seed_material[..seedlen],
292        )?;
293        // Key = 0, V = 0 already; then Update with the conditioned material.
294        s.ctr_drbg_update(&seed_material[..seedlen]);
295        s.reseed_counter = 1;
296        Ok(s)
297    }
298
299    /// Instantiate a CTR_DRBG **without** a derivation function
300    /// (SP 800-90A §10.2.1.3.1).
301    ///
302    /// The entropy input must be *full-entropy* and exactly `seedlen`
303    /// octets. `personalization`, if non-empty, must also be exactly
304    /// `seedlen` octets (it is `entropy XOR pers`; a shorter pers is padded
305    /// with zeros per §10.2.1.3.1 note, but CAVP supplies full-length or
306    /// empty). No nonce is used in no-df mode.
307    pub fn instantiate_no_df(key_len: usize, entropy_input: &[u8], personalization: &[u8]) -> Result<Self, Error> {
308        if !matches!(key_len, 16 | 24 | 32) {
309            return Err(Error::InvalidLength);
310        }
311        let seedlen = key_len + BLOCKLEN;
312        if entropy_input.len() != seedlen {
313            return Err(Error::InsufficientEntropy);
314        }
315        if !personalization.is_empty() && personalization.len() != seedlen {
316            return Err(Error::InvalidLength);
317        }
318        let mut s = CtrDrbg {
319            key: [0u8; MAX_KEYLEN],
320            v: [0u8; BLOCKLEN],
321            keylen: key_len,
322            reseed_counter: 1,
323            use_df: false,
324        };
325        // seed_material = entropy_input XOR (personalization padded to seedlen).
326        let mut seed_material = [0u8; MAX_SEEDLEN];
327        seed_material[..seedlen].copy_from_slice(&entropy_input[..seedlen]);
328        for i in 0..personalization.len() {
329            seed_material[i] ^= personalization[i];
330        }
331        s.ctr_drbg_update(&seed_material[..seedlen]);
332        s.reseed_counter = 1;
333        Ok(s)
334    }
335
336    /// Reseed a CTR_DRBG (SP 800-90A §10.2.1.4). Uses the df iff the
337    /// instance was instantiated with one.
338    ///
339    /// With df: `seed_material = Block_Cipher_df(entropy || additional, seedlen)`.
340    /// Without df: `seed_material = entropy XOR (additional padded)`, and both
341    /// must be exactly `seedlen` octets.
342    pub fn reseed(&mut self, entropy_input: &[u8], additional_input: &[u8]) -> Result<(), Error> {
343        let seedlen = self.seedlen();
344        let mut seed_material = [0u8; MAX_SEEDLEN];
345        if self.use_df {
346            if entropy_input.len() * 8 < security_strength(self.keylen) {
347                return Err(Error::InsufficientEntropy);
348            }
349            Self::block_cipher_df(
350                self.keylen,
351                &[entropy_input, additional_input],
352                &mut seed_material[..seedlen],
353            )?;
354        } else {
355            if entropy_input.len() != seedlen {
356                return Err(Error::InsufficientEntropy);
357            }
358            if !additional_input.is_empty() && additional_input.len() != seedlen {
359                return Err(Error::InvalidLength);
360            }
361            seed_material[..seedlen].copy_from_slice(&entropy_input[..seedlen]);
362            for i in 0..additional_input.len() {
363                seed_material[i] ^= additional_input[i];
364            }
365        }
366        self.ctr_drbg_update(&seed_material[..seedlen]);
367        self.reseed_counter = 1;
368        Ok(())
369    }
370
371    /// Generate pseudorandom bits (SP 800-90A §10.2.1.5.2).
372    ///
373    /// ```text
374    ///   if reseed_counter > reseed_interval: return "Reseed required"
375    ///   if additional_input != Null:
376    ///       if using df: additional_input = Block_Cipher_df(additional_input, seedlen)
377    ///       else        : additional_input = additional_input padded to seedlen
378    ///       (Key, V) = CTR_DRBG_Update(additional_input, Key, V)
379    ///   else additional_input = 0^seedlen
380    ///   temp = ""
381    ///   while len(temp) < requested_bits:
382    ///       V = (V + 1) mod 2^blocklen
383    ///       temp = temp || Block_Encrypt(Key, V)
384    ///   returned_bits = leftmost requested_bits of temp
385    ///   (Key, V) = CTR_DRBG_Update(additional_input, Key, V)
386    ///   reseed_counter += 1
387    /// ```
388    pub fn generate(&mut self, out: &mut [u8], additional_input: &[u8]) -> Result<(), Error> {
389        if out.len() > MAX_REQUEST_OCTETS {
390            return Err(Error::RequestTooLarge);
391        }
392        if self.reseed_counter > RESEED_INTERVAL {
393            return Err(Error::ReseedRequired);
394        }
395        let seedlen = self.seedlen();
396
397        // Step 2: condition additional_input to exactly seedlen octets.
398        let mut add = [0u8; MAX_SEEDLEN];
399        let have_add = !additional_input.is_empty();
400        if have_add {
401            if self.use_df {
402                Self::block_cipher_df(self.keylen, &[additional_input], &mut add[..seedlen])?;
403            } else {
404                if additional_input.len() != seedlen {
405                    return Err(Error::InvalidLength);
406                }
407                add[..seedlen].copy_from_slice(&additional_input[..seedlen]);
408            }
409            self.ctr_drbg_update(&add[..seedlen]);
410        }
411        // (add is already 0^seedlen when no additional_input.)
412
413        // Step 4-5: CTR expansion into `out`.
414        let mut off = 0;
415        let mut block = [0u8; BLOCKLEN];
416        while off < out.len() {
417            ct_increment(&mut self.v);
418            Self::block_encrypt(&self.key[..self.keylen], &self.v, &mut block);
419            let take = core::cmp::min(BLOCKLEN, out.len() - off);
420            out[off..off + take].copy_from_slice(&block[..take]);
421            off += take;
422        }
423
424        // Step 6: (Key, V) = CTR_DRBG_Update(additional_input, Key, V).
425        self.ctr_drbg_update(&add[..seedlen]);
426        // Step 7: reseed_counter += 1.
427        self.reseed_counter += 1;
428        Ok(())
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435    use crate::drbg::test_util::hx;
436
437    // Official NIST CAVP DRBGVS vectors (CAVS 14.3, "drbgtestvectors").
438    // Same two-generate DRBGVS procedure as the hash mechanisms.
439
440    // drbgvectors_no_reseed / CTR_DRBG.txt — [AES-256 use df], Pers=0, AddIn=0, COUNT=0.
441    #[test]
442    fn ctr_aes256_usedf_no_reseed_pers0_add0() {
443        let entropy = hx("36401940fa8b1fba91a1661f211d78a0b9389a74e5bccfece8d766af1a6d3b14");
444        let nonce = hx("496f25b0f1301b4f501be30380a137eb");
445        let mut drbg = CtrDrbg::instantiate(32, &entropy, &nonce, &[]).unwrap();
446        let mut out = [0u8; 64];
447        drbg.generate(&mut out, &[]).unwrap();
448        drbg.generate(&mut out, &[]).unwrap();
449        assert_eq!(
450            out[..],
451            hx("5862eb38bd558dd978a696e6df164782ddd887e7e9a6c9f3f1fbafb78941b535\
452                a64912dfd224c6dc7454e5250b3d97165e16260c2faf1cc7735cb75fb4f07e1d")[..]
453        );
454    }
455
456    // drbgvectors_no_reseed / CTR_DRBG.txt — [AES-256 use df], Pers=256, AddIn=256, COUNT=0.
457    #[test]
458    fn ctr_aes256_usedf_no_reseed_pers_add() {
459        let entropy = hx("87b56e964eba227154724bb9484b812d3e2c0c43b3d17f6098d9526e16e6d0ef");
460        let nonce = hx("9bea6a7ff2358df142e6c23e2157fb83");
461        let pers = hx("9860b432edd58d1ccbfeecbce99ffaee7d935a614860d4e965bd67041403096b");
462        let add0 = hx("99a5cc87924e8ea65a596f81fd17d63f5b4542fe6e8e1511b5d35c835dfadb0b");
463        let add1 = hx("9a8dec54734a34582a2332f3452e82313524c3e0dfb485faeac6ca5fc0ff504d");
464        let mut drbg = CtrDrbg::instantiate(32, &entropy, &nonce, &pers).unwrap();
465        let mut out = [0u8; 64];
466        drbg.generate(&mut out, &add0).unwrap();
467        drbg.generate(&mut out, &add1).unwrap();
468        assert_eq!(
469            out[..],
470            hx("dbc6a2330b19b5cddd8cd6392ec1fb508678c805e87d1aca07ac265007632503\
471                044a00610c79d98375afa7ab4cca1a90989cbfe7c674af5d823ced11c47e9af6")[..]
472        );
473    }
474
475    // drbgvectors_no_reseed / CTR_DRBG.txt — [AES-256 no df], Pers=0, AddIn=0, COUNT=0.
476    // No-df mode: entropy input is full-entropy and exactly seedlen (48) octets,
477    // no nonce.
478    #[test]
479    fn ctr_aes256_nodf_no_reseed_pers0_add0() {
480        let entropy = hx("df5d73faa468649edda33b5cca79b0b05600419ccb7a879ddfec9db32ee494e5\
481                          531b51de16a30f769262474c73bec010");
482        let mut drbg = CtrDrbg::instantiate_no_df(32, &entropy, &[]).unwrap();
483        let mut out = [0u8; 64];
484        drbg.generate(&mut out, &[]).unwrap();
485        drbg.generate(&mut out, &[]).unwrap();
486        assert_eq!(
487            out[..],
488            hx("d1c07cd95af8a7f11012c84ce48bb8cb87189e99d40fccb1771c619bdf82ab22\
489                80b1dc2f2581f39164f7ac0c510494b3a43c41b7db17514c87b107ae793e01c5")[..]
490        );
491    }
492
493    // drbgvectors_no_reseed / CTR_DRBG.txt — [AES-256 no df], Pers=384, AddIn=384, COUNT=0.
494    #[test]
495    fn ctr_aes256_nodf_no_reseed_pers_add() {
496        let entropy = hx("0dd4d80062ecc0f359efbe7723020be9b88b550fe74088094069e74428395856\
497                          f63eed4f5b0e7d1e006f0eaff74f638c");
498        let pers = hx("d2aa2ccd4bc6537e51f6550ab6d6294547bef3e971a7f128e4436f957de9982c\
499                       93ee22110b0e40ab33a7d3dfa22f599d");
500        let add0 = hx("0b081bab6c74d86b4a010e2ded99d14e0c9838f7c3d69afd64f1b66377d95cdc\
501                       b7f6ec5358e3516034c3339ced7e1638");
502        let add1 = hx("ca818f938ae0c7f4f507e4cfec10e7baf51fe34b89a502f754d2d2be7395120f\
503                       e1fb013c67ac2500b3d17b735da09a6e");
504        let mut drbg = CtrDrbg::instantiate_no_df(32, &entropy, &pers).unwrap();
505        let mut out = [0u8; 64];
506        drbg.generate(&mut out, &add0).unwrap();
507        drbg.generate(&mut out, &add1).unwrap();
508        assert_eq!(
509            out[..],
510            hx("6808268b13e236f642c06deba2494496e7003c937ebf6f7cb7c92104ea090f18\
511                484aa075560d7844a06eb559948c93b26ae40f2db98ecb53ad593eb4c78f82b1")[..]
512        );
513    }
514
515    // drbgvectors_pr_false / CTR_DRBG.txt — [AES-256 use df], Pers=0, AddIn=0, COUNT=0.
516    // With reseed interposed between instantiate and the first generate.
517    #[test]
518    fn ctr_aes256_usedf_pr_false_reseed() {
519        let entropy = hx("2d4c9f46b981c6a0b2b5d8c69391e569ff13851437ebc0fc00d616340252fed5");
520        let nonce = hx("0bf814b411f65ec4866be1abb59d3c32");
521        let reseed_entropy = hx("93500fae4fa32b86033b7a7bac9d37e710dcc67ca266bc8607d665937766d207");
522        let mut drbg = CtrDrbg::instantiate(32, &entropy, &nonce, &[]).unwrap();
523        drbg.reseed(&reseed_entropy, &[]).unwrap();
524        let mut out = [0u8; 64];
525        drbg.generate(&mut out, &[]).unwrap();
526        drbg.generate(&mut out, &[]).unwrap();
527        assert_eq!(
528            out[..],
529            hx("322dd28670e75c0ea638f3cb68d6a9d6e50ddfd052b772a7b1d78263a7b8978b\
530                6740c2b65a9550c3a76325866fa97e16d74006bc96f26249b9f0a90d076f08e5")[..]
531        );
532    }
533
534    // --- Per-width coverage: AES-128 / AES-192, use-df AND no-df. ---
535    // drbgvectors_no_reseed / CTR_DRBG.txt, COUNT=0; ReturnedBits = SECOND
536    // generate. no-df EntropyInput is full-entropy and exactly seedlen
537    // (AES-128 → 32 octets, AES-192 → 40 octets), no nonce.
538
539    // [AES-128 use df], Pers=0, AddIn=0, COUNT=0.
540    #[test]
541    fn ctr_aes128_usedf_no_reseed_pers0_add0() {
542        let entropy = hx("890eb067acf7382eff80b0c73bc872c6");
543        let nonce = hx("aad471ef3ef1d203");
544        let mut drbg = CtrDrbg::instantiate(16, &entropy, &nonce, &[]).unwrap();
545        let mut out = [0u8; 64];
546        drbg.generate(&mut out, &[]).unwrap();
547        drbg.generate(&mut out, &[]).unwrap();
548        assert_eq!(
549            out[..],
550            hx("a5514ed7095f64f3d0d3a5760394ab42062f373a25072a6ea6bcfd8489e94af6\
551                cf18659fea22ed1ca0a9e33f718b115ee536b12809c31b72b08ddd8be1910fa3")[..]
552        );
553    }
554
555    // [AES-128 use df], Pers=128, AddIn=128, COUNT=0.
556    #[test]
557    fn ctr_aes128_usedf_no_reseed_pers_add() {
558        let entropy = hx("cae48dd80d298103ef1ec0bf1bb96270");
559        let nonce = hx("d827f91613e0b47f");
560        let pers = hx("cc928f3d2df31a29f4e444f3df08be21");
561        let add0 = hx("7eaa1bbec79393a7f4a8227b691ecb68");
562        let add1 = hx("6869c6c7b9e6653b3977f0789e94478a");
563        let mut drbg = CtrDrbg::instantiate(16, &entropy, &nonce, &pers).unwrap();
564        let mut out = [0u8; 64];
565        drbg.generate(&mut out, &add0).unwrap();
566        drbg.generate(&mut out, &add1).unwrap();
567        assert_eq!(
568            out[..],
569            hx("920132cd284695b868b5bc4b703afea4d996624a8f57e9fbf5e793b509cb15b4\
570                beaf702dac28712d249ae75090a91fd35775294bf24ddebfd24e45d13f4a1748")[..]
571        );
572    }
573
574    // [AES-192 use df], Pers=0, AddIn=0, COUNT=0.
575    #[test]
576    fn ctr_aes192_usedf_no_reseed_pers0_add0() {
577        let entropy = hx("c35c2fa2a89d52a11fa32aa96c95b8f1c9a8f9cb245a8b40");
578        let nonce = hx("f3a6e5a7fbd9d3c68e277ba9ac9bbb00");
579        let mut drbg = CtrDrbg::instantiate(24, &entropy, &nonce, &[]).unwrap();
580        let mut out = [0u8; 64];
581        drbg.generate(&mut out, &[]).unwrap();
582        drbg.generate(&mut out, &[]).unwrap();
583        assert_eq!(
584            out[..],
585            hx("8c2e72abfd9bb8284db79e17a43a3146cd7694e35249fc3383914a7117f41368\
586                e6d4f148ff49bf29076b5015c59f457945662e3d3503843f4aa5a3df9a9df10d")[..]
587        );
588    }
589
590    // [AES-192 use df], Pers=256, AddIn=256, COUNT=0.
591    #[test]
592    fn ctr_aes192_usedf_no_reseed_pers_add() {
593        let entropy = hx("d5973b5c9105cbf67e978f419924790d83023e86a8b5dd6b");
594        let nonce = hx("358af1ae9a842c6e03f88dfa2a311161");
595        let pers = hx("294d7d35f53a5d7ddef5ca4100f3547112c93e41251257dc0a19b6dfaa4a60a4");
596        let add0 = hx("0805f31446c51d5d9d27b7cbb16e840b9e8b0dfe6fb4b69792bc8de9e3bd6d92");
597        let add1 = hx("934d7fd5e716376342607123ea113d6b20170ccda53fc86541407a156cd94904");
598        let mut drbg = CtrDrbg::instantiate(24, &entropy, &nonce, &pers).unwrap();
599        let mut out = [0u8; 64];
600        drbg.generate(&mut out, &add0).unwrap();
601        drbg.generate(&mut out, &add1).unwrap();
602        assert_eq!(
603            out[..],
604            hx("cb95459d1735cb9bce8a75bf097a099c9f7c70bad43e3e431f2d3829d7ca9d06\
605                17b9a99337af5248d4741cb5a60dff6f8c5221e23f3cb524a94ffdd2190bfb3b")[..]
606        );
607    }
608
609    // [AES-128 no df], Pers=0, AddIn=0, COUNT=0. Entropy = seedlen (32) octets.
610    #[test]
611    fn ctr_aes128_nodf_no_reseed_pers0_add0() {
612        let entropy = hx("ce50f33da5d4c1d3d4004eb35244b7f2cd7f2e5076fbf6780a7ff634b249a5fc");
613        let mut drbg = CtrDrbg::instantiate_no_df(16, &entropy, &[]).unwrap();
614        let mut out = [0u8; 64];
615        drbg.generate(&mut out, &[]).unwrap();
616        drbg.generate(&mut out, &[]).unwrap();
617        assert_eq!(
618            out[..],
619            hx("6545c0529d372443b392ceb3ae3a99a30f963eaf313280f1d1a1e87f9db373d3\
620                61e75d18018266499cccd64d9bbb8de0185f213383080faddec46bae1f784e5a")[..]
621        );
622    }
623
624    // [AES-128 no df], Pers=256, AddIn=256, COUNT=0.
625    #[test]
626    fn ctr_aes128_nodf_no_reseed_pers_add() {
627        let entropy = hx("50b96542a1f2b8b05074051fe8fb0e45adbbd5560e3594e12d485fe1bfcb741f");
628        let pers = hx("820c3030f97b3ead81a93b88b871937278fd3d711d2085d9280cba394673b17e");
629        let add0 = hx("1f1632058806d6d8e231288f3b15a3c324e90ccef4891bd595f09c3e80e27469");
630        let add1 = hx("5cadc8bfd86d2a5d44f921f64c7d153001b9bdd7caa6618639b948ebfad5cb8a");
631        let mut drbg = CtrDrbg::instantiate_no_df(16, &entropy, &pers).unwrap();
632        let mut out = [0u8; 64];
633        drbg.generate(&mut out, &add0).unwrap();
634        drbg.generate(&mut out, &add1).unwrap();
635        assert_eq!(
636            out[..],
637            hx("02b76a66f103e98d450e25e09c35337747d987471d2b3d81e03be24c7e985417\
638                a32acd72bc0a6eddd9871410dacb921c659249b4e2b368c4ac8580fb5db559bc")[..]
639        );
640    }
641
642    // [AES-192 no df], Pers=0, AddIn=0, COUNT=0. Entropy = seedlen (40) octets.
643    #[test]
644    fn ctr_aes192_nodf_no_reseed_pers0_add0() {
645        let entropy = hx("f1ef7eb311c850e189be229df7e6d68f1795aa8e21d93504e75abe78f04139587\
646                          3540386812a9a2a");
647        let mut drbg = CtrDrbg::instantiate_no_df(24, &entropy, &[]).unwrap();
648        let mut out = [0u8; 64];
649        drbg.generate(&mut out, &[]).unwrap();
650        drbg.generate(&mut out, &[]).unwrap();
651        assert_eq!(
652            out[..],
653            hx("6bb0aa5b4b97ee83765736ad0e9068dfef0ccfc93b71c1d3425302ef7ba4635f\
654                fc09981d262177e208a7ec90a557b6d76112d56c40893892c3034835036d7a69")[..]
655        );
656    }
657
658    // [AES-192 no df], Pers=320, AddIn=320, COUNT=0.
659    #[test]
660    fn ctr_aes192_nodf_no_reseed_pers_add() {
661        let entropy = hx("8db47ca284294daeb303e6459422e32577ca539c00fead8839d42946bad8eb30\
662                          e489c6d858763a28");
663        let pers = hx("c2491a6ce1ef9bd13eb66a86c0f5be3d2f3239bf7f713e830e8ae8907a2084f8\
664                       73ed3f5eddf5b569");
665        let add0 = hx("e9a3f24180345a0c0680aacdb89ce7cf841c7ad547ef92dad54f8262445e2f0c\
666                       54c8f8e42350f79c");
667        let add1 = hx("e2427b93738424c0fce14cb6c5f1d6b6a0532787157b6d907bc55d1c9a670494\
668                       776212a643a9fb2c");
669        let mut drbg = CtrDrbg::instantiate_no_df(24, &entropy, &pers).unwrap();
670        let mut out = [0u8; 64];
671        drbg.generate(&mut out, &add0).unwrap();
672        drbg.generate(&mut out, &add1).unwrap();
673        assert_eq!(
674            out[..],
675            hx("494b1957ab382b382054f54bb62f0b5b464afb3b18492c60809c26e86e45b6b9\
676                fa44524dd89ecdca99c60e68ed107ff336be1591cadd6fd7e35f742611809f2e")[..]
677        );
678    }
679
680    // [AES-128 use df], Pers=0, AddIn=0, COUNT=0, prediction-resistance
681    // FALSE with a single reseed interposed (drbgvectors_pr_false).
682    #[test]
683    fn ctr_aes128_usedf_pr_false_reseed() {
684        let entropy = hx("0f65da13dca407999d4773c2b4a11d85");
685        let nonce = hx("5209e5b4ed82a234");
686        let reseed_entropy = hx("1dea0a12c52bf64339dd291c80d8ca89");
687        let mut drbg = CtrDrbg::instantiate(16, &entropy, &nonce, &[]).unwrap();
688        drbg.reseed(&reseed_entropy, &[]).unwrap();
689        let mut out = [0u8; 64];
690        drbg.generate(&mut out, &[]).unwrap();
691        drbg.generate(&mut out, &[]).unwrap();
692        assert_eq!(
693            out[..],
694            hx("2859cc468a76b08661ffd23b28547ffd0997ad526a0f51261b99ed3a37bd407b\
695                f418dbe6c6c3e26ed0ddefcb7474d899bd99f3655427519fc5b4057bcaf306d4")[..]
696        );
697    }
698
699    #[test]
700    fn ctr_aes256_nodf_rejects_wrong_entropy_len() {
701        // no-df requires exactly seedlen = 48 octets.
702        let bad = [0u8; 32];
703        assert_eq!(
704            CtrDrbg::instantiate_no_df(32, &bad, &[]).err(),
705            Some(Error::InsufficientEntropy)
706        );
707    }
708
709    #[test]
710    fn ctr_rejects_bad_key_len() {
711        assert_eq!(
712            CtrDrbg::instantiate(20, &[0u8; 32], &[0u8; 16], &[]).err(),
713            Some(Error::InvalidLength)
714        );
715    }
716}