Skip to main content

crypto/chacha/
chacha20_poly1305.rs

1use super::{ChaCha, hchacha20};
2use crate::{Aead, AeadError, Hash, StreamCipher, bytes::Bytes, poly1305::Poly1305};
3
4/// ChaCha20-Poly1305 AEAD as specified in RFC 8439.
5///
6/// # Parameters
7///
8/// - Key: 256 bits (32 bytes)
9/// - Nonce: 96 bits (12 bytes)
10/// - Tag: 128 bits (16 bytes)
11#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
12pub struct ChaCha20Poly1305 {
13    key: [u8; 32],
14}
15
16impl ChaCha20Poly1305 {
17    /// Creates a new AEAD instance from a 32-byte key.
18    pub fn new(key: &[u8; 32]) -> ChaCha20Poly1305 {
19        return ChaCha20Poly1305 {
20            key: *key,
21        };
22    }
23
24    /// Generates the one-time Poly1305 key using ChaCha20 with counter=0.
25    #[inline]
26    fn poly1305_key_gen(&self, nonce: &[u8; 12]) -> ([u8; 32], ChaCha<20, true>) {
27        let mut cipher = ChaCha::<20, true>::new(&self.key, nonce);
28        cipher.set_counter(0);
29        let mut block = [0u8; 64];
30        cipher.xor_keystream(&mut block);
31        let mut key = [0u8; 32];
32        key.copy_from_slice(&block[..32]);
33        return (key, cipher);
34    }
35}
36
37impl Aead for ChaCha20Poly1305 {
38    const TAG_SIZE: usize = 16;
39    const NONCE_SIZE: usize = 12;
40
41    fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
42        let nonce: &[u8; 12] = nonce.try_into().expect("nonce must be 12 bytes");
43        let (poly1305_key, mut cipher) = self.poly1305_key_gen(nonce);
44
45        cipher.set_counter(1);
46        cipher.xor_keystream(in_out);
47
48        let mut mac = Poly1305::new(&poly1305_key);
49        update_poly1305_padded(&mut mac, aad);
50        update_poly1305_padded(&mut mac, in_out);
51        mac.update(&(aad.len() as u64).to_le_bytes());
52        mac.update(&(in_out.len() as u64).to_le_bytes());
53        let tag_bytes = mac.finalize();
54
55        let mut tag = Hash(Bytes::<64>::with_length(16));
56        tag.as_mut().copy_from_slice(&tag_bytes);
57        return tag;
58    }
59
60    fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
61        if tag.len() != Self::TAG_SIZE {
62            return Err(AeadError::InvalidCiphertext);
63        }
64        let nonce: &[u8; 12] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
65        let (poly1305_key, mut cipher) = self.poly1305_key_gen(nonce);
66
67        let mut mac = Poly1305::new(&poly1305_key);
68        update_poly1305_padded(&mut mac, aad);
69        update_poly1305_padded(&mut mac, in_out);
70        mac.update(&(aad.len() as u64).to_le_bytes());
71        mac.update(&(in_out.len() as u64).to_le_bytes());
72        let computed = mac.finalize();
73
74        if !constant_time_eq::constant_time_eq(&computed, tag) {
75            return Err(AeadError::InvalidCiphertext);
76        }
77
78        cipher.set_counter(1);
79        cipher.xor_keystream(in_out);
80
81        return Ok(());
82    }
83}
84
85/// XChaCha20-Poly1305 AEAD (draft-irtf-cfrg-xchacha-03).
86///
87/// Extends ChaCha20-Poly1305 with a 24-byte (192-bit) nonce.
88/// Internally uses HChaCha20 to derive a subkey from the first 16 nonce bytes.
89///
90/// # Parameters
91///
92/// - Key: 256 bits (32 bytes)
93/// - Nonce: 192 bits (24 bytes)
94/// - Tag: 128 bits (16 bytes)
95#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
96pub struct XChaCha20Poly1305 {
97    key: [u8; 32],
98}
99
100impl XChaCha20Poly1305 {
101    pub fn new(key: &[u8; 32]) -> XChaCha20Poly1305 {
102        return XChaCha20Poly1305 {
103            key: *key,
104        };
105    }
106
107    fn derive_subkey(&self, nonce: &[u8; 24]) -> ([u8; 32], [u8; 12]) {
108        let subkey = hchacha20(&self.key, nonce[..16].try_into().unwrap());
109        let mut ietf_nonce = [0u8; 12];
110        ietf_nonce[4..12].copy_from_slice(&nonce[16..24]);
111        return (subkey, ietf_nonce);
112    }
113}
114
115impl Aead for XChaCha20Poly1305 {
116    const TAG_SIZE: usize = 16;
117    const NONCE_SIZE: usize = 24;
118
119    fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
120        let nonce: &[u8; 24] = nonce.try_into().expect("nonce must be 24 bytes");
121        let (subkey, ietf_nonce) = self.derive_subkey(nonce);
122
123        let mut keygen = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
124        keygen.set_counter(0);
125        let mut block = [0u8; 64];
126        keygen.xor_keystream(&mut block);
127        let mut otk = [0u8; 32];
128        otk.copy_from_slice(&block[..32]);
129
130        let mut cipher = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
131        cipher.set_counter(1);
132        cipher.xor_keystream(in_out);
133
134        let mut mac = Poly1305::new(&otk);
135        update_poly1305_padded(&mut mac, aad);
136        update_poly1305_padded(&mut mac, in_out);
137        mac.update(&(aad.len() as u64).to_le_bytes());
138        mac.update(&(in_out.len() as u64).to_le_bytes());
139        let tag_bytes = mac.finalize();
140
141        let mut tag = Hash(Bytes::<64>::with_length(16));
142        tag.as_mut().copy_from_slice(&tag_bytes);
143        return tag;
144    }
145
146    fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
147        if tag.len() != Self::TAG_SIZE {
148            return Err(AeadError::InvalidCiphertext);
149        }
150        let nonce: &[u8; 24] = nonce.try_into().map_err(|_| AeadError::InvalidNonce)?;
151        let (subkey, ietf_nonce) = self.derive_subkey(nonce);
152
153        let mut keygen = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
154        keygen.set_counter(0);
155        let mut block = [0u8; 64];
156        keygen.xor_keystream(&mut block);
157        let mut otk = [0u8; 32];
158        otk.copy_from_slice(&block[..32]);
159
160        let mut mac = Poly1305::new(&otk);
161        update_poly1305_padded(&mut mac, aad);
162        update_poly1305_padded(&mut mac, in_out);
163        mac.update(&(aad.len() as u64).to_le_bytes());
164        mac.update(&(in_out.len() as u64).to_le_bytes());
165        let computed = mac.finalize();
166
167        if !constant_time_eq::constant_time_eq(&computed, tag) {
168            in_out.fill(0);
169            return Err(AeadError::InvalidCiphertext);
170        }
171
172        let mut cipher = ChaCha::<20, true>::new(&subkey, &ietf_nonce);
173        cipher.set_counter(1);
174        cipher.xor_keystream(in_out);
175
176        return Ok(());
177    }
178}
179
180#[inline]
181pub(crate) fn update_poly1305_padded(mac: &mut Poly1305, data: &[u8]) {
182    mac.update(data);
183    let rem = data.len() % 16;
184    if rem != 0 {
185        let pad = [0u8; 15];
186        mac.update(&pad[..16 - rem]);
187    }
188}
189
190#[cfg(test)]
191mod test {
192    use super::ChaCha20Poly1305;
193    use crate::Aead;
194
195    /// RFC 8439 Appendix A.4: Poly1305 Key Generation Using ChaCha20.
196    #[test]
197    fn poly1305_key_gen_vectors() {
198        struct KeyGenTest {
199            key_hex: &'static str,
200            nonce_hex: &'static str,
201            expected_otk_hex: &'static str,
202        }
203
204        let tests = [
205            KeyGenTest {
206                key_hex: "0000000000000000000000000000000000000000000000000000000000000000",
207                nonce_hex: "000000000000000000000000",
208                expected_otk_hex: "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7",
209            },
210            KeyGenTest {
211                key_hex: "0000000000000000000000000000000000000000000000000000000000000001",
212                nonce_hex: "000000000000000000000002",
213                expected_otk_hex: "ecfa254f845f647473d3cb140da9e87606cb33066c447b87bc2666dde3fbb739",
214            },
215            KeyGenTest {
216                key_hex: "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
217                nonce_hex: "000000000000000000000002",
218                expected_otk_hex: "965e3bc6f9ec7ed9560808f4d229f94b137ff275ca9b3fcbdd59deaad23310ae",
219            },
220        ];
221
222        for (i, test) in tests.iter().enumerate() {
223            let key: [u8; 32] = hex::decode(test.key_hex).unwrap().try_into().unwrap();
224            let nonce: [u8; 12] = hex::decode(test.nonce_hex).unwrap().try_into().unwrap();
225
226            let ae = ChaCha20Poly1305::new(&key);
227            let (otk, _) = ae.poly1305_key_gen(&nonce);
228            let expected_otk = hex::decode(test.expected_otk_hex).unwrap();
229
230            assert_eq!(otk.as_slice(), expected_otk.as_slice(), "key gen test [{i}] failed");
231        }
232    }
233
234    /// RFC 8439 Appendix A.5: ChaCha20-Poly1305 AEAD Decryption.
235    #[test]
236    fn aead_decrypt_test() {
237        let key: [u8; 32] = hex::decode("1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0")
238            .unwrap()
239            .try_into()
240            .unwrap();
241        let nonce: [u8; 12] = hex::decode("000000000102030405060708").unwrap().try_into().unwrap();
242        let aad = hex::decode("f33388860000000000004e91").unwrap();
243
244        let ciphertext = hex::decode(concat!(
245            "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2",
246            "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf",
247            "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855",
248            "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4",
249            "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e",
250            "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a",
251            "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10",
252            "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29",
253            "a6ad5cb4022b02709b",
254        ))
255        .unwrap();
256
257        let tag = hex::decode("eead9d67890cbb22392336fea1851f38").unwrap();
258
259        let ae = ChaCha20Poly1305::new(&key);
260
261        let mut decrypted = ciphertext.clone();
262        ae.decrypt_in_place(&mut decrypted, &nonce, &aad, &tag).unwrap();
263
264        let expected_plaintext = hex::decode(concat!(
265            "496e7465726e65742d4472616674732061726520647261667420646f63756d65",
266            "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d",
267            "6f6e74687320616e64206d617920626520757064617465642c207265706c6163",
268            "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65",
269            "6e747320617420616e792074696d652e20497420697320696e617070726f7072",
270            "6961746520746f2075736520496e7465726e65742d4472616674732061732072",
271            "65666572656e6365206d6174657269616c206f7220746f206369746520746865",
272            "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67",
273            "726573732e2fe2809d",
274        ))
275        .unwrap();
276
277        assert_eq!(decrypted, expected_plaintext);
278    }
279
280    /// Round-trip: encrypt then decrypt.
281    #[test]
282    fn aead_roundtrip() {
283        let key: [u8; 32] = [0x55; 32];
284        let nonce: [u8; 12] = [0xaa; 12];
285        let aad = b"associated data";
286        let plaintext = b"hello, world!";
287
288        let ae = ChaCha20Poly1305::new(&key);
289
290        let mut ciphertext = plaintext.to_vec();
291        let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, aad);
292
293        let mut decrypted = ciphertext.clone();
294        ae.decrypt_in_place(&mut decrypted, &nonce, aad, tag.as_ref()).unwrap();
295        assert_eq!(decrypted, plaintext);
296    }
297
298    /// Tampered tag should fail.
299    #[test]
300    fn aead_tampered_tag_fails() {
301        let key: [u8; 32] = [0x55; 32];
302        let nonce: [u8; 12] = [0xaa; 12];
303
304        let ae = ChaCha20Poly1305::new(&key);
305
306        let mut ciphertext = b"secret".to_vec();
307        let mut tag = ae.encrypt_in_place(&mut ciphertext, &nonce, b"");
308        tag.as_mut()[0] ^= 1;
309
310        let mut decrypted = ciphertext.clone();
311        let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
312        assert!(result.is_err());
313    }
314
315    /// Tampered ciphertext should fail.
316    #[test]
317    fn aead_tampered_ciphertext_fails() {
318        let key: [u8; 32] = [0x55; 32];
319        let nonce: [u8; 12] = [0xaa; 12];
320
321        let ae = ChaCha20Poly1305::new(&key);
322
323        let mut ciphertext = b"secret".to_vec();
324        let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, b"");
325
326        ciphertext[3] ^= 1;
327
328        let mut decrypted = ciphertext.clone();
329        let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
330        assert!(result.is_err());
331    }
332
333    /// Wrong nonce should fail decryption.
334    #[test]
335    fn aead_wrong_nonce_fails() {
336        let key: [u8; 32] = [0x55; 32];
337
338        let ae = ChaCha20Poly1305::new(&key);
339
340        let mut ciphertext = b"secret".to_vec();
341        let tag = ae.encrypt_in_place(&mut ciphertext, &[0u8; 12], b"");
342
343        let mut decrypted = ciphertext.clone();
344        let result = ae.decrypt_in_place(&mut decrypted, &[1u8; 12], b"", tag.as_ref());
345        assert!(result.is_err());
346    }
347
348    // --- Wycheproof test vectors ---
349
350    #[test]
351    fn wycheproof_chacha20_poly1305_vectors() {
352        let data: serde_json::Value = serde_json::from_str(include_str!(
353            "../../testdata/wycheproof/testvectors_v1/chacha20_poly1305_test.json"
354        ))
355        .unwrap();
356        let mut valid_tested = 0u64;
357        let mut invalid_tested = 0u64;
358        for group in data["testGroups"].as_array().unwrap() {
359            if group["keySize"].as_u64() != Some(256) {
360                continue;
361            }
362            if group["ivSize"].as_u64() != Some(96) {
363                continue;
364            }
365            if group["tagSize"].as_u64() != Some(128) {
366                continue;
367            }
368            for test in group["tests"].as_array().unwrap() {
369                let key_hex = test["key"].as_str().unwrap();
370                let iv_hex = test["iv"].as_str().unwrap();
371                let msg_hex = test["msg"].as_str().unwrap();
372                let aad_hex = test["aad"].as_str().unwrap();
373                let ct_hex = test["ct"].as_str().unwrap();
374                let tag_hex = test["tag"].as_str().unwrap();
375                let result = test["result"].as_str().unwrap();
376
377                let key: [u8; 32] = hex::decode(key_hex).unwrap().try_into().unwrap();
378                let nonce: [u8; 12] = hex::decode(iv_hex).unwrap().try_into().unwrap();
379                let expected_ct = hex::decode(ct_hex).unwrap();
380                let expected_tag: [u8; 16] = hex::decode(tag_hex).unwrap().try_into().unwrap();
381                let pt = hex::decode(msg_hex).unwrap();
382                let aad = hex::decode(aad_hex).unwrap();
383
384                let cipher = ChaCha20Poly1305::new(&key);
385
386                if result == "valid" {
387                    let mut buf = pt.clone();
388                    let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
389                    assert_eq!(
390                        buf, expected_ct,
391                        "wycheproof ChaCha20-Poly1305 tcId={} ct mismatch",
392                        test["tcId"]
393                    );
394                    assert_eq!(
395                        tag.as_ref(),
396                        &expected_tag[..],
397                        "wycheproof ChaCha20-Poly1305 tcId={} tag mismatch",
398                        test["tcId"]
399                    );
400
401                    let mut buf2 = expected_ct.clone();
402                    cipher
403                        .decrypt_in_place(&mut buf2, &nonce, &aad, &expected_tag)
404                        .expect("wycheproof ChaCha20-Poly1305 decrypt failed");
405                    assert_eq!(buf2, pt, "wycheproof ChaCha20-Poly1305 tcId={} pt mismatch", test["tcId"]);
406                    valid_tested += 1;
407                } else {
408                    let mut buf = expected_ct.clone();
409                    let res = cipher.decrypt_in_place(&mut buf, &nonce, &aad, &expected_tag);
410                    assert!(
411                        res.is_err(),
412                        "wycheproof ChaCha20-Poly1305 tcId={} expected invalid but passed",
413                        test["tcId"]
414                    );
415                    invalid_tested += 1;
416                }
417            }
418        }
419        assert!(valid_tested > 0, "no valid ChaCha20-Poly1305 wycheproof tests were run");
420        assert!(invalid_tested > 0, "no invalid ChaCha20-Poly1305 wycheproof tests were run");
421    }
422}
423
424#[cfg(test)]
425mod xchacha20poly1305_tests {
426    use super::XChaCha20Poly1305;
427    use crate::{Aead, StreamCipher};
428
429    /// draft-irtf-cfrg-xchacha-03, Appendix A.3.1: AEAD_XCHACHA20_POLY1305
430    #[test]
431    fn aead_xchacha20poly1305_test_vector() {
432        let key: [u8; 32] = hex::decode("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
433            .unwrap()
434            .try_into()
435            .unwrap();
436        let nonce: [u8; 24] = hex::decode("404142434445464748494a4b4c4d4e4f5051525354555657")
437            .unwrap()
438            .try_into()
439            .unwrap();
440        let aad = hex::decode("50515253c0c1c2c3c4c5c6c7").unwrap();
441
442        let plaintext = hex::decode(concat!(
443            "4c616469657320616e642047656e746c",
444            "656d656e206f662074686520636c6173",
445            "73206f66202739393a20496620492063",
446            "6f756c64206f6666657220796f75206f",
447            "6e6c79206f6e652074697020666f7220",
448            "746865206675747572652c2073756e73",
449            "637265656e20776f756c642062652069",
450            "742e",
451        ))
452        .unwrap();
453
454        // Verify Poly1305 key derivation
455        let ae = XChaCha20Poly1305::new(&key);
456        let (subkey, _ietf_nonce) = ae.derive_subkey(&nonce);
457        // The draft gives the Poly1305 key (first 32 bytes of ChaCha20 block 0)
458        let mut keygen = crate::chacha::ChaCha::<20, true>::new(&subkey, &_ietf_nonce);
459        keygen.set_counter(0);
460        let mut block = [0u8; 64];
461        keygen.xor_keystream(&mut block);
462        let expected_otk = hex::decode("7b191f80f361f099094f6f4b8fb97df847cc6873a8f2b190dd73807183f907d5").unwrap();
463        assert_eq!(&block[..32], expected_otk.as_slice(), "Poly1305 key derivation failed");
464
465        // Encrypt
466        let mut ciphertext = plaintext.clone();
467        let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, &aad);
468
469        let expected_ciphertext = hex::decode(concat!(
470            "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb",
471            "731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452",
472            "2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9",
473            "21f9664c97637da9768812f615c68b13b52e",
474        ))
475        .unwrap();
476        assert_eq!(ciphertext, expected_ciphertext, "ciphertext mismatch");
477
478        let expected_tag = hex::decode("c0875924c1c7987947deafd8780acf49").unwrap();
479        assert_eq!(tag.as_ref(), expected_tag.as_slice(), "tag mismatch");
480
481        // Decrypt
482        let mut decrypted = ciphertext.clone();
483        ae.decrypt_in_place(&mut decrypted, &nonce, &aad, tag.as_ref()).unwrap();
484        assert_eq!(decrypted, plaintext);
485    }
486
487    #[test]
488    fn aead_xchacha20poly1305_roundtrip() {
489        let key: [u8; 32] = [0x55; 32];
490        let nonce: [u8; 24] = [0xaa; 24];
491        let aad = b"associated data";
492        let plaintext = b"hello, world!";
493
494        let ae = XChaCha20Poly1305::new(&key);
495
496        let mut ciphertext = plaintext.to_vec();
497        let tag = ae.encrypt_in_place(&mut ciphertext, &nonce, aad);
498
499        let mut decrypted = ciphertext.clone();
500        ae.decrypt_in_place(&mut decrypted, &nonce, aad, tag.as_ref()).unwrap();
501        assert_eq!(decrypted, plaintext);
502    }
503
504    #[test]
505    fn aead_xchacha20poly1305_tampered_tag_fails() {
506        let key: [u8; 32] = [0x55; 32];
507        let nonce: [u8; 24] = [0xaa; 24];
508
509        let ae = XChaCha20Poly1305::new(&key);
510
511        let mut ciphertext = b"secret".to_vec();
512        let mut tag = ae.encrypt_in_place(&mut ciphertext, &nonce, b"");
513        tag.as_mut()[0] ^= 1;
514
515        let mut decrypted = ciphertext.clone();
516        let result = ae.decrypt_in_place(&mut decrypted, &nonce, b"", tag.as_ref());
517        assert!(result.is_err());
518        assert!(decrypted.iter().all(|b| *b == 0));
519    }
520
521    #[test]
522    fn aead_xchacha20poly1305_wrong_nonce_fails() {
523        let key: [u8; 32] = [0x55; 32];
524
525        let ae = XChaCha20Poly1305::new(&key);
526
527        let mut ciphertext = b"secret".to_vec();
528        let tag = ae.encrypt_in_place(&mut ciphertext, &[0u8; 24], b"");
529
530        let mut decrypted = ciphertext.clone();
531        let result = ae.decrypt_in_place(&mut decrypted, &[1u8; 24], b"", tag.as_ref());
532        assert!(result.is_err());
533    }
534
535    // --- Wycheproof test vectors ---
536
537    #[test]
538    fn wycheproof_xchacha20_poly1305_vectors() {
539        let data: serde_json::Value = serde_json::from_str(include_str!(
540            "../../testdata/wycheproof/testvectors_v1/xchacha20_poly1305_test.json"
541        ))
542        .unwrap();
543        let mut valid_tested = 0u64;
544        let mut invalid_tested = 0u64;
545        for group in data["testGroups"].as_array().unwrap() {
546            if group["keySize"].as_u64() != Some(256) {
547                continue;
548            }
549            if group["ivSize"].as_u64() != Some(192) {
550                continue;
551            }
552            if group["tagSize"].as_u64() != Some(128) {
553                continue;
554            }
555            for test in group["tests"].as_array().unwrap() {
556                let key_hex = test["key"].as_str().unwrap();
557                let iv_hex = test["iv"].as_str().unwrap();
558                let msg_hex = test["msg"].as_str().unwrap();
559                let aad_hex = test["aad"].as_str().unwrap();
560                let ct_hex = test["ct"].as_str().unwrap();
561                let tag_hex = test["tag"].as_str().unwrap();
562                let result = test["result"].as_str().unwrap();
563
564                let key: [u8; 32] = hex::decode(key_hex).unwrap().try_into().unwrap();
565                let nonce: [u8; 24] = hex::decode(iv_hex).unwrap().try_into().unwrap();
566                let expected_ct = hex::decode(ct_hex).unwrap();
567                let expected_tag: [u8; 16] = hex::decode(tag_hex).unwrap().try_into().unwrap();
568                let pt = hex::decode(msg_hex).unwrap();
569                let aad = hex::decode(aad_hex).unwrap();
570
571                let cipher = XChaCha20Poly1305::new(&key);
572
573                if result == "valid" {
574                    let mut buf = pt.clone();
575                    let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
576                    assert_eq!(
577                        buf, expected_ct,
578                        "wycheproof XChaCha20-Poly1305 tcId={} ct mismatch",
579                        test["tcId"]
580                    );
581                    assert_eq!(
582                        tag.as_ref(),
583                        &expected_tag[..],
584                        "wycheproof XChaCha20-Poly1305 tcId={} tag mismatch",
585                        test["tcId"]
586                    );
587
588                    let mut buf2 = expected_ct.clone();
589                    cipher
590                        .decrypt_in_place(&mut buf2, &nonce, &aad, &expected_tag)
591                        .expect("wycheproof XChaCha20-Poly1305 decrypt failed");
592                    assert_eq!(buf2, pt, "wycheproof XChaCha20-Poly1305 tcId={} pt mismatch", test["tcId"]);
593                    valid_tested += 1;
594                } else {
595                    let mut buf = expected_ct.clone();
596                    let res = cipher.decrypt_in_place(&mut buf, &nonce, &aad, &expected_tag);
597                    assert!(
598                        res.is_err(),
599                        "wycheproof XChaCha20-Poly1305 tcId={} expected invalid but passed",
600                        test["tcId"]
601                    );
602                    invalid_tested += 1;
603                }
604            }
605        }
606        assert!(valid_tested > 0, "no valid XChaCha20-Poly1305 wycheproof tests were run");
607        assert!(invalid_tested > 0, "no invalid XChaCha20-Poly1305 wycheproof tests were run");
608    }
609}