Skip to main content

storage_xts/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
15// This assumes little-endianness which is likely to always be the case.
16assert_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
32#[cfg(target_arch = "aarch64")]
33fn assert_dcz_block_size_is_64() {
34    static CHECK: std::sync::OnceLock<()> = std::sync::OnceLock::new();
35    CHECK.get_or_init(|| {
36        let dczid: u64;
37        unsafe {
38            core::arch::asm!("mrs {0}, dczid_el0", out(reg) dczid);
39        }
40        let dzp = (dczid >> 4) & 1;
41        assert_eq!(dzp, 0, "DC ZVA is prohibited on this CPU");
42        let bs_bytes = (1usize << (dczid & 0xf)) * 4;
43        assert_eq!(bs_bytes, 64, "Expected DC ZVA block size to be 64 bytes, found {bs_bytes}");
44    });
45}
46
47/// To be used with encrypt|decrypt_with_backend for out-of-place operation.
48/// Conforms to IEEE 1619-2007.
49pub struct XtsProcessor<'a, 'b> {
50    tweak: Tweak,
51    src: PtrByteSlice<'a>,
52    dst: MutPtrByteSlice<'b>,
53}
54
55/// To be used with encrypt|decrypt_with_backend for in-place operation.
56/// Conforms to IEEE 1619-2007.
57pub struct XtsInPlaceProcessor<'a> {
58    tweak: Tweak,
59    src: PtrByteSlice<'a>,
60    dst: MutPtrByteSlice<'a>,
61}
62
63fn xts_encrypt_chunk<B: BlockCipherEncBackend<BlockSize = U16>>(
64    backend: &B,
65    mut val: u128,
66    tweak: &Tweak,
67) -> u128 {
68    // XOR plaintext with tweak.
69    val ^= tweak.0;
70
71    let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
72    backend.encrypt_block(InOut::from(arr));
73
74    // XOR ciphertext with tweak.
75    val ^ tweak.0
76}
77
78fn xts_encrypt_buffer_out_of_place<B: BlockCipherEncBackend<BlockSize = U16>>(
79    backend: &B,
80    src: PtrByteSlice<'_>,
81    mut dst: MutPtrByteSlice<'_>,
82    tweak: &mut Tweak,
83) {
84    debug_assert_eq!(src.len(), dst.len());
85    debug_assert_eq!(src.as_ptr() as usize % 64, 0);
86    debug_assert_eq!(dst.as_ptr() as usize % 64, 0);
87    debug_assert_eq!(src.len() % 64, 0);
88
89    let mut src_chunks = src.iter_as::<u128>();
90    let mut dst_chunks = dst.iter_as_mut::<u128>();
91
92    while let Some(s0) = src_chunks.next() {
93        let d0 = dst_chunks.next().unwrap();
94        // Zero the 64-byte destination cache line on ARM64 ("DC ZVA") to avoid fetching
95        // stale destination cache lines from RAM before writing out-of-place outputs.
96        #[cfg(target_arch = "aarch64")]
97        unsafe {
98            core::arch::asm!(
99                "dc zva, {0}",
100                in(reg) d0.as_ptr(),
101                options(nostack, preserves_flags),
102            );
103        }
104        let val0 = xts_encrypt_chunk(backend, s0, tweak);
105        d0.write(val0);
106        tweak.update();
107
108        let s1 = src_chunks.next().unwrap();
109        let d1 = dst_chunks.next().unwrap();
110        let val1 = xts_encrypt_chunk(backend, s1, tweak);
111        d1.write(val1);
112        tweak.update();
113
114        let s2 = src_chunks.next().unwrap();
115        let d2 = dst_chunks.next().unwrap();
116        let val2 = xts_encrypt_chunk(backend, s2, tweak);
117        d2.write(val2);
118        tweak.update();
119
120        let s3 = src_chunks.next().unwrap();
121        let d3 = dst_chunks.next().unwrap();
122        let val3 = xts_encrypt_chunk(backend, s3, tweak);
123        d3.write(val3);
124        tweak.update();
125    }
126}
127
128fn xts_encrypt_buffer<B: BlockCipherEncBackend<BlockSize = U16>>(
129    backend: &B,
130    src: PtrByteSlice<'_>,
131    mut dst: MutPtrByteSlice<'_>,
132    tweak: &mut Tweak,
133) {
134    debug_assert_eq!(src.len(), dst.len());
135    debug_assert!(src.as_ptr().cast::<u128>().is_aligned());
136    debug_assert!(dst.as_ptr().cast::<u128>().is_aligned());
137
138    let src_chunks = src.iter_as::<u128>();
139    let dst_chunks = dst.iter_as_mut::<u128>();
140
141    for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
142        let val = xts_encrypt_chunk(backend, src_chunk, tweak);
143        dst_chunk.write(val);
144        tweak.update();
145    }
146}
147
148fn xts_decrypt_chunk<B: BlockCipherDecBackend<BlockSize = U16>>(
149    backend: &B,
150    mut val: u128,
151    tweak: &Tweak,
152) -> u128 {
153    // XOR ciphertext with tweak.
154    val ^= tweak.0;
155
156    let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
157    backend.decrypt_block(InOut::from(arr));
158
159    // XOR plaintext with tweak.
160    val ^ tweak.0
161}
162
163fn xts_decrypt_buffer_out_of_place<B: BlockCipherDecBackend<BlockSize = U16>>(
164    backend: &B,
165    src: PtrByteSlice<'_>,
166    mut dst: MutPtrByteSlice<'_>,
167    tweak: &mut Tweak,
168) {
169    debug_assert_eq!(src.len(), dst.len());
170    debug_assert_eq!(src.as_ptr() as usize % 64, 0);
171    debug_assert_eq!(dst.as_ptr() as usize % 64, 0);
172    debug_assert_eq!(src.len() % 64, 0);
173
174    let mut src_chunks = src.iter_as::<u128>();
175    let mut dst_chunks = dst.iter_as_mut::<u128>();
176
177    while let Some(s0) = src_chunks.next() {
178        let d0 = dst_chunks.next().unwrap();
179        // Zero the 64-byte destination cache line on ARM64 ("DC ZVA") to avoid fetching
180        // stale destination cache lines from RAM before writing out-of-place outputs.
181        #[cfg(target_arch = "aarch64")]
182        unsafe {
183            core::arch::asm!(
184                "dc zva, {0}",
185                in(reg) d0.as_ptr(),
186                options(nostack, preserves_flags),
187            );
188        }
189        let val0 = xts_decrypt_chunk(backend, s0, tweak);
190        d0.write(val0);
191        tweak.update();
192
193        let s1 = src_chunks.next().unwrap();
194        let d1 = dst_chunks.next().unwrap();
195        let val1 = xts_decrypt_chunk(backend, s1, tweak);
196        d1.write(val1);
197        tweak.update();
198
199        let s2 = src_chunks.next().unwrap();
200        let d2 = dst_chunks.next().unwrap();
201        let val2 = xts_decrypt_chunk(backend, s2, tweak);
202        d2.write(val2);
203        tweak.update();
204
205        let s3 = src_chunks.next().unwrap();
206        let d3 = dst_chunks.next().unwrap();
207        let val3 = xts_decrypt_chunk(backend, s3, tweak);
208        d3.write(val3);
209        tweak.update();
210    }
211}
212
213fn xts_decrypt_buffer<B: BlockCipherDecBackend<BlockSize = U16>>(
214    backend: &B,
215    src: PtrByteSlice<'_>,
216    mut dst: MutPtrByteSlice<'_>,
217    tweak: &mut Tweak,
218) {
219    debug_assert_eq!(src.len(), dst.len());
220    debug_assert!(src.as_ptr().cast::<u128>().is_aligned());
221    debug_assert!(dst.as_ptr().cast::<u128>().is_aligned());
222
223    let src_chunks = src.iter_as::<u128>();
224    let dst_chunks = dst.iter_as_mut::<u128>();
225
226    for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
227        let val = xts_decrypt_chunk(backend, src_chunk, tweak);
228        dst_chunk.write(val);
229        tweak.update();
230    }
231}
232
233impl<'a, 'b> XtsProcessor<'a, 'b> {
234    /// `tweak` should be encrypted. `src` and `dst` must have the same length, be 64-byte
235    /// aligned, and length must be a multiple of 64 bytes.
236    pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, dst: MutPtrByteSlice<'b>) -> Self {
237        assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
238        assert_eq!(src.len() % 64, 0, "length must be a multiple of 64 bytes");
239        assert_eq!(src.as_ptr() as usize % 64, 0, "src must be 64-byte aligned");
240        assert_eq!(dst.as_ptr() as usize % 64, 0, "dst must be 64-byte aligned");
241        #[cfg(target_arch = "aarch64")]
242        assert_dcz_block_size_is_64();
243        Self { tweak, src, dst }
244    }
245}
246
247impl BlockSizeUser for XtsProcessor<'_, '_> {
248    type BlockSize = U16;
249}
250
251impl BlockCipherEncClosure for XtsProcessor<'_, '_> {
252    fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
253        let Self { mut tweak, src, dst } = self;
254        xts_encrypt_buffer_out_of_place(backend, src, dst, &mut tweak);
255    }
256}
257
258impl BlockCipherDecClosure for XtsProcessor<'_, '_> {
259    fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
260        let Self { mut tweak, src, dst } = self;
261        xts_decrypt_buffer_out_of_place(backend, src, dst, &mut tweak);
262    }
263}
264
265impl<'a> XtsInPlaceProcessor<'a> {
266    /// Creates an XtsInPlaceProcessor for in-place operation on a single buffer.
267    pub fn new(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> Self {
268        assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
269        let len = buf.len();
270        let ptr = buf.as_ptr_slice().as_ptr();
271        // SAFETY: We are creating a PtrByteSlice that aliases with the MutPtrByteSlice.
272        // This is safe because PtrByteSlice only allows read access, and we control the
273        // execution in `call` to ensure we don't violate safety (we read a block, then write it,
274        // so we don't have concurrent read/write on the same sub-block).
275        let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
276        Self { tweak, src, dst: buf }
277    }
278}
279
280impl BlockSizeUser for XtsInPlaceProcessor<'_> {
281    type BlockSize = U16;
282}
283
284impl BlockCipherEncClosure for XtsInPlaceProcessor<'_> {
285    fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
286        let Self { mut tweak, src, dst } = self;
287        xts_encrypt_buffer(backend, src, dst, &mut tweak);
288    }
289}
290
291impl BlockCipherDecClosure for XtsInPlaceProcessor<'_> {
292    fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
293        let Self { mut tweak, src, dst } = self;
294        xts_decrypt_buffer(backend, src, dst, &mut tweak);
295    }
296}
297
298/// Handles ciphertext stealing in order to allow non-BlockSize lengths to be used as long as
299/// they are >= BlockSize. To be used with encrypt|decrypt_with_backend for out-of-place and
300/// in-place operation. Conforms to IEEE 1619-2007.
301pub struct XtsCtsProcessor<'a, 'b> {
302    tweak: Tweak,
303    src: PtrByteSlice<'a>,
304    dst: MutPtrByteSlice<'b>,
305}
306
307impl BlockSizeUser for XtsCtsProcessor<'_, '_> {
308    type BlockSize = U16;
309}
310
311impl<'a, 'b> XtsCtsProcessor<'a, 'b> {
312    /// `tweak` should be encrypted. `src` and `dst` must have the same length and be 16 byte
313    /// aligned.
314    pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, dst: MutPtrByteSlice<'b>) -> Self {
315        assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
316        assert!(src.len() >= size_of::<u128>());
317        assert!(src.as_ptr().cast::<u128>().is_aligned(), "src must be 16 byte aligned");
318        assert!(dst.as_ptr().cast::<u128>().is_aligned(), "dst must be 16 byte aligned");
319        Self { tweak, src, dst }
320    }
321
322    /// Creates an XtsCtsProcessor for in-place operation on a single buffer.
323    pub fn new_in_place(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> XtsCtsProcessor<'a, 'a> {
324        assert!(buf.len() >= size_of::<u128>());
325        assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
326        let len = buf.len();
327        let ptr = buf.as_ptr_slice().as_ptr();
328        // SAFETY: We are creating a PtrByteSlice that aliases with the MutPtrByteSlice.
329        // This is safe because PtrByteSlice only allows read access, and we control the
330        // execution in `call` to ensure we don't violate safety.
331        let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
332        XtsCtsProcessor { tweak, src, dst: buf }
333    }
334}
335
336impl BlockCipherEncClosure for XtsCtsProcessor<'_, '_> {
337    fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
338        let Self { mut tweak, src, mut dst } = self;
339        let len = src.len();
340        let base_size = len & (!0x0F);
341
342        // Fast path for non-CTS (len is an exact multiple of 16):
343        if len == base_size {
344            xts_encrypt_buffer(backend, src, dst, &mut tweak);
345            return;
346        }
347
348        // All but the last two blocks are normal.
349        if base_size > 16 {
350            let src_base = src.subslice(0..(base_size - 16));
351            let dst_base = dst.subslice_mut(0..(base_size - 16));
352            xts_encrypt_buffer(backend, src_base, dst_base, &mut tweak);
353        }
354
355        let last_full_block_range = (base_size - 16)..base_size;
356        let last_full_block =
357            src.subslice(last_full_block_range.clone()).read().expect("Size validated above");
358        let intermediate_ciphertext = xts_encrypt_chunk(backend, last_full_block, &tweak);
359
360        let extra = len & 0x0F;
361        let intermediate_cipher_bytes = intermediate_ciphertext.to_le_bytes();
362
363        // Construct combined block: start with intermediate_cipher_bytes at the end and put the
364        // remaining plaintext at the beginning. Must read from second-to-last before writing to
365        // last block to support in-place operation.
366        let mut combined_block_bytes = intermediate_cipher_bytes;
367        src.subslice(base_size..len).copy_to_slice(&mut combined_block_bytes[0..extra]);
368
369        // Write the first ciphertext bytes into the partial block slot.
370        dst.subslice_mut(base_size..len).copy_from_slice(&intermediate_cipher_bytes[0..extra]);
371
372        tweak.update();
373
374        let combined_block = u128::from_le_bytes(combined_block_bytes);
375        let full_ciphertext = xts_encrypt_chunk(backend, combined_block, &tweak);
376
377        // Write the encrypted full ciphertext block into the second-to-last block slot.
378        dst.subslice_mut(last_full_block_range)
379            .write(full_ciphertext)
380            .expect("Size validated above");
381    }
382}
383
384impl BlockCipherDecClosure for XtsCtsProcessor<'_, '_> {
385    fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
386        let Self { mut tweak, src, mut dst } = self;
387        let len = src.len();
388        let base_size = len & (!0x0F);
389
390        // Fast path for non-CTS (len is an exact multiple of 16):
391        if len == base_size {
392            xts_decrypt_buffer(backend, src, dst, &mut tweak);
393            return;
394        }
395
396        // All but the last two blocks are normal.
397        if base_size > 16 {
398            let src_base = src.subslice(0..(base_size - 16));
399            let dst_base = dst.subslice_mut(0..(base_size - 16));
400            xts_decrypt_buffer(backend, src_base, dst_base, &mut tweak);
401        }
402
403        let last_full_block_tweak = tweak;
404        let last_full_block_range = (base_size - 16)..base_size;
405
406        tweak.update();
407        let extra = len & 0x0F;
408
409        // Decrypt full ciphertext block at last_full_block_range using updated tweak:
410        let full_ciphertext =
411            src.subslice(last_full_block_range.clone()).read().expect("Size validated above");
412        let decrypted_combined_block = xts_decrypt_chunk(backend, full_ciphertext, &tweak);
413        let combined_bytes = decrypted_combined_block.to_le_bytes();
414
415        // Reconstruct intermediate ciphertext for second-to-last block. Start with combined_bytes
416        // at the end and put the input ciphertext at the beginning. Must read from second-to-last
417        // before writing to last block to support in-place operation.
418        let mut intermediate_bytes = combined_bytes;
419        src.subslice(base_size..len).copy_to_slice(&mut intermediate_bytes[0..extra]);
420
421        // Write recovered partial plaintext.
422        dst.subslice_mut(base_size..len).copy_from_slice(&combined_bytes[0..extra]);
423
424        let last_block_ciphertext = u128::from_le_bytes(intermediate_bytes);
425        let val = xts_decrypt_chunk(backend, last_block_ciphertext, &last_full_block_tweak);
426        dst.subslice_mut(last_full_block_range).write(val).expect("Size validated above");
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433    use cipher::inout::InOut;
434    use cipher::typenum::consts::U1;
435    use cipher::{Block, ParBlocksSizeUser};
436    use std::cell::RefCell;
437    use test_case::test_case;
438    use zerocopy::{FromBytes, Immutable, IntoBytes};
439
440    struct MockCipher {
441        recorded_blocks: RefCell<Vec<u128>>,
442        key: u128,
443    }
444
445    impl MockCipher {
446        fn new(key: u128) -> Self {
447            Self { recorded_blocks: RefCell::new(Vec::new()), key }
448        }
449    }
450
451    impl BlockSizeUser for MockCipher {
452        type BlockSize = U16;
453    }
454
455    impl ParBlocksSizeUser for MockCipher {
456        type ParBlocksSize = U1;
457    }
458
459    impl BlockCipherEncBackend for MockCipher {
460        fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
461            // SAFETY: Block<Self> is Array<u8, U16>, which is 16 bytes.
462            let mut val =
463                unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
464            self.recorded_blocks.borrow_mut().push(val);
465            val ^= self.key;
466            // SAFETY: Block<Self> is Array<u8, U16>, which is 16 bytes.
467            unsafe {
468                std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
469            }
470        }
471    }
472
473    impl BlockCipherDecBackend for MockCipher {
474        fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
475            // SAFETY: Block<Self> is Array<u8, U16>, which is 16 bytes.
476            let mut val =
477                unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
478            self.recorded_blocks.borrow_mut().push(val);
479            // Lazy scramble. See above for the encryption method.
480            val = val.rotate_right(3);
481            val ^= self.key;
482            // SAFETY: Block<Self> is Array<u8, U16>, which is 16 bytes.
483            unsafe {
484                std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
485            }
486        }
487    }
488
489    /// A simple fake scrambler that includes a rotate left to keep the XOR from canceling itself
490    /// out.
491    struct MockNonLinearCipher {
492        key: u128,
493    }
494
495    impl MockNonLinearCipher {
496        fn new(key: u128) -> Self {
497            Self { key }
498        }
499    }
500
501    impl BlockSizeUser for MockNonLinearCipher {
502        type BlockSize = U16;
503    }
504
505    impl ParBlocksSizeUser for MockNonLinearCipher {
506        type ParBlocksSize = U1;
507    }
508
509    impl BlockCipherEncBackend for MockNonLinearCipher {
510        fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
511            let mut val =
512                unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
513            val = val.rotate_left(11) ^ self.key;
514            unsafe {
515                std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
516            }
517        }
518    }
519
520    impl BlockCipherDecBackend for MockNonLinearCipher {
521        fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
522            let mut val =
523                unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
524            val = (val ^ self.key).rotate_right(11);
525            unsafe {
526                std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
527            }
528        }
529    }
530
531    #[repr(C)]
532    #[derive(FromBytes, IntoBytes, Immutable)]
533    struct Blocks<const N: usize>([u128; N]);
534
535    #[repr(C, align(64))]
536    struct Aligned64<T>(T);
537
538    static_assertions::const_assert!(std::mem::align_of::<Aligned64<Blocks<1>>>() == 64);
539
540    impl<const N: usize> Default for Blocks<N> {
541        fn default() -> Self {
542            Self([0u128; N])
543        }
544    }
545
546    #[test]
547    fn test_xts_out_of_place() {
548        let mut plaintext: Aligned64<Blocks<4>> = Aligned64(Default::default());
549        for (i, x) in plaintext.0.as_mut_bytes().iter_mut().enumerate() {
550            *x = i as u8;
551        }
552        let mut ciphertext: Aligned64<Blocks<4>> = Aligned64(Default::default());
553
554        let src = PtrByteSlice::from(plaintext.0.as_bytes());
555        let dst = MutPtrByteSlice::from(ciphertext.0.as_mut_bytes());
556
557        let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
558        let tweak = Tweak::new(tweak_val);
559        let key = 0xffeeddccbbaa99887766554433221100u128;
560
561        let processor = XtsProcessor::new(tweak, src, dst);
562        let cipher = MockCipher::new(key);
563
564        BlockCipherEncClosure::call(processor, &cipher);
565
566        // Verify ciphertext.
567        // Since our mock cipher is just XOR with key, the tweak should cancel out.
568        // C = P ^ K.
569        let expected_c0 =
570            u128::from_le_bytes(plaintext.0.as_bytes()[0..16].try_into().unwrap()) ^ key;
571        let expected_c1 =
572            u128::from_le_bytes(plaintext.0.as_bytes()[16..32].try_into().unwrap()) ^ key;
573
574        let actual_c0 = u128::from_le_bytes(ciphertext.0.as_bytes()[0..16].try_into().unwrap());
575        let actual_c1 = u128::from_le_bytes(ciphertext.0.as_bytes()[16..32].try_into().unwrap());
576
577        assert_eq!(actual_c0, expected_c0);
578        assert_eq!(actual_c1, expected_c1);
579
580        // Verify recorded blocks (should be P ^ T).
581        assert_eq!(cipher.recorded_blocks.borrow().len(), 4);
582
583        let p0 = u128::from_le_bytes(plaintext.0.as_bytes()[0..16].try_into().unwrap());
584        let p1 = u128::from_le_bytes(plaintext.0.as_bytes()[16..32].try_into().unwrap());
585
586        let mut t0 = tweak;
587        assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
588        t0.update();
589        assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
590    }
591
592    #[test]
593    fn test_xts_in_place() {
594        let mut buf: Blocks<2> = Default::default();
595        for (i, x) in buf.as_mut_bytes().iter_mut().enumerate() {
596            *x = i as u8;
597        }
598
599        let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
600        let tweak = Tweak::new(tweak_val);
601        let key = 0xffeeddccbbaa99887766554433221100u128;
602
603        // Save original plaintext for verification.
604        let p0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
605        let p1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
606
607        let slice = MutPtrByteSlice::from(buf.as_mut_bytes());
608        let processor = XtsInPlaceProcessor::new(tweak, slice);
609        let cipher = MockCipher::new(key);
610
611        BlockCipherEncClosure::call(processor, &cipher);
612
613        // Verify in-place ciphertext.
614        let expected_c0 = p0 ^ key;
615        let expected_c1 = p1 ^ key;
616
617        let actual_c0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
618        let actual_c1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
619
620        assert_eq!(actual_c0, expected_c0);
621        assert_eq!(actual_c1, expected_c1);
622
623        // Verify recorded blocks.
624        assert_eq!(cipher.recorded_blocks.borrow().len(), 2);
625        let mut t0 = tweak;
626        assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
627        t0.update();
628        assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
629    }
630
631    #[test_case(16; "exact_block")]
632    #[test_case(17; "one_byte_more_than_block")]
633    #[test_case(31; "one_byte_less_than_two_blocks")]
634    #[test_case(32; "exact_two_blocks")]
635    #[test_case(80; "bunch_of_blocks_exact")]
636    #[test_case(87; "bunch_of_blocks_cts")]
637    fn test_cts_encrypt_decrypt(len: usize) {
638        let tweak = Tweak::new(0x123456789abcdef0123456789abcdef0);
639        let key = 0xffeeddccbbaa99887766554433221100;
640
641        let mut plaintext_vec = vec![0u128; (len + 15) / 16];
642        let plaintext_bytes = plaintext_vec.as_mut_bytes();
643        for (i, b) in plaintext_bytes[..len].iter_mut().enumerate() {
644            *b = ((i % 255) + 1) as u8;
645        }
646        let plaintext = &plaintext_bytes[..len];
647
648        let mut ciphertext_vec = vec![0u128; (len + 15) / 16];
649        let ciphertext_bytes = ciphertext_vec.as_mut_bytes();
650
651        let mut decrypted_vec = vec![0u128; (len + 15) / 16];
652        let decrypted_bytes = decrypted_vec.as_mut_bytes();
653
654        let cipher = MockNonLinearCipher::new(key);
655
656        // Encrypt out-of-place
657        {
658            let src = PtrByteSlice::from(plaintext);
659            let dst = MutPtrByteSlice::from(&mut ciphertext_bytes[..len]);
660            let processor = XtsCtsProcessor::new(tweak, src, dst);
661            BlockCipherEncClosure::call(processor, &cipher);
662        }
663
664        let ciphertext = &ciphertext_bytes[..len];
665
666        // Verify none of the original blocks/content are intact
667        assert_ne!(ciphertext, plaintext, "Ciphertext should not match plaintext");
668        for chunk_start in (0..len).step_by(16) {
669            let chunk_end = (chunk_start + 16).min(len);
670            assert_ne!(
671                &ciphertext[chunk_start..chunk_end],
672                &plaintext[chunk_start..chunk_end],
673                "Chunk at {chunk_start}..{chunk_end} should not match plaintext"
674            );
675        }
676
677        // Decrypt out-of-place
678        {
679            let src = PtrByteSlice::from(ciphertext);
680            let dst = MutPtrByteSlice::from(&mut decrypted_bytes[..len]);
681            let processor = XtsCtsProcessor::new(tweak, src, dst);
682            BlockCipherDecClosure::call(processor, &cipher);
683        }
684
685        let decrypted = &decrypted_bytes[..len];
686
687        // Verify decrypted matches original plaintext
688        assert_eq!(decrypted, plaintext, "Decrypted text should match original plaintext");
689    }
690
691    #[test]
692    fn test_cts_encrypt_decrypt_in_place() {
693        const LEN: usize = 87;
694
695        let tweak = Tweak::new(0x123456789abcdef0123456789abcdef0);
696        let key = 0xffeeddccbbaa99887766554433221100;
697
698        let mut buf_vec = vec![0u128; (LEN + 15) / 16];
699        let buf_bytes = buf_vec.as_mut_bytes();
700        for (i, b) in buf_bytes[..LEN].iter_mut().enumerate() {
701            *b = ((i % 255) + 1) as u8;
702        }
703        let original_plaintext = buf_bytes[..LEN].to_vec();
704
705        let cipher = MockNonLinearCipher::new(key);
706
707        // Encrypt in-place
708        {
709            let slice = MutPtrByteSlice::from(&mut buf_bytes[..LEN]);
710            let processor = XtsCtsProcessor::new_in_place(tweak, slice);
711            BlockCipherEncClosure::call(processor, &cipher);
712        }
713
714        // Verify in-place ciphertext differs from original plaintext
715        assert_ne!(
716            &buf_bytes[..LEN],
717            &original_plaintext[..],
718            "In-place ciphertext should not match plaintext"
719        );
720        for chunk_start in (0..LEN).step_by(16) {
721            let chunk_end = (chunk_start + 16).min(LEN);
722            assert_ne!(
723                &buf_bytes[chunk_start..chunk_end],
724                &original_plaintext[chunk_start..chunk_end],
725                "Chunk at {chunk_start}..{chunk_end} should not match plaintext"
726            );
727        }
728
729        // Decrypt in-place
730        {
731            let slice = MutPtrByteSlice::from(&mut buf_bytes[..LEN]);
732            let processor = XtsCtsProcessor::new_in_place(tweak, slice);
733            BlockCipherDecClosure::call(processor, &cipher);
734        }
735
736        // Verify decrypted matches original plaintext
737        assert_eq!(
738            &buf_bytes[..LEN],
739            &original_plaintext[..],
740            "Decrypted in-place text should match original plaintext"
741        );
742    }
743
744    #[test_case(4; "four_blocks")]
745    #[test_case(8; "eight_blocks")]
746    fn test_cts_matches_normal_xts_on_exact_blocks(num_blocks: usize) {
747        let tweak = Tweak::new(0x9876543210abcdef9876543210abcdef);
748        let key = 0x0123456789abcdef0123456789abcdef;
749
750        let len = num_blocks * 16;
751        let mut plaintext_vec = vec![0u128; num_blocks + 4];
752        let addr = plaintext_vec.as_ptr() as usize;
753        let offset = (64 - (addr % 64)) % 64;
754        let plaintext_bytes = &mut plaintext_vec.as_mut_bytes()[offset..offset + len];
755        for (i, b) in plaintext_bytes.iter_mut().enumerate() {
756            *b = (i as u8).wrapping_mul(17).wrapping_add(3);
757        }
758
759        let mut normal_cts_vec = vec![0u128; num_blocks + 4];
760        let normal_addr = normal_cts_vec.as_ptr() as usize;
761        let normal_offset = (64 - (normal_addr % 64)) % 64;
762        let normal_cts_bytes =
763            &mut normal_cts_vec.as_mut_bytes()[normal_offset..normal_offset + len];
764
765        let mut cts_vec = vec![0u128; num_blocks + 4];
766        let cts_addr = cts_vec.as_ptr() as usize;
767        let cts_offset = (64 - (cts_addr % 64)) % 64;
768        let cts_bytes = &mut cts_vec.as_mut_bytes()[cts_offset..cts_offset + len];
769
770        let cipher = MockNonLinearCipher::new(key);
771
772        // Normal XTS encryption
773        {
774            let src = PtrByteSlice::from(&*plaintext_bytes);
775            let dst = MutPtrByteSlice::from(&mut *normal_cts_bytes);
776            let processor = XtsProcessor::new(tweak, src, dst);
777            BlockCipherEncClosure::call(processor, &cipher);
778        }
779
780        // CTS XTS encryption
781        {
782            let src = PtrByteSlice::from(&*plaintext_bytes);
783            let dst = MutPtrByteSlice::from(&mut *cts_bytes);
784            let processor = XtsCtsProcessor::new(tweak, src, dst);
785            BlockCipherEncClosure::call(processor, &cipher);
786        }
787
788        assert_eq!(
789            normal_cts_bytes, cts_bytes,
790            "CTS XTS should match normal XTS for {num_blocks} blocks"
791        );
792    }
793
794    #[test_case(1; "one_block")]
795    #[test_case(2; "two_blocks")]
796    #[test_case(5; "five_blocks")]
797    fn test_cts_matches_inplace_xts_on_exact_blocks(num_blocks: usize) {
798        let tweak = Tweak::new(0x9876543210abcdef9876543210abcdef);
799        let key = 0x0123456789abcdef0123456789abcdef;
800
801        let len = num_blocks * 16;
802        let mut plaintext_vec = vec![0u128; num_blocks];
803        let plaintext_bytes = plaintext_vec.as_mut_bytes();
804        for (i, b) in plaintext_bytes[..len].iter_mut().enumerate() {
805            *b = (i as u8).wrapping_mul(17).wrapping_add(3);
806        }
807        let plaintext = &plaintext_bytes[..len];
808
809        let mut normal_cts_vec = vec![0u128; num_blocks];
810        let mut cts_vec = vec![0u128; num_blocks];
811
812        let cipher = MockNonLinearCipher::new(key);
813
814        // In-place XTS encryption
815        {
816            let mut slice = MutPtrByteSlice::from(&mut normal_cts_vec.as_mut_bytes()[..len]);
817            slice.copy_from_slice(plaintext);
818            let processor = XtsInPlaceProcessor::new(tweak, slice);
819            BlockCipherEncClosure::call(processor, &cipher);
820        }
821
822        // CTS XTS encryption
823        {
824            let src = PtrByteSlice::from(plaintext);
825            let dst = MutPtrByteSlice::from(&mut cts_vec.as_mut_bytes()[..len]);
826            let processor = XtsCtsProcessor::new(tweak, src, dst);
827            BlockCipherEncClosure::call(processor, &cipher);
828        }
829
830        assert_eq!(
831            normal_cts_vec, cts_vec,
832            "CTS XTS should match in-place XTS for {num_blocks} blocks"
833        );
834    }
835
836    #[test]
837    fn test_cts_different_tweaks_different_ciphertext() {
838        const LEN: usize = 87;
839
840        let tweak1 = Tweak::new(0x123456789abcdef0123456789abcdef0);
841        let tweak2 = Tweak::new(0xfeef876543210fedcba9876543210fed);
842        let key = 0xffeeddccbbaa99887766554433221100;
843
844        let mut plaintext_vec = vec![0u128; (LEN + 15) / 16];
845        let plaintext_bytes = plaintext_vec.as_mut_bytes();
846        for (i, b) in plaintext_bytes[..LEN].iter_mut().enumerate() {
847            *b = ((i % 255) + 1) as u8;
848        }
849        let plaintext = &plaintext_bytes[..LEN];
850
851        let mut ciphertext1_vec = vec![0u128; (LEN + 15) / 16];
852        let ciphertext1_bytes = ciphertext1_vec.as_mut_bytes();
853
854        let mut ciphertext2_vec = vec![0u128; (LEN + 15) / 16];
855        let ciphertext2_bytes = ciphertext2_vec.as_mut_bytes();
856
857        let cipher = MockNonLinearCipher::new(key);
858
859        // Encrypt with tweak1
860        {
861            let src = PtrByteSlice::from(plaintext);
862            let dst = MutPtrByteSlice::from(&mut ciphertext1_bytes[..LEN]);
863            let processor = XtsCtsProcessor::new(tweak1, src, dst);
864            BlockCipherEncClosure::call(processor, &cipher);
865        }
866
867        // Encrypt with tweak2
868        {
869            let src = PtrByteSlice::from(plaintext);
870            let dst = MutPtrByteSlice::from(&mut ciphertext2_bytes[..LEN]);
871            let processor = XtsCtsProcessor::new(tweak2, src, dst);
872            BlockCipherEncClosure::call(processor, &cipher);
873        }
874
875        assert_ne!(
876            &ciphertext1_bytes[..LEN],
877            &ciphertext2_bytes[..LEN],
878            "Different tweaks should produce different ciphertexts"
879        );
880    }
881
882    #[test]
883    #[should_panic(expected = "Source and destination lengths must match")]
884    fn test_cts_panic_length_mismatch() {
885        let tweak = Tweak::new(0);
886        let src_buf = vec![0u128; 2];
887        let mut dst_buf = vec![0u128; 3];
888        let src = PtrByteSlice::from(src_buf.as_bytes());
889        let dst = MutPtrByteSlice::from(dst_buf.as_mut_bytes());
890        let _ = XtsCtsProcessor::new(tweak, src, dst);
891    }
892
893    #[test]
894    #[should_panic]
895    fn test_cts_panic_too_short() {
896        let tweak = Tweak::new(0);
897        let src_buf = vec![0u128; 2];
898        let mut dst_buf = vec![0u128; 2];
899        let src = PtrByteSlice::from(&src_buf.as_bytes()[..15]);
900        let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[..15]);
901        let _ = XtsCtsProcessor::new(tweak, src, dst);
902    }
903
904    #[test]
905    #[should_panic(expected = "src must be 16 byte aligned")]
906    fn test_cts_panic_unaligned_src() {
907        let tweak = Tweak::new(0);
908        let src_buf = vec![0u128; 3];
909        let mut dst_buf = vec![0u128; 2];
910        let src = PtrByteSlice::from(&src_buf.as_bytes()[1..17]);
911        let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[..16]);
912        let _ = XtsCtsProcessor::new(tweak, src, dst);
913    }
914
915    #[test]
916    #[should_panic(expected = "dst must be 16 byte aligned")]
917    fn test_cts_panic_unaligned_dst() {
918        let tweak = Tweak::new(0);
919        let src_buf = vec![0u128; 2];
920        let mut dst_buf = vec![0u128; 3];
921        let src = PtrByteSlice::from(&src_buf.as_bytes()[..16]);
922        let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[1..17]);
923        let _ = XtsCtsProcessor::new(tweak, src, dst);
924    }
925}