1use cipher::inout::InOut;
6use cipher::typenum::consts::U16;
7use cipher::{
8 Array, BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend,
9 BlockCipherEncClosure, BlockSizeUser,
10};
11use static_assertions::assert_cfg;
12use storage_ptr_slice::{MutPtrByteSlice, PtrByteSlice};
13use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
14
15assert_cfg!(target_endian = "little");
17
18#[derive(IntoBytes, KnownLayout, FromBytes, Immutable, Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(C)]
20pub struct Tweak(pub u128);
21
22impl Tweak {
23 pub fn new(val: u128) -> Self {
24 Self(val)
25 }
26
27 fn update(&mut self) {
28 self.0 = (self.0 << 1) ^ ((self.0 as i128 >> 127) as u128 & 0x87);
29 }
30}
31
32pub struct XtsProcessor<'a, 'b> {
34 tweak: Tweak,
35 src: PtrByteSlice<'a>,
36 dst: MutPtrByteSlice<'b>,
37}
38
39fn xts_encrypt_chunk<B: BlockCipherEncBackend<BlockSize = U16>>(
40 backend: &B,
41 mut val: u128,
42 tweak: &Tweak,
43) -> u128 {
44 val ^= tweak.0;
46
47 let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
48 backend.encrypt_block(InOut::from(arr));
49
50 val ^ tweak.0
52}
53
54fn xts_encrypt_buffer<B: BlockCipherEncBackend<BlockSize = U16>>(
56 backend: &B,
57 src: PtrByteSlice<'_>,
58 mut dst: MutPtrByteSlice<'_>,
59 tweak: &mut Tweak,
60) {
61 debug_assert!(src.as_ptr().cast::<u128>().is_aligned());
62 debug_assert!(dst.as_ptr().cast::<u128>().is_aligned());
63 let src_chunks = src.iter_as::<u128>();
64 let dst_chunks = dst.iter_as_mut::<u128>();
65
66 for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
67 let val = xts_encrypt_chunk(backend, src_chunk.read(), &tweak);
68 dst_chunk.write(val);
69 tweak.update();
70 }
71}
72
73fn xts_decrypt_chunk<B: BlockCipherDecBackend<BlockSize = U16>>(
74 backend: &B,
75 mut val: u128,
76 tweak: &Tweak,
77) -> u128 {
78 val ^= tweak.0;
80
81 let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
82 backend.decrypt_block(InOut::from(arr));
83
84 val ^ tweak.0
86}
87
88fn xts_decrypt_buffer<B: BlockCipherDecBackend<BlockSize = U16>>(
90 backend: &B,
91 src: PtrByteSlice<'_>,
92 mut dst: MutPtrByteSlice<'_>,
93 tweak: &mut Tweak,
94) {
95 debug_assert!(src.as_ptr().cast::<u128>().is_aligned());
96 debug_assert!(dst.as_ptr().cast::<u128>().is_aligned());
97 let src_chunks = src.iter_as::<u128>();
98 let dst_chunks = dst.iter_as_mut::<u128>();
99
100 for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
101 let val = xts_decrypt_chunk(backend, src_chunk.read(), &tweak);
102 dst_chunk.write(val);
103 tweak.update();
104 }
105}
106
107impl<'a, 'b> XtsProcessor<'a, 'b> {
108 pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, mut dst: MutPtrByteSlice<'b>) -> Self {
111 assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
112 assert!(src.as_ptr().cast::<u128>().is_aligned(), "src must be 16 byte aligned");
113 assert!(dst.as_ptr().cast::<u128>().is_aligned(), "dst must be 16 byte aligned");
114 dst.zero_no_rfo();
115 Self { tweak, src, dst }
116 }
117
118 pub fn new_in_place(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> XtsProcessor<'a, 'a> {
120 assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
121 let len = buf.len();
122 let ptr = buf.as_ptr_slice().as_ptr();
123 let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
128 XtsProcessor { tweak, src, dst: buf }
129 }
130}
131
132impl BlockSizeUser for XtsProcessor<'_, '_> {
133 type BlockSize = U16;
134}
135
136impl BlockCipherEncClosure for XtsProcessor<'_, '_> {
137 fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
138 let Self { mut tweak, src, dst } = self;
139 xts_encrypt_buffer(backend, src, dst, &mut tweak);
140 }
141}
142
143impl BlockCipherDecClosure for XtsProcessor<'_, '_> {
144 fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
145 let Self { mut tweak, src, dst } = self;
146 xts_decrypt_buffer(backend, src, dst, &mut tweak);
147 }
148}
149
150pub struct XtsCtsProcessor<'a, 'b> {
153 tweak: Tweak,
154 src: PtrByteSlice<'a>,
155 dst: MutPtrByteSlice<'b>,
156}
157
158impl BlockSizeUser for XtsCtsProcessor<'_, '_> {
159 type BlockSize = U16;
160}
161
162impl<'a, 'b> XtsCtsProcessor<'a, 'b> {
163 pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, mut dst: MutPtrByteSlice<'b>) -> Self {
166 assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
167 assert!(src.len() >= size_of::<u128>());
168 assert!(src.as_ptr().cast::<u128>().is_aligned(), "src must be 16 byte aligned");
169 assert!(dst.as_ptr().cast::<u128>().is_aligned(), "dst must be 16 byte aligned");
170 dst.zero_no_rfo();
171 Self { tweak, src, dst }
172 }
173
174 pub fn new_in_place(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> XtsCtsProcessor<'a, 'a> {
176 assert!(buf.len() >= size_of::<u128>());
177 assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
178 let len = buf.len();
179 let ptr = buf.as_ptr_slice().as_ptr();
180 let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
184 XtsCtsProcessor { tweak, src, dst: buf }
185 }
186}
187
188impl BlockCipherEncClosure for XtsCtsProcessor<'_, '_> {
189 fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
190 let Self { mut tweak, src, mut dst } = self;
191 let len = src.len();
192 let base_size = len & (!0x0F);
193
194 if len == base_size {
196 xts_encrypt_buffer(backend, src, dst, &mut tweak);
197 return;
198 }
199
200 if base_size > 16 {
202 let src_base = src.subslice(0..(base_size - 16));
203 let dst_base = dst.subslice_mut(0..(base_size - 16));
204 xts_encrypt_buffer(backend, src_base, dst_base, &mut tweak);
205 }
206
207 let last_full_block_range = (base_size - 16)..base_size;
208 let last_full_block =
209 src.subslice(last_full_block_range.clone()).read().expect("Size validated above");
210 let intermediate_ciphertext = xts_encrypt_chunk(backend, last_full_block, &tweak);
211
212 let extra = len & 0x0F;
213 let intermediate_cipher_bytes = intermediate_ciphertext.to_le_bytes();
214
215 let mut combined_block_bytes = intermediate_cipher_bytes;
219 src.subslice(base_size..len).copy_to_slice(&mut combined_block_bytes[0..extra]);
220
221 dst.subslice_mut(base_size..len).copy_from_slice(&intermediate_cipher_bytes[0..extra]);
223
224 tweak.update();
225
226 let combined_block = u128::from_le_bytes(combined_block_bytes);
227 let full_ciphertext = xts_encrypt_chunk(backend, combined_block, &tweak);
228
229 dst.subslice_mut(last_full_block_range)
231 .write(full_ciphertext)
232 .expect("Size validated above");
233 }
234}
235
236impl BlockCipherDecClosure for XtsCtsProcessor<'_, '_> {
237 fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
238 let Self { mut tweak, src, mut dst } = self;
239 let len = src.len();
240 let base_size = len & (!0x0F);
241
242 if len == base_size {
244 xts_decrypt_buffer(backend, src, dst, &mut tweak);
245 return;
246 }
247
248 if base_size > 16 {
250 let src_base = src.subslice(0..(base_size - 16));
251 let dst_base = dst.subslice_mut(0..(base_size - 16));
252 xts_decrypt_buffer(backend, src_base, dst_base, &mut tweak);
253 }
254
255 let last_full_block_tweak = tweak;
256 let last_full_block_range = (base_size - 16)..base_size;
257
258 tweak.update();
259 let extra = len & 0x0F;
260
261 let full_ciphertext =
263 src.subslice(last_full_block_range.clone()).read().expect("Size validated above");
264 let decrypted_combined_block = xts_decrypt_chunk(backend, full_ciphertext, &tweak);
265 let combined_bytes = decrypted_combined_block.to_le_bytes();
266
267 let mut intermediate_bytes = combined_bytes;
271 src.subslice(base_size..len).copy_to_slice(&mut intermediate_bytes[0..extra]);
272
273 dst.subslice_mut(base_size..len).copy_from_slice(&combined_bytes[0..extra]);
275
276 let last_block_ciphertext = u128::from_le_bytes(intermediate_bytes);
277 let val = xts_decrypt_chunk(backend, last_block_ciphertext, &last_full_block_tweak);
278 dst.subslice_mut(last_full_block_range).write(val).expect("Size validated above");
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use cipher::inout::InOut;
286 use cipher::typenum::consts::U1;
287 use cipher::{Block, ParBlocksSizeUser};
288 use std::cell::RefCell;
289 use test_case::test_case;
290
291 struct MockCipher {
292 recorded_blocks: RefCell<Vec<u128>>,
293 key: u128,
294 }
295
296 impl MockCipher {
297 fn new(key: u128) -> Self {
298 Self { recorded_blocks: RefCell::new(Vec::new()), key }
299 }
300 }
301
302 impl BlockSizeUser for MockCipher {
303 type BlockSize = U16;
304 }
305
306 impl ParBlocksSizeUser for MockCipher {
307 type ParBlocksSize = U1;
308 }
309
310 impl BlockCipherEncBackend for MockCipher {
311 fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
312 let mut val =
314 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
315 self.recorded_blocks.borrow_mut().push(val);
316 val ^= self.key;
317 unsafe {
319 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
320 }
321 }
322 }
323
324 impl BlockCipherDecBackend for MockCipher {
325 fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
326 let mut val =
328 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
329 self.recorded_blocks.borrow_mut().push(val);
330 val = val.rotate_right(3);
332 val ^= self.key;
333 unsafe {
335 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
336 }
337 }
338 }
339
340 struct MockNonLinearCipher {
343 key: u128,
344 }
345
346 impl MockNonLinearCipher {
347 fn new(key: u128) -> Self {
348 Self { key }
349 }
350 }
351
352 impl BlockSizeUser for MockNonLinearCipher {
353 type BlockSize = U16;
354 }
355
356 impl ParBlocksSizeUser for MockNonLinearCipher {
357 type ParBlocksSize = U1;
358 }
359
360 impl BlockCipherEncBackend for MockNonLinearCipher {
361 fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
362 let mut val =
363 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
364 val = val.rotate_left(11) ^ self.key;
365 unsafe {
366 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
367 }
368 }
369 }
370
371 impl BlockCipherDecBackend for MockNonLinearCipher {
372 fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
373 let mut val =
374 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
375 val = (val ^ self.key).rotate_right(11);
376 unsafe {
377 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
378 }
379 }
380 }
381
382 #[repr(C)]
383 #[derive(FromBytes, IntoBytes, Immutable)]
384 struct Blocks<const N: usize>([u128; N]);
385
386 static_assertions::const_assert!(std::mem::align_of::<Blocks<1>>() == 16);
387 static_assertions::const_assert!(std::mem::align_of::<Blocks<2>>() == 16);
388
389 impl<const N: usize> Default for Blocks<N> {
390 fn default() -> Self {
391 Self([0u128; N])
392 }
393 }
394
395 #[test]
396 fn test_xts_out_of_place() {
397 let mut plaintext: Blocks<2> = Default::default();
398 for (i, x) in plaintext.as_mut_bytes().iter_mut().enumerate() {
399 *x = i as u8;
400 }
401 let mut ciphertext: Blocks<2> = Default::default();
402
403 let src = PtrByteSlice::from(plaintext.as_bytes());
404 let dst = MutPtrByteSlice::from(ciphertext.as_mut_bytes());
405
406 let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
407 let tweak = Tweak::new(tweak_val);
408 let key = 0xffeeddccbbaa99887766554433221100u128;
409
410 let processor = XtsProcessor::new(tweak, src, dst);
411 let cipher = MockCipher::new(key);
412
413 BlockCipherEncClosure::call(processor, &cipher);
414
415 let expected_c0 =
419 u128::from_le_bytes(plaintext.as_bytes()[0..16].try_into().unwrap()) ^ key;
420 let expected_c1 =
421 u128::from_le_bytes(plaintext.as_bytes()[16..32].try_into().unwrap()) ^ key;
422
423 let actual_c0 = u128::from_le_bytes(ciphertext.as_bytes()[0..16].try_into().unwrap());
424 let actual_c1 = u128::from_le_bytes(ciphertext.as_bytes()[16..32].try_into().unwrap());
425
426 assert_eq!(actual_c0, expected_c0);
427 assert_eq!(actual_c1, expected_c1);
428
429 assert_eq!(cipher.recorded_blocks.borrow().len(), 2);
431
432 let p0 = u128::from_le_bytes(plaintext.as_bytes()[0..16].try_into().unwrap());
433 let p1 = u128::from_le_bytes(plaintext.as_bytes()[16..32].try_into().unwrap());
434
435 let mut t0 = tweak;
436 assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
437 t0.update();
438 assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
439 }
440
441 #[test]
442 fn test_xts_in_place() {
443 let mut buf: Blocks<2> = Default::default();
444 for (i, x) in buf.as_mut_bytes().iter_mut().enumerate() {
445 *x = i as u8;
446 }
447
448 let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
449 let tweak = Tweak::new(tweak_val);
450 let key = 0xffeeddccbbaa99887766554433221100u128;
451
452 let p0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
454 let p1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
455
456 let slice = MutPtrByteSlice::from(buf.as_mut_bytes());
457 let processor = XtsProcessor::new_in_place(tweak, slice);
458 let cipher = MockCipher::new(key);
459
460 BlockCipherEncClosure::call(processor, &cipher);
461
462 let expected_c0 = p0 ^ key;
464 let expected_c1 = p1 ^ key;
465
466 let actual_c0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
467 let actual_c1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
468
469 assert_eq!(actual_c0, expected_c0);
470 assert_eq!(actual_c1, expected_c1);
471
472 assert_eq!(cipher.recorded_blocks.borrow().len(), 2);
474 let mut t0 = tweak;
475 assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
476 t0.update();
477 assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
478 }
479
480 #[test_case(16; "exact_block")]
481 #[test_case(17; "one_byte_more_than_block")]
482 #[test_case(31; "one_byte_less_than_two_blocks")]
483 #[test_case(32; "exact_two_blocks")]
484 #[test_case(80; "bunch_of_blocks_exact")]
485 #[test_case(87; "bunch_of_blocks_cts")]
486 fn test_cts_encrypt_decrypt(len: usize) {
487 let tweak = Tweak::new(0x123456789abcdef0123456789abcdef0);
488 let key = 0xffeeddccbbaa99887766554433221100;
489
490 let mut plaintext_vec = vec![0u128; (len + 15) / 16];
491 let plaintext_bytes = plaintext_vec.as_mut_bytes();
492 for (i, b) in plaintext_bytes[..len].iter_mut().enumerate() {
493 *b = ((i % 255) + 1) as u8;
494 }
495 let plaintext = &plaintext_bytes[..len];
496
497 let mut ciphertext_vec = vec![0u128; (len + 15) / 16];
498 let ciphertext_bytes = ciphertext_vec.as_mut_bytes();
499
500 let mut decrypted_vec = vec![0u128; (len + 15) / 16];
501 let decrypted_bytes = decrypted_vec.as_mut_bytes();
502
503 let cipher = MockNonLinearCipher::new(key);
504
505 {
507 let src = PtrByteSlice::from(plaintext);
508 let dst = MutPtrByteSlice::from(&mut ciphertext_bytes[..len]);
509 let processor = XtsCtsProcessor::new(tweak, src, dst);
510 BlockCipherEncClosure::call(processor, &cipher);
511 }
512
513 let ciphertext = &ciphertext_bytes[..len];
514
515 assert_ne!(ciphertext, plaintext, "Ciphertext should not match plaintext");
517 for chunk_start in (0..len).step_by(16) {
518 let chunk_end = (chunk_start + 16).min(len);
519 assert_ne!(
520 &ciphertext[chunk_start..chunk_end],
521 &plaintext[chunk_start..chunk_end],
522 "Chunk at {chunk_start}..{chunk_end} should not match plaintext"
523 );
524 }
525
526 {
528 let src = PtrByteSlice::from(ciphertext);
529 let dst = MutPtrByteSlice::from(&mut decrypted_bytes[..len]);
530 let processor = XtsCtsProcessor::new(tweak, src, dst);
531 BlockCipherDecClosure::call(processor, &cipher);
532 }
533
534 let decrypted = &decrypted_bytes[..len];
535
536 assert_eq!(decrypted, plaintext, "Decrypted text should match original plaintext");
538 }
539
540 #[test]
541 fn test_cts_encrypt_decrypt_in_place() {
542 const LEN: usize = 87;
543
544 let tweak = Tweak::new(0x123456789abcdef0123456789abcdef0);
545 let key = 0xffeeddccbbaa99887766554433221100;
546
547 let mut buf_vec = vec![0u128; (LEN + 15) / 16];
548 let buf_bytes = buf_vec.as_mut_bytes();
549 for (i, b) in buf_bytes[..LEN].iter_mut().enumerate() {
550 *b = ((i % 255) + 1) as u8;
551 }
552 let original_plaintext = buf_bytes[..LEN].to_vec();
553
554 let cipher = MockNonLinearCipher::new(key);
555
556 {
558 let slice = MutPtrByteSlice::from(&mut buf_bytes[..LEN]);
559 let processor = XtsCtsProcessor::new_in_place(tweak, slice);
560 BlockCipherEncClosure::call(processor, &cipher);
561 }
562
563 assert_ne!(
565 &buf_bytes[..LEN],
566 &original_plaintext[..],
567 "In-place ciphertext should not match plaintext"
568 );
569 for chunk_start in (0..LEN).step_by(16) {
570 let chunk_end = (chunk_start + 16).min(LEN);
571 assert_ne!(
572 &buf_bytes[chunk_start..chunk_end],
573 &original_plaintext[chunk_start..chunk_end],
574 "Chunk at {chunk_start}..{chunk_end} should not match plaintext"
575 );
576 }
577
578 {
580 let slice = MutPtrByteSlice::from(&mut buf_bytes[..LEN]);
581 let processor = XtsCtsProcessor::new_in_place(tweak, slice);
582 BlockCipherDecClosure::call(processor, &cipher);
583 }
584
585 assert_eq!(
587 &buf_bytes[..LEN],
588 &original_plaintext[..],
589 "Decrypted in-place text should match original plaintext"
590 );
591 }
592
593 #[test_case(1; "one_block")]
594 #[test_case(2; "two_blocks")]
595 #[test_case(5; "five_blocks")]
596 fn test_cts_matches_normal_xts_on_exact_blocks(num_blocks: usize) {
597 let tweak = Tweak::new(0x9876543210abcdef9876543210abcdef);
598 let key = 0x0123456789abcdef0123456789abcdef;
599
600 let len = num_blocks * 16;
601 let mut plaintext_vec = vec![0u128; num_blocks];
602 let plaintext_bytes = plaintext_vec.as_mut_bytes();
603 for (i, b) in plaintext_bytes[..len].iter_mut().enumerate() {
604 *b = (i as u8).wrapping_mul(17).wrapping_add(3);
605 }
606 let plaintext = &plaintext_bytes[..len];
607
608 let mut normal_cts_vec = vec![0u128; num_blocks];
609 let mut cts_vec = vec![0u128; num_blocks];
610
611 let cipher = MockNonLinearCipher::new(key);
612
613 {
615 let src = PtrByteSlice::from(plaintext);
616 let dst = MutPtrByteSlice::from(&mut normal_cts_vec.as_mut_bytes()[..len]);
617 let processor = XtsProcessor::new(tweak, src, dst);
618 BlockCipherEncClosure::call(processor, &cipher);
619 }
620
621 {
623 let src = PtrByteSlice::from(plaintext);
624 let dst = MutPtrByteSlice::from(&mut cts_vec.as_mut_bytes()[..len]);
625 let processor = XtsCtsProcessor::new(tweak, src, dst);
626 BlockCipherEncClosure::call(processor, &cipher);
627 }
628
629 assert_eq!(
630 normal_cts_vec, cts_vec,
631 "CTS XTS should match normal XTS for {num_blocks} blocks"
632 );
633 }
634
635 #[test]
636 fn test_cts_different_tweaks_different_ciphertext() {
637 const LEN: usize = 87;
638
639 let tweak1 = Tweak::new(0x123456789abcdef0123456789abcdef0);
640 let tweak2 = Tweak::new(0xfeef876543210fedcba9876543210fed);
641 let key = 0xffeeddccbbaa99887766554433221100;
642
643 let mut plaintext_vec = vec![0u128; (LEN + 15) / 16];
644 let plaintext_bytes = plaintext_vec.as_mut_bytes();
645 for (i, b) in plaintext_bytes[..LEN].iter_mut().enumerate() {
646 *b = ((i % 255) + 1) as u8;
647 }
648 let plaintext = &plaintext_bytes[..LEN];
649
650 let mut ciphertext1_vec = vec![0u128; (LEN + 15) / 16];
651 let ciphertext1_bytes = ciphertext1_vec.as_mut_bytes();
652
653 let mut ciphertext2_vec = vec![0u128; (LEN + 15) / 16];
654 let ciphertext2_bytes = ciphertext2_vec.as_mut_bytes();
655
656 let cipher = MockNonLinearCipher::new(key);
657
658 {
660 let src = PtrByteSlice::from(plaintext);
661 let dst = MutPtrByteSlice::from(&mut ciphertext1_bytes[..LEN]);
662 let processor = XtsCtsProcessor::new(tweak1, src, dst);
663 BlockCipherEncClosure::call(processor, &cipher);
664 }
665
666 {
668 let src = PtrByteSlice::from(plaintext);
669 let dst = MutPtrByteSlice::from(&mut ciphertext2_bytes[..LEN]);
670 let processor = XtsCtsProcessor::new(tweak2, src, dst);
671 BlockCipherEncClosure::call(processor, &cipher);
672 }
673
674 assert_ne!(
675 &ciphertext1_bytes[..LEN],
676 &ciphertext2_bytes[..LEN],
677 "Different tweaks should produce different ciphertexts"
678 );
679 }
680
681 #[test]
682 #[should_panic(expected = "Source and destination lengths must match")]
683 fn test_cts_panic_length_mismatch() {
684 let tweak = Tweak::new(0);
685 let src_buf = vec![0u128; 2];
686 let mut dst_buf = vec![0u128; 3];
687 let src = PtrByteSlice::from(src_buf.as_bytes());
688 let dst = MutPtrByteSlice::from(dst_buf.as_mut_bytes());
689 let _ = XtsCtsProcessor::new(tweak, src, dst);
690 }
691
692 #[test]
693 #[should_panic]
694 fn test_cts_panic_too_short() {
695 let tweak = Tweak::new(0);
696 let src_buf = vec![0u128; 2];
697 let mut dst_buf = vec![0u128; 2];
698 let src = PtrByteSlice::from(&src_buf.as_bytes()[..15]);
699 let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[..15]);
700 let _ = XtsCtsProcessor::new(tweak, src, dst);
701 }
702
703 #[test]
704 #[should_panic(expected = "src must be 16 byte aligned")]
705 fn test_cts_panic_unaligned_src() {
706 let tweak = Tweak::new(0);
707 let src_buf = vec![0u128; 3];
708 let mut dst_buf = vec![0u128; 2];
709 let src = PtrByteSlice::from(&src_buf.as_bytes()[1..17]);
710 let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[..16]);
711 let _ = XtsCtsProcessor::new(tweak, src, dst);
712 }
713
714 #[test]
715 #[should_panic(expected = "dst must be 16 byte aligned")]
716 fn test_cts_panic_unaligned_dst() {
717 let tweak = Tweak::new(0);
718 let src_buf = vec![0u128; 2];
719 let mut dst_buf = vec![0u128; 3];
720 let src = PtrByteSlice::from(&src_buf.as_bytes()[..16]);
721 let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[1..17]);
722 let _ = XtsCtsProcessor::new(tweak, src, dst);
723 }
724}