Skip to main content

lib_q_saturnin/
qcb.rs

1//! Saturnin-QCB authenticated encryption
2//!
3//! Saturnin-QCB is the one-pass, parallelizable AEAD proposed in "An Update on Saturnin". It is
4//! a `ΘCB`/`TAE`-style mode built on the Saturnin [tweakable block cipher](crate::tbc): each
5//! block of plaintext is encrypted by one TBC call whose tweak binds a domain separator, the
6//! nonce, and the block number; the tag is produced by encrypting a checksum of the (padded)
7//! message under a distinct domain. Because nonce + block-number give every TBC call a unique
8//! tweak (when nonces are not reused), the mode achieves rate-one encryption with a tighter
9//! quantum-security proof than Saturnin-CTR-Cascade, and every block can be processed
10//! independently (parallelized).
11//!
12//! # ⚠️ Instantiation note — not validated against designer test vectors
13//!
14//! The update note gives only a **high-level** description of Saturnin-QCB (Section 5 and
15//! Figure 1); the full mode definition lives in the separate QCB paper `[BBC+20]`, which is not
16//! bundled with this repository, and **no official QCB known-answer test vectors are
17//! published**. The construction below faithfully follows everything the update note specifies,
18//! and fills the gaps it leaves open with explicit, documented choices:
19//!
20//! - **TBC** (unambiguous, from the note): `TBC_d(K,T)(M) = Saturnin16^d_{K⊕T}(M)`. See
21//!   [`crate::tbc`].
22//! - **Domains** (from Figure 1): message blocks use domain **9**, the tag uses domain **10**.
23//!   Associated-data blocks use domain **11** (the note states AD blocks also cost 16
24//!   super-rounds but Figure 1 omits AD layout — this domain choice is ours).
25//! - **Tweak encoding** (ours): `T = N (16 bytes) ‖ 0x00·8 ‖ block_index_be_u64 (8 bytes)`, a
26//!   256-bit value. AD-block tweaks use the same layout with the nonce field zeroed, so AD
27//!   authentication is nonce-independent (OCB tradition).
28//! - **Padding** (from the note: "the message is always padded with a 01* padding, so the
29//!   ciphertext can be up to 512 bits longer than the plaintext"): `10*` padding (`0x80` then
30//!   zeros) is **always** applied, adding a whole extra block when the input is already a block
31//!   multiple. This trades a little length for a simple, unambiguous, invertible mode.
32//! - **Checksum / AD folding** (ours): `checksum = ⊕ padded_message_blocks`;
33//!   `tag = TBC_10(K, tweak(N, last)) (checksum) ⊕ ⊕_j TBC_11(K, tweak_ad(j)) (A_j)`.
34//!
35//! This module is therefore a **spec-faithful interpretation** suitable for experimentation and
36//! cross-checking, not a byte-compatible reference for an external Saturnin-QCB. It is verified
37//! by round-trip, tamper-detection, parallel-equivalence, and pinned self-consistency vectors —
38//! not by designer KATs. If/when official QCB vectors are published, pin them here.
39//!
40//! ## Usage Example
41//!
42//! ```rust
43//! # #[cfg(feature = "qcb")]
44//! # {
45//! use lib_q_saturnin::{
46//!     Aead,
47//!     AeadKey,
48//!     Nonce,
49//!     SaturninQcb,
50//! };
51//!
52//! let aead = SaturninQcb::new();
53//! let key = AeadKey::new(vec![0u8; 32]);
54//! let nonce = Nonce::new(vec![0u8; 16]);
55//!
56//! let ciphertext = aead
57//!     .encrypt(&key, &nonce, b"Secret message", Some(b"metadata"))
58//!     .unwrap();
59//! let decrypted = aead
60//!     .decrypt(&key, &nonce, &ciphertext, Some(b"metadata"))
61//!     .unwrap();
62//! assert_eq!(decrypted, b"Secret message");
63//! # }
64//! ```
65
66#[cfg(feature = "alloc")]
67use alloc::{
68    string::ToString,
69    vec::Vec,
70};
71
72use lib_q_core::{
73    Aead,
74    AeadDecryptSemantic,
75    AeadKey,
76    DecryptSemanticOutcome,
77    Error,
78    Nonce,
79    Result,
80};
81use zeroize::{
82    Zeroize,
83    Zeroizing,
84};
85
86use crate::tbc::{
87    SaturninTbc,
88    TBC_BLOCK_BYTES,
89};
90
91/// Domain separator for message blocks (Figure 1).
92const DOMAIN_MESSAGE: u8 = 9;
93/// Domain separator for the tag / checksum block (Figure 1).
94const DOMAIN_TAG: u8 = 10;
95/// Domain separator for associated-data blocks (this instantiation's choice).
96const DOMAIN_AD: u8 = 11;
97
98/// Block size in bytes (256-bit Saturnin block).
99const BLOCK: usize = TBC_BLOCK_BYTES;
100
101/// Saturnin-QCB AEAD.
102///
103/// Holds pre-built tweakable block ciphers for the three domains used by the mode so that
104/// per-message work allocates no round constants.
105pub struct SaturninQcb {
106    msg: SaturninTbc,
107    tag: SaturninTbc,
108    ad: SaturninTbc,
109}
110
111impl SaturninQcb {
112    /// Create a new Saturnin-QCB instance.
113    pub fn new() -> Self {
114        Self {
115            msg: SaturninTbc::new(DOMAIN_MESSAGE).expect("domain 9 is valid"),
116            tag: SaturninTbc::new(DOMAIN_TAG).expect("domain 10 is valid"),
117            ad: SaturninTbc::new(DOMAIN_AD).expect("domain 11 is valid"),
118        }
119    }
120
121    /// Key size in bytes (256 bits).
122    pub const fn key_size() -> usize {
123        32
124    }
125
126    /// Nonce size in bytes (128 bits).
127    pub const fn nonce_size() -> usize {
128        16
129    }
130
131    /// Tag size in bytes (256 bits).
132    pub const fn tag_size() -> usize {
133        BLOCK
134    }
135
136    /// Build the 256-bit message/tag tweak `N ‖ 0·8 ‖ block_index_be`.
137    fn tweak(nonce16: &[u8; 16], block_index: u64) -> [u8; BLOCK] {
138        let mut t = [0u8; BLOCK];
139        t[0..16].copy_from_slice(nonce16);
140        t[24..32].copy_from_slice(&block_index.to_be_bytes());
141        t
142    }
143
144    /// Build the 256-bit associated-data tweak (nonce field zeroed; AD is nonce-independent).
145    fn ad_tweak(block_index: u64) -> [u8; BLOCK] {
146        let mut t = [0u8; BLOCK];
147        t[24..32].copy_from_slice(&block_index.to_be_bytes());
148        t
149    }
150
151    /// `10*`-pad `data` to a positive multiple of [`BLOCK`], always appending at least the `0x80`
152    /// marker (a whole extra block when `data` is already a block multiple, or when empty).
153    fn pad(data: &[u8]) -> Zeroizing<Vec<u8>> {
154        let padded_len = (data.len() / BLOCK + 1) * BLOCK;
155        let mut out = Zeroizing::new(Vec::with_capacity(padded_len));
156        out.extend_from_slice(data);
157        out.push(0x80);
158        out.resize(padded_len, 0u8);
159        out
160    }
161
162    /// Authenticate associated data into a 256-bit accumulator (`0` when AD is empty).
163    fn absorb_ad(&self, key: &[u8; 32], ad: &[u8]) -> Result<Zeroizing<[u8; BLOCK]>> {
164        let mut auth = Zeroizing::new([0u8; BLOCK]);
165        if ad.is_empty() {
166            return Ok(auth);
167        }
168        let padded = Self::pad(ad);
169        for (j, chunk) in padded.chunks_exact(BLOCK).enumerate() {
170            let tweak = Self::ad_tweak(j as u64);
171            let mut block = [0u8; BLOCK];
172            block.copy_from_slice(chunk);
173            self.ad.encrypt_block(key, &tweak, &mut block)?;
174            for i in 0..BLOCK {
175                auth[i] ^= block[i];
176            }
177            block.zeroize();
178        }
179        Ok(auth)
180    }
181
182    /// Compute the 256-bit tag over the padded-message checksum and the AD accumulator.
183    fn compute_tag(
184        &self,
185        key: &[u8; 32],
186        nonce16: &[u8; 16],
187        checksum: &[u8; BLOCK],
188        last_index: u64,
189        ad_auth: &[u8; BLOCK],
190    ) -> Result<Zeroizing<[u8; BLOCK]>> {
191        let mut tag = Zeroizing::new(*checksum);
192        let tweak = Self::tweak(nonce16, last_index);
193        self.tag.encrypt_block(key, &tweak, &mut tag)?;
194        for i in 0..BLOCK {
195            tag[i] ^= ad_auth[i];
196        }
197        Ok(tag)
198    }
199
200    fn validate_lengths(key: &AeadKey, nonce: &Nonce) -> Result<()> {
201        if key.as_bytes().len() != Self::key_size() {
202            return Err(Error::InvalidKeySize {
203                expected: Self::key_size(),
204                actual: key.as_bytes().len(),
205            });
206        }
207        if nonce.as_bytes().len() != Self::nonce_size() {
208            return Err(Error::InvalidNonceSize {
209                expected: Self::nonce_size(),
210                actual: nonce.as_bytes().len(),
211            });
212        }
213        Ok(())
214    }
215
216    /// Shared decrypt core for Layer A ([`Aead::decrypt`]) and Layer B
217    /// ([`AeadDecryptSemantic::decrypt_semantic`]). Always decrypts the full ciphertext body
218    /// before the authentication outcome is allowed to influence the returned plaintext.
219    fn decrypt_core(
220        &self,
221        key: &AeadKey,
222        nonce: &Nonce,
223        ciphertext: &[u8],
224        associated_data: Option<&[u8]>,
225    ) -> Result<DecryptSemanticOutcome> {
226        Self::validate_lengths(key, nonce)?;
227
228        // Need at least one message block plus the tag.
229        if ciphertext.len() < 2 * BLOCK {
230            return Err(Error::aead_ciphertext_shorter_than_tag(
231                2 * BLOCK,
232                ciphertext.len(),
233            ));
234        }
235        // The body (ciphertext minus tag) must be block-aligned.
236        if !ciphertext.len().is_multiple_of(BLOCK) {
237            return Err(Error::InvalidCiphertextSize {
238                expected: (ciphertext.len() / BLOCK + 1) * BLOCK,
239                actual: ciphertext.len(),
240            });
241        }
242
243        let body_len = ciphertext.len() - BLOCK;
244        let body = &ciphertext[..body_len];
245        let received_tag = &ciphertext[body_len..];
246        let m = body_len / BLOCK;
247
248        let mut key_staged = Zeroizing::new([0u8; 32]);
249        key_staged.copy_from_slice(key.as_bytes());
250        let mut nonce16 = Zeroizing::new([0u8; 16]);
251        nonce16.copy_from_slice(nonce.as_bytes());
252        let ad = associated_data.unwrap_or(&[]);
253
254        // Decrypt every block and accumulate the checksum (full work, no early exit).
255        let mut plain = Zeroizing::new(Vec::with_capacity(body_len));
256        let mut checksum = Zeroizing::new([0u8; BLOCK]);
257        for (i, chunk) in body.chunks_exact(BLOCK).enumerate() {
258            let tweak = Self::tweak(&nonce16, i as u64);
259            let mut block = [0u8; BLOCK];
260            block.copy_from_slice(chunk);
261            self.msg.decrypt_block(&key_staged, &tweak, &mut block)?;
262            for k in 0..BLOCK {
263                checksum[k] ^= block[k];
264            }
265            plain.extend_from_slice(&block);
266            block.zeroize();
267        }
268
269        let ad_auth = self.absorb_ad(&key_staged, ad)?;
270        let expected_tag =
271            self.compute_tag(&key_staged, &nonce16, &checksum, (m - 1) as u64, &ad_auth)?;
272
273        let tag_valid = lib_q_core::Utils::constant_time_compare(&*expected_tag, received_tag);
274
275        if !tag_valid {
276            return Ok(DecryptSemanticOutcome::AuthenticationFailed);
277        }
278
279        // Authenticated: strip 10* padding (the final block always carries the 0x80 marker).
280        let plaintext_len = match unpad_len(&plain) {
281            Some(len) => len,
282            // Authentic ciphertext we produced always has well-formed padding; treat a malformed
283            // (e.g. truncated/forged-yet-matching) layout as an authentication failure.
284            None => return Ok(DecryptSemanticOutcome::AuthenticationFailed),
285        };
286        let mut out = Vec::with_capacity(plaintext_len);
287        out.extend_from_slice(&plain[..plaintext_len]);
288        Ok(DecryptSemanticOutcome::Success(Zeroizing::new(out)))
289    }
290}
291
292/// Locate the `10*` padding marker; returns the unpadded length, or `None` if malformed.
293fn unpad_len(padded: &[u8]) -> Option<usize> {
294    let mut idx = padded.len();
295    while idx > 0 && padded[idx - 1] == 0 {
296        idx -= 1;
297    }
298    if idx == 0 || padded[idx - 1] != 0x80 {
299        return None;
300    }
301    Some(idx - 1)
302}
303
304impl Aead for SaturninQcb {
305    fn encrypt(
306        &self,
307        key: &AeadKey,
308        nonce: &Nonce,
309        plaintext: &[u8],
310        associated_data: Option<&[u8]>,
311    ) -> Result<Vec<u8>> {
312        Self::validate_lengths(key, nonce)?;
313
314        let mut key_staged = Zeroizing::new([0u8; 32]);
315        key_staged.copy_from_slice(key.as_bytes());
316        let mut nonce16 = Zeroizing::new([0u8; 16]);
317        nonce16.copy_from_slice(nonce.as_bytes());
318        let ad = associated_data.unwrap_or(&[]);
319
320        let padded = Self::pad(plaintext);
321        let m = padded.len() / BLOCK;
322
323        let mut output = Vec::with_capacity(padded.len() + BLOCK);
324        let mut checksum = Zeroizing::new([0u8; BLOCK]);
325        for (i, chunk) in padded.chunks_exact(BLOCK).enumerate() {
326            for k in 0..BLOCK {
327                checksum[k] ^= chunk[k];
328            }
329            let tweak = Self::tweak(&nonce16, i as u64);
330            let mut block = [0u8; BLOCK];
331            block.copy_from_slice(chunk);
332            self.msg.encrypt_block(&key_staged, &tweak, &mut block)?;
333            output.extend_from_slice(&block);
334            block.zeroize();
335        }
336
337        let ad_auth = self.absorb_ad(&key_staged, ad)?;
338        let tag = self.compute_tag(&key_staged, &nonce16, &checksum, (m - 1) as u64, &ad_auth)?;
339        output.extend_from_slice(&*tag);
340        Ok(output)
341    }
342
343    fn decrypt(
344        &self,
345        key: &AeadKey,
346        nonce: &Nonce,
347        ciphertext: &[u8],
348        associated_data: Option<&[u8]>,
349    ) -> Result<Vec<u8>> {
350        match self.decrypt_core(key, nonce, ciphertext, associated_data)? {
351            DecryptSemanticOutcome::Success(p) => Ok(Vec::clone(&*p)),
352            DecryptSemanticOutcome::AuthenticationFailed => Err(Error::VerificationFailed {
353                operation: "Saturnin-QCB tag verification".to_string(),
354            }),
355        }
356    }
357}
358
359impl AeadDecryptSemantic for SaturninQcb {
360    fn decrypt_semantic(
361        &self,
362        key: &AeadKey,
363        nonce: &Nonce,
364        ciphertext: &[u8],
365        associated_data: Option<&[u8]>,
366    ) -> Result<DecryptSemanticOutcome> {
367        self.decrypt_core(key, nonce, ciphertext, associated_data)
368    }
369}
370
371impl Default for SaturninQcb {
372    fn default() -> Self {
373        Self::new()
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use alloc::vec;
380
381    use super::*;
382
383    fn key() -> AeadKey {
384        AeadKey::new((0..32u8).collect::<Vec<_>>())
385    }
386
387    fn nonce() -> Nonce {
388        Nonce::new((0..16u8).collect::<Vec<_>>())
389    }
390
391    #[test]
392    fn constants() {
393        assert_eq!(SaturninQcb::key_size(), 32);
394        assert_eq!(SaturninQcb::nonce_size(), 16);
395        assert_eq!(SaturninQcb::tag_size(), 32);
396    }
397
398    #[test]
399    fn round_trip_various_lengths() -> Result<()> {
400        let aead = SaturninQcb::new();
401        for len in [0usize, 1, 15, 31, 32, 33, 64, 100, 256] {
402            let pt: Vec<u8> = (0..len).map(|i| i as u8).collect();
403            let ct = aead.encrypt(&key(), &nonce(), &pt, Some(b"hdr"))?;
404            // Always-pad: body is padded message (multiple of 32) plus a 32-byte tag.
405            let expected_body = (len / 32 + 1) * 32;
406            assert_eq!(ct.len(), expected_body + 32, "len={len}");
407            let dec = aead.decrypt(&key(), &nonce(), &ct, Some(b"hdr"))?;
408            assert_eq!(dec, pt, "len={len}");
409        }
410        Ok(())
411    }
412
413    #[test]
414    fn empty_message_and_ad() -> Result<()> {
415        let aead = SaturninQcb::new();
416        let ct = aead.encrypt(&key(), &nonce(), b"", None)?;
417        assert_eq!(ct.len(), 64); // one padding block + tag
418        assert_eq!(aead.decrypt(&key(), &nonce(), &ct, None)?, b"");
419        Ok(())
420    }
421
422    #[test]
423    fn tampered_tag_fails() -> Result<()> {
424        let aead = SaturninQcb::new();
425        let ct = aead.encrypt(&key(), &nonce(), b"hello world", Some(b"ad"))?;
426        let mut bad = ct.clone();
427        *bad.last_mut().unwrap() ^= 0x01;
428        assert!(matches!(
429            aead.decrypt(&key(), &nonce(), &bad, Some(b"ad")),
430            Err(Error::VerificationFailed { .. })
431        ));
432        assert_eq!(
433            aead.decrypt_semantic(&key(), &nonce(), &bad, Some(b"ad"))?,
434            DecryptSemanticOutcome::AuthenticationFailed
435        );
436        Ok(())
437    }
438
439    #[test]
440    fn tampered_body_fails() -> Result<()> {
441        let aead = SaturninQcb::new();
442        let ct = aead.encrypt(&key(), &nonce(), b"hello world", None)?;
443        let mut bad = ct.clone();
444        bad[0] ^= 0x80;
445        assert!(aead.decrypt(&key(), &nonce(), &bad, None).is_err());
446        Ok(())
447    }
448
449    #[test]
450    fn ad_is_authenticated() -> Result<()> {
451        let aead = SaturninQcb::new();
452        let ct = aead.encrypt(&key(), &nonce(), b"msg", Some(b"header-A"))?;
453        // Wrong AD must fail.
454        assert!(
455            aead.decrypt(&key(), &nonce(), &ct, Some(b"header-B"))
456                .is_err()
457        );
458        // Missing AD must fail.
459        assert!(aead.decrypt(&key(), &nonce(), &ct, None).is_err());
460        Ok(())
461    }
462
463    #[test]
464    fn nonce_binding() -> Result<()> {
465        let aead = SaturninQcb::new();
466        let ct = aead.encrypt(&key(), &nonce(), b"msg", None)?;
467        let other = Nonce::new(vec![0xFFu8; 16]);
468        assert!(aead.decrypt(&key(), &other, &ct, None).is_err());
469        Ok(())
470    }
471
472    fn from_hex(s: &str) -> Vec<u8> {
473        (0..s.len())
474            .step_by(2)
475            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
476            .collect()
477    }
478
479    /// Pinned self-consistency vectors for this instantiation (key = 00..1f, nonce = 00..0f).
480    ///
481    /// These are **derived** from the construction in this module (Saturnin TBC + the documented
482    /// QCB instantiation), not official designer KATs — see the module-level instantiation note.
483    /// They lock the byte-level behavior so any accidental change to padding, tweak encoding,
484    /// domains, or AD folding is caught.
485    #[test]
486    fn pinned_kat_vectors() -> Result<()> {
487        let aead = SaturninQcb::new();
488        let cases: &[(&str, &str, &str)] = &[
489            (
490                "",
491                "",
492                "bd0abd723c4149718b458ac68f3a0a1e9e84e1e33c830a5894e48e6591a43a33718cd938614ad4c64e971ae1df9a657e290f3d862e5429088a7066642b07b29a",
493            ),
494            (
495                "",
496                "6173736f636961746564",
497                "bd0abd723c4149718b458ac68f3a0a1e9e84e1e33c830a5894e48e6591a43a33a40976d18060823323aa163b2ab7bf306cbbaff29aa86a0a31b6ba5d826c9dca",
498            ),
499            (
500                "616263",
501                "",
502                "52d715efbd6e430e4be8c2b682527e349a26fa62c69de5da978299c475f41c6df4620482177e4946c61ae01ff424a467ab76d31a63e75d045d3daaad64909edf",
503            ),
504            (
505                "0000000000000000000000000000000000000000000000000000000000000000",
506                "686472",
507                "16e51991ae3cb7cb92f3847c326188cb007267ece8153d03aeb98d4f161c84a730c8e81de51c9573d449dada58a211595a47a6f72f9776fd21347d45696e7f6743f9d93a4663c3f210ee1e99333007d9ceebd632ac2d5dacb2c9251499caddf2",
508            ),
509            (
510                "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672121",
511                "61642d31",
512                "fe81caa8f1ee16e54fd7b3df31247e7ccd4295382cff4f9f7efefb5e970c6880c10b857de55d457eff7ea96f9e4c0dc2f30180b2c037d52565e8895d48ae701ebd4ceb39dbeece08aafae995d41998ea656e1cedb4326176717a42d8b92693e4",
513            ),
514        ];
515        for (pt_hex, ad_hex, ct_hex) in cases {
516            let pt = from_hex(pt_hex);
517            let ad = from_hex(ad_hex);
518            let ad_opt = if ad.is_empty() {
519                None
520            } else {
521                Some(ad.as_slice())
522            };
523            let ct = aead.encrypt(&key(), &nonce(), &pt, ad_opt)?;
524            assert_eq!(
525                ct,
526                from_hex(ct_hex),
527                "encrypt mismatch for pt={pt_hex} ad={ad_hex}"
528            );
529            let dec = aead.decrypt(&key(), &nonce(), &ct, ad_opt)?;
530            assert_eq!(dec, pt, "decrypt mismatch for pt={pt_hex} ad={ad_hex}");
531        }
532        Ok(())
533    }
534
535    #[test]
536    fn parallel_block_independence() -> Result<()> {
537        // QCB is rate-one and embarrassingly parallel: each ciphertext block depends only on its
538        // own plaintext block, the key, the nonce, and its index. Changing one plaintext block
539        // must change only that ciphertext block (the tag aside).
540        let aead = SaturninQcb::new();
541        let mut a = vec![0u8; 96]; // 3 blocks
542        let mut b = a.clone();
543        b[40] ^= 0xFF; // flip a byte in block 1
544        let ca = aead.encrypt(&key(), &nonce(), &a, None)?;
545        let cb = aead.encrypt(&key(), &nonce(), &b, None)?;
546        // Block 0 (bytes 0..32) identical; block 1 (32..64) differs.
547        assert_eq!(ca[0..32], cb[0..32]);
548        assert_ne!(ca[32..64], cb[32..64]);
549        assert_eq!(ca[64..96], cb[64..96]); // block 2 unchanged
550        a.zeroize();
551        b.zeroize();
552        Ok(())
553    }
554
555    #[test]
556    fn unpad_len_handles_valid_and_malformed() {
557        // Valid 10* padding: marker then zeros.
558        assert_eq!(unpad_len(&[1, 2, 3, 0x80, 0, 0]), Some(3));
559        assert_eq!(unpad_len(&[0x80]), Some(0));
560        // Malformed: no marker (all zeros, or trailing non-zero that isn't 0x80).
561        assert_eq!(unpad_len(&[0, 0, 0]), None);
562        assert_eq!(unpad_len(&[]), None);
563        assert_eq!(unpad_len(&[1, 2, 3]), None);
564    }
565
566    #[test]
567    fn default_matches_new() -> Result<()> {
568        let a = SaturninQcb::default();
569        let b = SaturninQcb::new();
570        let pt = b"compare";
571        assert_eq!(
572            a.encrypt(&key(), &nonce(), pt, None)?,
573            b.encrypt(&key(), &nonce(), pt, None)?
574        );
575        Ok(())
576    }
577
578    #[test]
579    fn wrong_size_inputs_rejected() {
580        let aead = SaturninQcb::new();
581        assert!(
582            aead.encrypt(&AeadKey::new(vec![0u8; 16]), &nonce(), b"x", None)
583                .is_err()
584        );
585        assert!(
586            aead.encrypt(&key(), &Nonce::new(vec![0u8; 8]), b"x", None)
587                .is_err()
588        );
589        // Ciphertext shorter than one block + tag.
590        assert!(aead.decrypt(&key(), &nonce(), &[0u8; 40], None).is_err());
591        // Non block-aligned body.
592        assert!(aead.decrypt(&key(), &nonce(), &[0u8; 65], None).is_err());
593    }
594}