we (web engine): Experimental web browser project to understand the limits of Claude
at js-bytecode 658 lines 23 kB view raw
1//! ChaCha20-Poly1305 AEAD (RFC 8439). 2//! 3//! - ChaCha20 stream cipher: 256-bit key, 96-bit nonce, 32-bit counter 4//! - Poly1305 one-time MAC: GF(2^130 - 5) 5//! - AEAD construction: encrypt-then-MAC with padding and length encoding 6 7// --------------------------------------------------------------------------- 8// ChaCha20 quarter round (RFC 8439 §2.1) 9// --------------------------------------------------------------------------- 10 11fn quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) { 12 state[a] = state[a].wrapping_add(state[b]); 13 state[d] ^= state[a]; 14 state[d] = state[d].rotate_left(16); 15 16 state[c] = state[c].wrapping_add(state[d]); 17 state[b] ^= state[c]; 18 state[b] = state[b].rotate_left(12); 19 20 state[a] = state[a].wrapping_add(state[b]); 21 state[d] ^= state[a]; 22 state[d] = state[d].rotate_left(8); 23 24 state[c] = state[c].wrapping_add(state[d]); 25 state[b] ^= state[c]; 26 state[b] = state[b].rotate_left(7); 27} 28 29// --------------------------------------------------------------------------- 30// ChaCha20 block function (RFC 8439 §2.3) 31// --------------------------------------------------------------------------- 32 33/// The ChaCha20 constants: "expand 32-byte k" as little-endian u32s. 34const CONSTANTS: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; 35 36fn chacha20_block(key: &[u8; 32], counter: u32, nonce: &[u8; 12]) -> [u8; 64] { 37 let mut state = [0u32; 16]; 38 39 // Constants 40 state[0] = CONSTANTS[0]; 41 state[1] = CONSTANTS[1]; 42 state[2] = CONSTANTS[2]; 43 state[3] = CONSTANTS[3]; 44 45 // Key (8 words, little-endian) 46 for i in 0..8 { 47 state[4 + i] = u32::from_le_bytes(key[4 * i..4 * i + 4].try_into().unwrap()); 48 } 49 50 // Counter 51 state[12] = counter; 52 53 // Nonce (3 words, little-endian) 54 for i in 0..3 { 55 state[13 + i] = u32::from_le_bytes(nonce[4 * i..4 * i + 4].try_into().unwrap()); 56 } 57 58 let initial = state; 59 60 // 20 rounds (10 column rounds + 10 diagonal rounds) 61 for _ in 0..10 { 62 // Column rounds 63 quarter_round(&mut state, 0, 4, 8, 12); 64 quarter_round(&mut state, 1, 5, 9, 13); 65 quarter_round(&mut state, 2, 6, 10, 14); 66 quarter_round(&mut state, 3, 7, 11, 15); 67 // Diagonal rounds 68 quarter_round(&mut state, 0, 5, 10, 15); 69 quarter_round(&mut state, 1, 6, 11, 12); 70 quarter_round(&mut state, 2, 7, 8, 13); 71 quarter_round(&mut state, 3, 4, 9, 14); 72 } 73 74 // Add initial state 75 for i in 0..16 { 76 state[i] = state[i].wrapping_add(initial[i]); 77 } 78 79 // Serialize to bytes (little-endian) 80 let mut out = [0u8; 64]; 81 for i in 0..16 { 82 out[4 * i..4 * i + 4].copy_from_slice(&state[i].to_le_bytes()); 83 } 84 out 85} 86 87// --------------------------------------------------------------------------- 88// ChaCha20 encryption (RFC 8439 §2.4) 89// --------------------------------------------------------------------------- 90 91fn chacha20_encrypt(key: &[u8; 32], counter: u32, nonce: &[u8; 12], data: &[u8]) -> Vec<u8> { 92 let mut result = Vec::with_capacity(data.len()); 93 let mut block_counter = counter; 94 let mut offset = 0; 95 96 while offset < data.len() { 97 let keystream = chacha20_block(key, block_counter, nonce); 98 let remaining = (data.len() - offset).min(64); 99 for i in 0..remaining { 100 result.push(data[offset + i] ^ keystream[i]); 101 } 102 offset += remaining; 103 block_counter = block_counter.wrapping_add(1); 104 } 105 106 result 107} 108 109// --------------------------------------------------------------------------- 110// Poly1305 MAC (RFC 8439 §2.5) 111// --------------------------------------------------------------------------- 112 113/// Poly1305 one-time authenticator. 114/// 115/// Computes the MAC over `data` using the one-time key `key` (32 bytes). 116/// The key is split into `r` (clamped, first 16 bytes) and `s` (last 16 bytes). 117/// 118/// All arithmetic is in GF(2^130 - 5) using 5 limbs of 26 bits each. 119fn poly1305_mac(key: &[u8; 32], data: &[u8]) -> [u8; 16] { 120 // Clamp r per RFC 8439 §2.5 121 let mut r_bytes = [0u8; 16]; 122 r_bytes.copy_from_slice(&key[0..16]); 123 r_bytes[3] &= 15; 124 r_bytes[7] &= 15; 125 r_bytes[11] &= 15; 126 r_bytes[15] &= 15; 127 r_bytes[4] &= 252; 128 r_bytes[8] &= 252; 129 r_bytes[12] &= 252; 130 131 // r as 5 limbs of 26 bits 132 let r0 = (u32::from_le_bytes(r_bytes[0..4].try_into().unwrap())) & 0x3ffffff; 133 let r1 = (u32::from_le_bytes(r_bytes[3..7].try_into().unwrap()) >> 2) & 0x3ffffff; 134 let r2 = (u32::from_le_bytes(r_bytes[6..10].try_into().unwrap()) >> 4) & 0x3ffffff; 135 let r3 = (u32::from_le_bytes(r_bytes[9..13].try_into().unwrap()) >> 6) & 0x3ffffff; 136 let r4 = (u32::from_le_bytes(r_bytes[12..16].try_into().unwrap()) >> 8) & 0x3ffffff; 137 138 // s (second half of key) 139 let s = &key[16..32]; 140 141 // Precompute 5*r[i] for reduction 142 let s1 = r1 * 5; 143 let s2 = r2 * 5; 144 let s3 = r3 * 5; 145 let s4 = r4 * 5; 146 147 // Accumulator h = 0 148 let mut h0: u32 = 0; 149 let mut h1: u32 = 0; 150 let mut h2: u32 = 0; 151 let mut h3: u32 = 0; 152 let mut h4: u32 = 0; 153 154 // Process message in 16-byte blocks 155 let mut offset = 0; 156 while offset < data.len() { 157 let remaining = data.len() - offset; 158 let block_len = remaining.min(16); 159 160 // Read block and add padding byte 161 let mut block = [0u8; 17]; 162 block[..block_len].copy_from_slice(&data[offset..offset + block_len]); 163 block[block_len] = 1; // hibit 164 165 // Convert to 5 limbs of 26 bits 166 let t0 = u32::from_le_bytes(block[0..4].try_into().unwrap()); 167 let t1 = u32::from_le_bytes(block[4..8].try_into().unwrap()); 168 let t2 = u32::from_le_bytes(block[8..12].try_into().unwrap()); 169 let t3 = u32::from_le_bytes(block[12..16].try_into().unwrap()); 170 let hibit = block[16] as u32; 171 172 h0 = h0.wrapping_add(t0 & 0x3ffffff); 173 h1 = h1.wrapping_add(((t0 >> 26) | (t1 << 6)) & 0x3ffffff); 174 h2 = h2.wrapping_add(((t1 >> 20) | (t2 << 12)) & 0x3ffffff); 175 h3 = h3.wrapping_add(((t2 >> 14) | (t3 << 18)) & 0x3ffffff); 176 h4 = h4.wrapping_add((t3 >> 8) | (hibit << 24)); 177 178 // h *= r (mod 2^130 - 5) 179 let d0 = (h0 as u64) * (r0 as u64) 180 + (h1 as u64) * (s4 as u64) 181 + (h2 as u64) * (s3 as u64) 182 + (h3 as u64) * (s2 as u64) 183 + (h4 as u64) * (s1 as u64); 184 let d1 = (h0 as u64) * (r1 as u64) 185 + (h1 as u64) * (r0 as u64) 186 + (h2 as u64) * (s4 as u64) 187 + (h3 as u64) * (s3 as u64) 188 + (h4 as u64) * (s2 as u64); 189 let d2 = (h0 as u64) * (r2 as u64) 190 + (h1 as u64) * (r1 as u64) 191 + (h2 as u64) * (r0 as u64) 192 + (h3 as u64) * (s4 as u64) 193 + (h4 as u64) * (s3 as u64); 194 let d3 = (h0 as u64) * (r3 as u64) 195 + (h1 as u64) * (r2 as u64) 196 + (h2 as u64) * (r1 as u64) 197 + (h3 as u64) * (r0 as u64) 198 + (h4 as u64) * (s4 as u64); 199 let d4 = (h0 as u64) * (r4 as u64) 200 + (h1 as u64) * (r3 as u64) 201 + (h2 as u64) * (r2 as u64) 202 + (h3 as u64) * (r1 as u64) 203 + (h4 as u64) * (r0 as u64); 204 205 // Partial reduction mod 2^130 - 5 206 let mut c: u32; 207 c = (d0 >> 26) as u32; 208 h0 = d0 as u32 & 0x3ffffff; 209 let d1 = d1 + c as u64; 210 c = (d1 >> 26) as u32; 211 h1 = d1 as u32 & 0x3ffffff; 212 let d2 = d2 + c as u64; 213 c = (d2 >> 26) as u32; 214 h2 = d2 as u32 & 0x3ffffff; 215 let d3 = d3 + c as u64; 216 c = (d3 >> 26) as u32; 217 h3 = d3 as u32 & 0x3ffffff; 218 let d4 = d4 + c as u64; 219 c = (d4 >> 26) as u32; 220 h4 = d4 as u32 & 0x3ffffff; 221 h0 = h0.wrapping_add(c * 5); 222 c = h0 >> 26; 223 h0 &= 0x3ffffff; 224 h1 = h1.wrapping_add(c); 225 226 offset += block_len; 227 } 228 229 // Final reduction: fully carry h 230 let mut c = h1 >> 26; 231 h1 &= 0x3ffffff; 232 h2 = h2.wrapping_add(c); 233 c = h2 >> 26; 234 h2 &= 0x3ffffff; 235 h3 = h3.wrapping_add(c); 236 c = h3 >> 26; 237 h3 &= 0x3ffffff; 238 h4 = h4.wrapping_add(c); 239 c = h4 >> 26; 240 h4 &= 0x3ffffff; 241 h0 = h0.wrapping_add(c * 5); 242 c = h0 >> 26; 243 h0 &= 0x3ffffff; 244 h1 = h1.wrapping_add(c); 245 246 // Compute h + -(2^130 - 5) = h - p 247 let mut g0 = h0.wrapping_add(5); 248 c = g0 >> 26; 249 g0 &= 0x3ffffff; 250 let mut g1 = h1.wrapping_add(c); 251 c = g1 >> 26; 252 g1 &= 0x3ffffff; 253 let mut g2 = h2.wrapping_add(c); 254 c = g2 >> 26; 255 g2 &= 0x3ffffff; 256 let mut g3 = h3.wrapping_add(c); 257 c = g3 >> 26; 258 g3 &= 0x3ffffff; 259 let g4 = h4.wrapping_add(c).wrapping_sub(1 << 26); 260 261 // Select h or g based on whether g overflowed (constant-time) 262 let mask = (g4 >> 31).wrapping_sub(1); // 0xffffffff if g4 >= 0, 0 if g4 < 0 263 g0 &= mask; 264 g1 &= mask; 265 g2 &= mask; 266 g3 &= mask; 267 let g4 = g4 & mask; 268 let nmask = !mask; 269 h0 = (h0 & nmask) | g0; 270 h1 = (h1 & nmask) | g1; 271 h2 = (h2 & nmask) | g2; 272 h3 = (h3 & nmask) | g3; 273 h4 = (h4 & nmask) | g4; 274 275 // Reassemble h as a 128-bit number (mod 2^128) and add s 276 let h_val: u128 = (h0 as u128) 277 | ((h1 as u128) << 26) 278 | ((h2 as u128) << 52) 279 | ((h3 as u128) << 78) 280 | ((h4 as u128) << 104); 281 let s_val = u128::from_le_bytes(s.try_into().unwrap()); 282 let result = h_val.wrapping_add(s_val); 283 result.to_le_bytes() 284} 285 286// --------------------------------------------------------------------------- 287// Constant-time tag comparison 288// --------------------------------------------------------------------------- 289 290fn ct_eq_16(a: &[u8; 16], b: &[u8; 16]) -> bool { 291 let mut diff = 0u8; 292 for i in 0..16 { 293 diff |= a[i] ^ b[i]; 294 } 295 diff == 0 296} 297 298// --------------------------------------------------------------------------- 299// ChaCha20-Poly1305 AEAD (RFC 8439 §2.8) 300// --------------------------------------------------------------------------- 301 302/// Construct the Poly1305 input per RFC 8439 §2.8: 303/// AAD ∥ pad(AAD) ∥ ciphertext ∥ pad(ciphertext) ∥ len(AAD) ∥ len(CT) 304fn build_mac_data(aad: &[u8], ciphertext: &[u8]) -> Vec<u8> { 305 let mut mac_data = Vec::new(); 306 307 // AAD + padding to 16-byte boundary 308 mac_data.extend_from_slice(aad); 309 let pad_aad = (16 - (aad.len() % 16)) % 16; 310 mac_data.extend(std::iter::repeat_n(0u8, pad_aad)); 311 312 // Ciphertext + padding to 16-byte boundary 313 mac_data.extend_from_slice(ciphertext); 314 let pad_ct = (16 - (ciphertext.len() % 16)) % 16; 315 mac_data.extend(std::iter::repeat_n(0u8, pad_ct)); 316 317 // Lengths as 64-bit little-endian 318 mac_data.extend_from_slice(&(aad.len() as u64).to_le_bytes()); 319 mac_data.extend_from_slice(&(ciphertext.len() as u64).to_le_bytes()); 320 321 mac_data 322} 323 324// --------------------------------------------------------------------------- 325// Public API 326// --------------------------------------------------------------------------- 327 328/// ChaCha20-Poly1305 AEAD encrypt (RFC 8439). 329/// 330/// Returns `(ciphertext, 128-bit tag)`. 331pub fn chacha20_poly1305_encrypt( 332 key: &[u8; 32], 333 nonce: &[u8; 12], 334 plaintext: &[u8], 335 aad: &[u8], 336) -> (Vec<u8>, [u8; 16]) { 337 // Generate Poly1305 one-time key from first ChaCha20 block (counter = 0) 338 let otk_block = chacha20_block(key, 0, nonce); 339 let mut otk = [0u8; 32]; 340 otk.copy_from_slice(&otk_block[0..32]); 341 342 // Encrypt plaintext with ChaCha20 (counter starts at 1) 343 let ciphertext = chacha20_encrypt(key, 1, nonce, plaintext); 344 345 // Compute tag 346 let mac_data = build_mac_data(aad, &ciphertext); 347 let tag = poly1305_mac(&otk, &mac_data); 348 349 (ciphertext, tag) 350} 351 352/// ChaCha20-Poly1305 AEAD decrypt (RFC 8439). 353/// 354/// Returns `None` if the authentication tag is invalid. 355pub fn chacha20_poly1305_decrypt( 356 key: &[u8; 32], 357 nonce: &[u8; 12], 358 ciphertext: &[u8], 359 aad: &[u8], 360 tag: &[u8; 16], 361) -> Option<Vec<u8>> { 362 // Generate Poly1305 one-time key 363 let otk_block = chacha20_block(key, 0, nonce); 364 let mut otk = [0u8; 32]; 365 otk.copy_from_slice(&otk_block[0..32]); 366 367 // Verify tag before decrypting 368 let mac_data = build_mac_data(aad, ciphertext); 369 let expected_tag = poly1305_mac(&otk, &mac_data); 370 371 if !ct_eq_16(&expected_tag, tag) { 372 return None; 373 } 374 375 // Decrypt ciphertext with ChaCha20 (counter starts at 1) 376 let plaintext = chacha20_encrypt(key, 1, nonce, ciphertext); 377 Some(plaintext) 378} 379 380// --------------------------------------------------------------------------- 381// Tests 382// --------------------------------------------------------------------------- 383 384#[cfg(test)] 385mod tests { 386 use super::*; 387 388 fn from_hex(s: &str) -> Vec<u8> { 389 (0..s.len()) 390 .step_by(2) 391 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) 392 .collect() 393 } 394 395 fn hex(bytes: &[u8]) -> String { 396 bytes.iter().map(|b| format!("{b:02x}")).collect() 397 } 398 399 // ----------------------------------------------------------------------- 400 // RFC 8439 §2.1.1 — Quarter Round Test Vector 401 // ----------------------------------------------------------------------- 402 403 #[test] 404 fn quarter_round_test_vector() { 405 let mut state = [0u32; 16]; 406 state[0] = 0x11111111; 407 state[1] = 0x01020304; 408 state[2] = 0x9b8d6f43; 409 state[3] = 0x01234567; 410 // Apply quarter round to indices 0,1,2,3 (need a 16-element state) 411 quarter_round(&mut state, 0, 1, 2, 3); 412 assert_eq!(state[0], 0xea2a92f4); 413 assert_eq!(state[1], 0xcb1cf8ce); 414 assert_eq!(state[2], 0x4581472e); 415 assert_eq!(state[3], 0x5881c4bb); 416 } 417 418 // ----------------------------------------------------------------------- 419 // RFC 8439 §2.3.2 — ChaCha20 Block Function Test Vector 420 // ----------------------------------------------------------------------- 421 422 #[test] 423 fn chacha20_block_test_vector() { 424 let key: [u8; 32] = from_hex( 425 "000102030405060708090a0b0c0d0e0f\ 426 101112131415161718191a1b1c1d1e1f", 427 ) 428 .try_into() 429 .unwrap(); 430 let nonce: [u8; 12] = from_hex("000000090000004a00000000").try_into().unwrap(); 431 let counter: u32 = 1; 432 433 let block = chacha20_block(&key, counter, &nonce); 434 435 let expected = from_hex( 436 "10f1e7e4d13b5915500fdd1fa32071c4\ 437 c7d1f4c733c068030422aa9ac3d46c4e\ 438 d2826446079faa0914c2d705d98b02a2\ 439 b5129cd1de164eb9cbd083e8a2503c4e", 440 ); 441 assert_eq!(block.to_vec(), expected); 442 } 443 444 // ----------------------------------------------------------------------- 445 // RFC 8439 §2.4.2 — ChaCha20 Encryption Test Vector 446 // ----------------------------------------------------------------------- 447 448 #[test] 449 fn chacha20_encryption_test_vector() { 450 let key: [u8; 32] = from_hex( 451 "000102030405060708090a0b0c0d0e0f\ 452 101112131415161718191a1b1c1d1e1f", 453 ) 454 .try_into() 455 .unwrap(); 456 let nonce: [u8; 12] = from_hex("000000000000004a00000000").try_into().unwrap(); 457 let counter: u32 = 1; 458 459 let plaintext = b"Ladies and Gentlemen of the class of '99: \ 460If I could offer you only one tip for the future, sunscreen would be it."; 461 462 let ciphertext = chacha20_encrypt(&key, counter, &nonce, plaintext); 463 464 let expected = from_hex( 465 "6e2e359a2568f98041ba0728dd0d6981\ 466 e97e7aec1d4360c20a27afccfd9fae0b\ 467 f91b65c5524733ab8f593dabcd62b357\ 468 1639d624e65152ab8f530c359f0861d8\ 469 07ca0dbf500d6a6156a38e088a22b65e\ 470 52bc514d16ccf806818ce91ab7793736\ 471 5af90bbf74a35be6b40b8eedf2785e42\ 472 874d", 473 ); 474 assert_eq!(hex(&ciphertext), hex(&expected)); 475 } 476 477 // ----------------------------------------------------------------------- 478 // RFC 8439 §2.5.2 — Poly1305 MAC Test Vector 479 // ----------------------------------------------------------------------- 480 481 #[test] 482 fn poly1305_mac_test_vector() { 483 let key: [u8; 32] = from_hex( 484 "85d6be7857556d337f4452fe42d506a8\ 485 0103808afb0db2fd4abff6af4149f51b", 486 ) 487 .try_into() 488 .unwrap(); 489 let msg = b"Cryptographic Forum Research Group"; 490 let tag = poly1305_mac(&key, msg); 491 assert_eq!(hex(&tag), "a8061dc1305136c6c22b8baf0c0127a9"); 492 } 493 494 // ----------------------------------------------------------------------- 495 // RFC 8439 §2.6.2 — Poly1305 Key Generation Test Vector 496 // ----------------------------------------------------------------------- 497 498 #[test] 499 fn poly1305_key_generation_test_vector() { 500 let key: [u8; 32] = from_hex( 501 "808182838485868788898a8b8c8d8e8f\ 502 909192939495969798999a9b9c9d9e9f", 503 ) 504 .try_into() 505 .unwrap(); 506 let nonce: [u8; 12] = from_hex("000000000001020304050607").try_into().unwrap(); 507 508 let otk_block = chacha20_block(&key, 0, &nonce); 509 let otk = &otk_block[0..32]; 510 511 assert_eq!( 512 hex(otk), 513 "8ad5a08b905f81cc815040274ab29471\ 514 a833b637e3fd0da508dbb8e2fdd1a646" 515 ); 516 } 517 518 // ----------------------------------------------------------------------- 519 // RFC 8439 §2.8.2 — AEAD Test Vector 520 // ----------------------------------------------------------------------- 521 522 #[test] 523 fn aead_encrypt_test_vector() { 524 let key: [u8; 32] = from_hex( 525 "808182838485868788898a8b8c8d8e8f\ 526 909192939495969798999a9b9c9d9e9f", 527 ) 528 .try_into() 529 .unwrap(); 530 let nonce: [u8; 12] = from_hex("070000004041424344454647").try_into().unwrap(); 531 let aad = from_hex("50515253c0c1c2c3c4c5c6c7"); 532 let plaintext = b"Ladies and Gentlemen of the class of '99: \ 533If I could offer you only one tip for the future, sunscreen would be it."; 534 535 let (ct, tag) = chacha20_poly1305_encrypt(&key, &nonce, plaintext, &aad); 536 537 let expected_ct = from_hex( 538 "d31a8d34648e60db7b86afbc53ef7ec2\ 539 a4aded51296e08fea9e2b5a736ee62d6\ 540 3dbea45e8ca9671282fafb69da92728b\ 541 1a71de0a9e060b2905d6a5b67ecd3b36\ 542 92ddbd7f2d778b8c9803aee328091b58\ 543 fab324e4fad675945585808b4831d7bc\ 544 3ff4def08e4b7a9de576d26586cec64b\ 545 6116", 546 ); 547 assert_eq!(hex(&ct), hex(&expected_ct)); 548 assert_eq!(hex(&tag), "1ae10b594f09e26a7e902ecbd0600691"); 549 } 550 551 #[test] 552 fn aead_decrypt_test_vector() { 553 let key: [u8; 32] = from_hex( 554 "808182838485868788898a8b8c8d8e8f\ 555 909192939495969798999a9b9c9d9e9f", 556 ) 557 .try_into() 558 .unwrap(); 559 let nonce: [u8; 12] = from_hex("070000004041424344454647").try_into().unwrap(); 560 let aad = from_hex("50515253c0c1c2c3c4c5c6c7"); 561 let ct = from_hex( 562 "d31a8d34648e60db7b86afbc53ef7ec2\ 563 a4aded51296e08fea9e2b5a736ee62d6\ 564 3dbea45e8ca9671282fafb69da92728b\ 565 1a71de0a9e060b2905d6a5b67ecd3b36\ 566 92ddbd7f2d778b8c9803aee328091b58\ 567 fab324e4fad675945585808b4831d7bc\ 568 3ff4def08e4b7a9de576d26586cec64b\ 569 6116", 570 ); 571 let tag: [u8; 16] = from_hex("1ae10b594f09e26a7e902ecbd0600691") 572 .try_into() 573 .unwrap(); 574 575 let pt = chacha20_poly1305_decrypt(&key, &nonce, &ct, &aad, &tag).unwrap(); 576 assert_eq!( 577 pt, 578 b"Ladies and Gentlemen of the class of '99: \ 579If I could offer you only one tip for the future, sunscreen would be it." 580 .to_vec() 581 ); 582 } 583 584 // ----------------------------------------------------------------------- 585 // Tag tamper detection 586 // ----------------------------------------------------------------------- 587 588 #[test] 589 fn decrypt_rejects_tampered_tag() { 590 let key = [0x42u8; 32]; 591 let nonce = [0u8; 12]; 592 let (ct, mut tag) = chacha20_poly1305_encrypt(&key, &nonce, b"hello", &[]); 593 tag[0] ^= 1; 594 assert!(chacha20_poly1305_decrypt(&key, &nonce, &ct, &[], &tag).is_none()); 595 } 596 597 #[test] 598 fn decrypt_rejects_tampered_ciphertext() { 599 let key = [0x42u8; 32]; 600 let nonce = [0u8; 12]; 601 let (mut ct, tag) = chacha20_poly1305_encrypt(&key, &nonce, b"hello", &[]); 602 ct[0] ^= 1; 603 assert!(chacha20_poly1305_decrypt(&key, &nonce, &ct, &[], &tag).is_none()); 604 } 605 606 #[test] 607 fn decrypt_rejects_tampered_aad() { 608 let key = [0x42u8; 32]; 609 let nonce = [0u8; 12]; 610 let aad = b"metadata"; 611 let (ct, tag) = chacha20_poly1305_encrypt(&key, &nonce, b"hello", aad); 612 let bad_aad = b"Metadata"; 613 assert!(chacha20_poly1305_decrypt(&key, &nonce, &ct, bad_aad, &tag).is_none()); 614 } 615 616 // ----------------------------------------------------------------------- 617 // Round-trip 618 // ----------------------------------------------------------------------- 619 620 #[test] 621 fn roundtrip_empty_plaintext() { 622 let key = [0xabu8; 32]; 623 let nonce = [0x01u8; 12]; 624 let (ct, tag) = chacha20_poly1305_encrypt(&key, &nonce, &[], &[]); 625 assert!(ct.is_empty()); 626 let pt = chacha20_poly1305_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); 627 assert!(pt.is_empty()); 628 } 629 630 #[test] 631 fn roundtrip_with_aad() { 632 let key: [u8; 32] = from_hex( 633 "deadbeefdeadbeefdeadbeefdeadbeef\ 634 deadbeefdeadbeefdeadbeefdeadbeef", 635 ) 636 .try_into() 637 .unwrap(); 638 let nonce: [u8; 12] = from_hex("0102030405060708090a0b0c").try_into().unwrap(); 639 let pt = b"The quick brown fox jumps over the lazy dog"; 640 let aad = b"additional authenticated data"; 641 642 let (ct, tag) = chacha20_poly1305_encrypt(&key, &nonce, pt, aad); 643 let recovered = chacha20_poly1305_decrypt(&key, &nonce, &ct, aad, &tag).unwrap(); 644 assert_eq!(recovered, pt.to_vec()); 645 } 646 647 #[test] 648 fn roundtrip_large_message() { 649 let key = [0xffu8; 32]; 650 let nonce = [0u8; 12]; 651 let pt: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect(); 652 let aad = b"large message test"; 653 654 let (ct, tag) = chacha20_poly1305_encrypt(&key, &nonce, &pt, aad); 655 let recovered = chacha20_poly1305_decrypt(&key, &nonce, &ct, aad, &tag).unwrap(); 656 assert_eq!(recovered, pt); 657 } 658}