1use super::{ChaCha, hchacha20};
2use crate::{Aead, AeadError, Hash, StreamCipher, bytes::Bytes, poly1305::Poly1305};
3
4#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
12pub struct ChaCha20Poly1305 {
13 key: [u8; 32],
14}
15
16impl ChaCha20Poly1305 {
17 pub fn new(key: &[u8; 32]) -> ChaCha20Poly1305 {
19 return ChaCha20Poly1305 {
20 key: *key,
21 };
22 }
23
24 #[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#[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 let ae = XChaCha20Poly1305::new(&key);
456 let (subkey, _ietf_nonce) = ae.derive_subkey(&nonce);
457 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 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 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 #[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}