Skip to main content

encrust_core/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(not(test), no_std)]
3#![doc = include_str!("../README.md")]
4
5#[cfg(feature = "hashstrings")]
6mod hashstrings;
7#[cfg(feature = "hashstrings")]
8pub use hashstrings::*;
9
10#[cfg(not(test))]
11extern crate core;
12
13extern crate alloc;
14
15use alloc::ffi::CString;
16use alloc::string::String;
17use alloc::vec::Vec;
18use core::any::TypeId;
19use core::ops::{Deref, DerefMut};
20use core::slice;
21
22use rand::rngs::Xoshiro256PlusPlus;
23use rand::seq::SliceRandom;
24use rand::{Rng, SeedableRng};
25use zeroize::Zeroize;
26
27/// Module with symbols needed by encrust's macros to generate working code regardless of whether
28/// the code is compiled with `std` or not.
29#[doc(hidden)]
30pub mod __private {
31    pub use alloc::ffi::CString;
32    pub use alloc::string::String;
33    pub use alloc::vec;
34}
35
36/// Container for encrusted data.
37///
38/// Care should be taken if `T` has a non-trivial `Drop` implementation, as `T` is not dropped until
39/// `zeroize` has been called on it.
40pub struct Encrusted<T>
41where
42    T: Encrust,
43    T::Storage: Zeroize,
44{
45    data: T::Storage,
46    seed: u64,
47}
48
49impl<T> Encrusted<T>
50where
51    T: Encrust,
52    T::Storage: Zeroize,
53{
54    /// Encrust `data` using `seed`.
55    pub fn new(data: T, seed: u64) -> Self {
56        let mut data = data.to_storage();
57
58        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(seed);
59
60        <T as Encrust>::toggle_encrust(&mut data, &mut encrust_rng);
61
62        Self { data, seed }
63    }
64
65    /// Create an [`Encrusted`] value from data that has already been encrusted.
66    ///
67    /// This is used by macros to include pre-encrusted objects in generated code.
68    ///
69    /// # Safety
70    /// `data` must be the result of calling [`Encrust::toggle_encrust`] exactly once on a valid
71    /// value of type `T`, using the same algorithm version and a random number generator seeded
72    /// with `seed`.
73    ///
74    /// If the storage does not match `seed`, then [`Encrusted::decrust`] may produce invalid
75    /// values. For example, `String` storage may no longer be valid UTF-8, which would make later
76    /// `str` access undefined behavior.
77    #[doc(hidden)]
78    #[cfg(feature = "macros")]
79    pub const unsafe fn from_encrusted_data(data: T::Storage, seed: u64) -> Self {
80        Self { data, seed }
81    }
82
83    /// Change the seed used to encrust the stored data.
84    pub fn reseed(&mut self, new_seed: u64) {
85        {
86            let mut decruster = Xoshiro256PlusPlus::seed_from_u64(self.seed);
87
88            <T as Encrust>::toggle_encrust(&mut self.data, &mut decruster);
89        }
90
91        self.seed = new_seed;
92
93        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(self.seed);
94
95        <T as Encrust>::toggle_encrust(&mut self.data, &mut encrust_rng);
96    }
97
98    /// Decrust the stored data and return a guard that gives access to it.
99    #[doc(alias("expose", "unlock"))]
100    pub fn decrust(&mut self) -> DecrustGuard<'_, T> {
101        DecrustGuard::new(self)
102    }
103}
104
105impl<T> Drop for Encrusted<T>
106where
107    T: Encrust,
108    T::Storage: Zeroize,
109{
110    /// Zeroize the stored data and seed before dropping them.
111    ///
112    /// Note that the data is zeroized prior to being dropped, which may cause problems for the drop
113    /// implementation of the underlying data.
114    fn drop(&mut self) {
115        self.data.zeroize();
116        self.seed.zeroize();
117    }
118}
119
120/// Guard used to access decrusted data.
121///
122/// When the guard is dropped, the underlying data is encrusted again.
123pub struct DecrustGuard<'decrusted, T>
124where
125    T: Encrust,
126    T::Storage: Zeroize,
127{
128    encrusted_data: &'decrusted mut Encrusted<T>,
129}
130
131impl<'decrusted, T> DecrustGuard<'decrusted, T>
132where
133    T: Encrust,
134    T::Storage: Zeroize,
135{
136    fn new(encrusted_data: &'decrusted mut Encrusted<T>) -> Self {
137        let mut decruster = Xoshiro256PlusPlus::seed_from_u64(encrusted_data.seed);
138
139        <T as Encrust>::toggle_encrust(&mut encrusted_data.data, &mut decruster);
140
141        Self { encrusted_data }
142    }
143}
144
145impl<T> Drop for DecrustGuard<'_, T>
146where
147    T: Encrust,
148    T::Storage: Zeroize,
149{
150    fn drop(&mut self) {
151        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(self.encrusted_data.seed);
152
153        <T as Encrust>::toggle_encrust(&mut self.encrusted_data.data, &mut encrust_rng);
154    }
155}
156
157impl<T> Deref for DecrustGuard<'_, T>
158where
159    T: Encrust,
160    T::Storage: Zeroize,
161{
162    type Target = T::Ref;
163
164    fn deref(&self) -> &Self::Target {
165        // SAFETY: `DecrustGuard::new` decrusted the storage, and the guard's exclusive borrow
166        // prevents any other code from re-encrusting or replacing it before this access.
167        unsafe { <T as Encrust>::as_ref(&self.encrusted_data.data) }
168    }
169}
170
171impl<T> DerefMut for DecrustGuard<'_, T>
172where
173    T: Encrust,
174    T::Storage: Zeroize,
175{
176    fn deref_mut(&mut self) -> &mut Self::Target {
177        // SAFETY: `DecrustGuard::new` decrusted the storage, and `&mut self` guarantees unique
178        // access to the decrusted storage for the returned mutable reference.
179        unsafe { <T as Encrust>::as_mut_ref(&mut self.encrusted_data.data) }
180    }
181}
182
183/// Trait for types that can be stored in [`Encrusted`].
184///
185/// For types where `Storage` and `Ref` are `Self` it is preferable to implement [`InPlaceEncrust`]
186/// as it is simpler. This crate has a blanket implementation of `Encrust` for all types that
187/// implement [`InPlaceEncrust`].
188pub trait Encrust {
189    /// The type used to store the encrusted data for the type implementing `Encrust`.
190    ///
191    /// For simple types where any bit pattern is valid data such as plain integers, `Storage` can
192    /// be `Self`. For types with requirements, such as `String` which requires that its data is
193    /// valid UTF-8 at all times, `Storage` must be set to an appropriate type. `String`'s
194    /// implementation of `Encrust` sets `Storage` to `Vec<u8>`.
195    type Storage;
196
197    /// The type used to access encrusted data. This is the type `DecrustGuard` sets as the `Target`
198    /// for its `Deref` and `DerefMut` implementations.
199    ///
200    /// `Vec` sets `Ref` to a slice of the underlying data to prevent accidentally pushing data to
201    /// the `Vec` as this may relocate the data and leave a copy of the underlying data in memory.
202    /// Similarly, a `String` only provides a `str`.
203    type Ref: ?Sized;
204
205    /// Convert `self` to `Self::Storage` for storage in `Encrusted`.
206    fn to_storage(self) -> Self::Storage;
207
208    /// Return a reference to `Self::Ref` from `Self::Storage`. This is essentially
209    /// [`DecrustGuard`]'s `Deref` implementation.
210    ///
211    /// # Safety
212    /// This function must never be called when `storage` is encrusted. Calling `as_ref` on
213    /// encrusted data may lead to undefined behavior.
214    unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref;
215
216    /// Return a mutable reference to `Self::Ref` from `Self::Storage`. This is essentially
217    /// [`DecrustGuard`]'s `DerefMut` implementation.
218    ///
219    /// # Safety
220    /// This function must never be called when `storage` is encrusted. Calling `as_ref` on
221    /// encrusted data may lead to undefined behavior.
222    unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref;
223
224    /// Toggle data between encrusted and decrusted form.
225    ///
226    /// This function should only be used by the encrust crate to toggle the encrust state. Do
227    /// **not** call this function manually.
228    ///
229    /// Calling `toggle_encrust` itself must always be safe. However, calling `as_ref` or
230    /// `as_mut_ref` on a value where `toggle_encrust` has been called an odd number of times may
231    /// lead to undefined behavior.
232    fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng);
233}
234
235/// A simpler alternative to [`Encrust`] for types where `Storage` and `Ref` are `Self`.
236///
237/// This crate implements [`Encrust`] for all types that implement `InPlaceEncrust`.
238///
239/// This trait is safe because the blanket [`Encrust`] implementation stores and exposes `Self`
240/// directly. Implementations that use only safe Rust cannot create invalid values of `Self`; if an
241/// implementation uses unsafe code to do so, that unsafe code is responsible for upholding Rust's
242/// validity rules.
243pub trait InPlaceEncrust {
244    /// Toggle `self` between encrusted and decrusted form.
245    ///
246    /// `toggle_encrust` must be safe to call for any valid value of `Self`. It may not leave `self`
247    /// in an invalid state or otherwise make safe access to `self` undefined behavior. Calling the
248    /// method twice with the same RNG and RNG state must restore the original value.
249    ///
250    /// This function should only be used by the encrust crate to toggle the encrust state. Do
251    /// **not** call this function manually.
252    fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng);
253}
254
255impl<T> Encrust for T
256where
257    T: InPlaceEncrust,
258{
259    type Storage = Self;
260    type Ref = Self;
261
262    fn to_storage(self) -> Self::Storage {
263        self
264    }
265
266    unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
267        storage
268    }
269
270    unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
271        storage
272    }
273
274    fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
275        <T as InPlaceEncrust>::toggle_encrust(storage, encrust_rng);
276    }
277}
278
279macro_rules! encrust_int {
280    ( $( $t:ty ),* ) => {
281        $(
282            impl InPlaceEncrust for $t {
283                fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng) {
284                    let mut bytes = self.to_le_bytes();
285
286                    // Using 8 bytes as most numbers that will be used with encrust are (most
287                    // likely) 64-bit or smaller.
288                    for chunk in bytes.chunks_mut(8) {
289                        let key = encrust_rng.next_u64().to_le_bytes();
290                        for (byte, byte_key) in chunk.iter_mut().zip(key.iter()) {
291                            *byte ^= byte_key;
292                        }
293                    }
294
295                    *self = Self::from_le_bytes(bytes);
296                }
297            }
298        )*
299    };
300}
301
302encrust_int!(
303    u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
304);
305
306impl<T, const N: usize> InPlaceEncrust for [T; N]
307where
308    T: InPlaceEncrust + 'static,
309{
310    fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng) {
311        slice_toggle_encrust::<T>(self, encrust_rng);
312    }
313}
314
315impl Encrust for String {
316    type Storage = Vec<u8>;
317
318    type Ref = str;
319
320    fn to_storage(self) -> Self::Storage {
321        self.into_bytes()
322    }
323
324    unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
325        // SAFETY: It is up to the caller to ensure that it is safe to access the storage as a
326        // `str`.
327        unsafe { str::from_utf8_unchecked(storage) }
328    }
329
330    unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
331        // SAFETY: It is up to the caller to ensure that it is safe to access the storage as a
332        // `str`.
333        unsafe { str::from_utf8_unchecked_mut(storage) }
334    }
335
336    fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
337        slice_toggle_encrust::<u8>(storage, encrust_rng);
338    }
339}
340
341impl Encrust for CString {
342    type Storage = Vec<u8>;
343    // Note: it is currently not supported to get
344    type Ref = [u8];
345
346    fn to_storage(self) -> Self::Storage {
347        self.into_bytes_with_nul()
348    }
349
350    unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
351        storage.as_ref()
352    }
353
354    unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
355        storage.as_mut()
356    }
357
358    fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
359        slice_toggle_encrust::<u8>(storage, encrust_rng);
360    }
361}
362
363impl<T> Encrust for Vec<T>
364where
365    T: InPlaceEncrust + 'static,
366{
367    type Storage = Self;
368    type Ref = [T];
369
370    fn to_storage(self) -> Self::Storage {
371        self
372    }
373
374    unsafe fn as_ref(storage: &Self::Storage) -> &Self::Ref {
375        storage.as_ref()
376    }
377
378    unsafe fn as_mut_ref(storage: &mut Self::Storage) -> &mut Self::Ref {
379        storage.as_mut()
380    }
381
382    fn toggle_encrust(storage: &mut Self::Storage, encrust_rng: &mut impl Rng) {
383        slice_toggle_encrust::<T>(storage, encrust_rng);
384    }
385}
386
387/// Macro for implementing `toggle_encrust` for a slice of integers.
388///
389/// # Safety
390/// Must be called inside an unsafe block because it casts `encrust_slice` to `&mut [$ty]`. The
391/// caller must prove that `T::Storage` is exactly `$ty`, so the cast preserves element type,
392/// alignment, and length.
393macro_rules! toggle_encrust_for_integer {
394    ($ty:ty, $type_id:ident, $slice:ident, $rng:ident) => {{
395        const ARRAY_SIZE: usize = core::mem::size_of::<u64>() / core::mem::size_of::<$ty>();
396
397        // The following `assert`s should be optimized away. They help ensure the following:
398        // * `T::Storage` is `$ty`, needed to make `slice::from_raw_parts_mut` sound.
399        // * There are enough bytes in a `u64` for a `[$ty; ARRAY_SIZE]`.
400        assert!($type_id == TypeId::of::<$ty>());
401        assert!(core::mem::size_of::<u64>() >= core::mem::size_of::<[$ty; ARRAY_SIZE]>());
402
403        let slice = slice::from_raw_parts_mut($slice.as_mut_ptr().cast::<$ty>(), $slice.len());
404
405        for chunk in slice.chunks_mut(ARRAY_SIZE) {
406            let key = $rng.next_u64().to_le_bytes();
407            let key: [$ty; ARRAY_SIZE] = core::array::from_fn(|i| {
408                let start = i * core::mem::size_of::<$ty>();
409                <$ty>::from_le_bytes(
410                    key[start..start + core::mem::size_of::<$ty>()]
411                        .try_into()
412                        .unwrap(),
413                )
414            });
415
416            for (num, key) in chunk.iter_mut().zip(key.iter()) {
417                *num ^= key;
418            }
419        }
420    }};
421}
422
423/// Helper function that implements encrust for any slice of `Encrust`-able types.
424fn slice_toggle_encrust<T>(encrust_slice: &mut [T::Storage], encrust_rng: &mut impl Rng)
425where
426    T: Encrust,
427    T::Storage: 'static,
428{
429    let type_id: TypeId = TypeId::of::<T::Storage>();
430
431    if type_id == TypeId::of::<u8>() {
432        // SAFETY:
433        // * The `TypeId` check proves `T::Storage` is `u8`, so the cast preserves element type,
434        //   alignment, and length.
435        // * The original slice is shadowed, so no aliasing reference is used while the cast slice
436        //   exists.
437        let encrust_slice = unsafe {
438            slice::from_raw_parts_mut(encrust_slice.as_mut_ptr().cast::<u8>(), encrust_slice.len())
439        };
440
441        u8_slice_toggle_encrust(encrust_slice, encrust_rng);
442    } else if type_id == TypeId::of::<i8>() {
443        // SAFETY: The `TypeId` check above proves `T::Storage` is `i8`.
444        unsafe {
445            toggle_encrust_for_integer!(i8, type_id, encrust_slice, encrust_rng);
446        }
447    } else if type_id == TypeId::of::<i16>() {
448        // SAFETY: The `TypeId` check above proves `T::Storage` is `i16`.
449        unsafe {
450            toggle_encrust_for_integer!(i16, type_id, encrust_slice, encrust_rng);
451        }
452    } else if type_id == TypeId::of::<i32>() {
453        // SAFETY: The `TypeId` check above proves `T::Storage` is `i32`.
454        unsafe {
455            toggle_encrust_for_integer!(i32, type_id, encrust_slice, encrust_rng);
456        }
457    } else if type_id == TypeId::of::<i64>() {
458        // SAFETY: The `TypeId` check above proves `T::Storage` is `i64`.
459        unsafe {
460            toggle_encrust_for_integer!(i64, type_id, encrust_slice, encrust_rng);
461        }
462    } else if type_id == TypeId::of::<isize>() {
463        // SAFETY: The `TypeId` check above proves `T::Storage` is `isize`.
464        unsafe {
465            toggle_encrust_for_integer!(isize, type_id, encrust_slice, encrust_rng);
466        }
467    } else if type_id == TypeId::of::<i128>() {
468        // SAFETY:
469        // * The `TypeId` check proves `T::Storage` is `i128`, so the cast preserves element type,
470        //   alignment, and length.
471        // * The original slice is shadowed, so no aliasing reference is used while the cast slice
472        //   exists.
473        let encrust_slice = unsafe {
474            slice::from_raw_parts_mut(
475                encrust_slice.as_mut_ptr().cast::<i128>(),
476                encrust_slice.len(),
477            )
478        };
479
480        let mut key = [0u8; 16];
481
482        for num in encrust_slice {
483            encrust_rng.fill_bytes(&mut key);
484            *num ^= i128::from_le_bytes(key);
485        }
486    } else if type_id == TypeId::of::<u16>() {
487        // SAFETY: The `TypeId` check above proves `T::Storage` is `u16`.
488        unsafe {
489            toggle_encrust_for_integer!(u16, type_id, encrust_slice, encrust_rng);
490        }
491    } else if type_id == TypeId::of::<u32>() {
492        // SAFETY: The `TypeId` check above proves `T::Storage` is `u32`.
493        unsafe {
494            toggle_encrust_for_integer!(u32, type_id, encrust_slice, encrust_rng);
495        }
496    } else if type_id == TypeId::of::<u64>() {
497        // SAFETY: The `TypeId` check above proves `T::Storage` is `u64`.
498        unsafe {
499            toggle_encrust_for_integer!(u64, type_id, encrust_slice, encrust_rng);
500        }
501    } else if type_id == TypeId::of::<usize>() {
502        // SAFETY: The `TypeId` check above proves `T::Storage` is `usize`.
503        unsafe {
504            toggle_encrust_for_integer!(usize, type_id, encrust_slice, encrust_rng);
505        }
506    } else if type_id == TypeId::of::<u128>() {
507        // SAFETY:
508        // * The `TypeId` check proves `T::Storage` is `u128`, so the cast preserves element type,
509        //   alignment, and length.
510        // * The original slice is shadowed, so no aliasing reference is used while the cast slice
511        //   exists.
512        let encrust_slice = unsafe {
513            slice::from_raw_parts_mut(
514                encrust_slice.as_mut_ptr().cast::<u128>(),
515                encrust_slice.len(),
516            )
517        };
518
519        let mut key = [0u8; 16];
520
521        for num in encrust_slice {
522            encrust_rng.fill_bytes(&mut key);
523            *num ^= u128::from_le_bytes(key);
524        }
525    } else {
526        // Fallback to encrust each item separately if we have no special optimization.
527        for element in encrust_slice {
528            <T as Encrust>::toggle_encrust(element, encrust_rng);
529        }
530    }
531}
532
533/// `toggle_encrust` for `u8` slices.
534///
535/// This is handled as a special case as it is by far the most likely slice used with encrust, and
536/// the crate author wants to avoid increasing the entropy of `u8` slices by too much.
537fn u8_slice_toggle_encrust(encrust_slice: &mut [u8], encrust_rng: &mut impl Rng) {
538    let mut shuffle_indices: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
539
540    for chunk in encrust_slice.chunks_mut(16) {
541        let mut key = encrust_rng.next_u64().to_le_bytes();
542
543        // Mask to 3 bits per key byte (8 distinct values) to limit entropy increase.
544        for byte in &mut key {
545            *byte &= 0b00_01_10_01;
546        }
547
548        // Store how many numbers we swap to avoid attempting to access out of bounds in `chunk`.
549        let (n_shuffle_indices, is_odd) = if chunk.len() < 16 {
550            // If we are on the last chunk with less than 16 bytes, we need to make sure that we
551            // only get indices in range while swapping. If we have an odd number of bytes, we do
552            // not shuffle the last byte as the current approach can only shuffle pairs of bytes.
553
554            let (shuffle_count, is_odd) = if (chunk.len() % 2) == 0 {
555                (chunk.len(), false)
556            } else {
557                (chunk.len() - 1, true)
558            };
559
560            for (i, elem) in shuffle_indices.iter_mut().enumerate().take(shuffle_count) {
561                *elem = u8::try_from(i).unwrap();
562            }
563
564            shuffle_indices[0..shuffle_count].shuffle(encrust_rng);
565
566            (shuffle_count, is_odd)
567        } else {
568            shuffle_indices.shuffle(encrust_rng);
569
570            (chunk.len(), false)
571        };
572
573        // For each shuffled index pair, transform the chunk: add/subtract key byte, then swap.
574        // This order ensures the transformation is idempotent when applied with the same seed.
575        for (pair, key) in shuffle_indices[..n_shuffle_indices].chunks(2).zip(key) {
576            let i = usize::from(pair[0]);
577            let j = usize::from(pair[1]);
578
579            chunk[i] = chunk[i].wrapping_add(key);
580            chunk[j] = chunk[j].wrapping_sub(key);
581            chunk.swap(i, j);
582        }
583
584        if is_odd {
585            *chunk.last_mut().unwrap() ^= key.last().unwrap();
586        }
587    }
588}
589
590#[cfg(test)]
591mod tests {
592    use super::*;
593
594    const TEST_STRING: &str = "The quick brown fox jumps over the lazy dog😊";
595
596    const SEED: u64 = 0x2357_bd11_1317_1d1f;
597
598    macro_rules! test_ints {
599        ( $( $t:ty => [$($value:expr),+ $(,)?] ),* $(,)? ) => {
600            $(
601                $(
602                    {
603                        let mut encrusted = Encrusted::<$t>::new($value, SEED);
604
605                        {
606                            let decrusted = encrusted.decrust();
607                            assert_eq!(*decrusted, $value);
608                        }
609
610                        {
611                            let decrusted = encrusted.decrust();
612                            assert_eq!(*decrusted, $value);
613                        }
614                    }
615                )+
616
617                #[cfg(feature = "macros")]
618                {
619                    $(
620                        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
621                        let mut encrusted_data: $t = $value;
622                        encrusted_data.toggle_encrust(&mut encrust_rng);
623
624                        // SAFETY: `toggle_encrust` is called before using `encrusted_data` in
625                        // `from_encrusted_data`.
626                        let mut encrusted = unsafe {
627                            Encrusted::<$t>::from_encrusted_data(encrusted_data, SEED)
628                        };
629
630                        assert_ne!(encrusted.data, $value);
631
632                        {
633                            let decrusted = encrusted.decrust();
634                            assert_eq!(*decrusted, $value);
635                        }
636
637                        assert_ne!(encrusted.data, $value);
638                    )+
639                }
640            )*
641        };
642    }
643
644    macro_rules! test_int_arrays {
645        ( $( $t:ty ),* ) => {
646            $(
647                let zero_array: [$t; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
648                {
649                    let mut encrusted = Encrusted::<[$t; 9]>::new(zero_array, SEED);
650                    assert_ne!(encrusted.data, zero_array);
651
652                    {
653                        let decrusted = encrusted.decrust();
654                        assert_eq!(*decrusted, zero_array);
655                    }
656
657                    assert_ne!(encrusted.data, zero_array);
658                }
659
660                #[cfg(feature = "macros")]
661                {
662                    let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
663                    let mut encrusted_data: [$t; 9] = zero_array;
664                    encrusted_data.toggle_encrust(&mut encrust_rng);
665
666                    // SAFETY: `toggle_encrust` is called before using `encrusted_data` in
667                    // `from_encrusted_data`.
668                    let mut encrusted = unsafe {
669                        Encrusted::<[$t; 9]>::from_encrusted_data(encrusted_data, SEED)
670                    };
671
672                    assert_ne!(encrusted.data, zero_array);
673
674                    {
675                        let decrusted = encrusted.decrust();
676                        assert_eq!(*decrusted, zero_array);
677                    }
678
679                    assert_ne!(encrusted.data, zero_array);
680                }
681            )*
682        };
683    }
684
685    #[test]
686    fn test_ints() {
687        test_ints!(
688            u8 => [0u8, 1u8, u8::MAX],
689            i8 => [0i8, 1i8, -1i8, i8::MIN, i8::MAX],
690            u16 => [0u16, 1u16, u16::MAX],
691            i16 => [0i16, 1i16, -1i16, i16::MIN, i16::MAX],
692            u32 => [0u32, 1u32, u32::MAX],
693            i32 => [0i32, 1i32, -1i32, i32::MIN, i32::MAX],
694            u64 => [0u64, 1u64, u64::MAX],
695            i64 => [0i64, 1i64, -1i64, i64::MIN, i64::MAX],
696            u128 => [0u128, 1u128, u128::MAX],
697            i128 => [0i128, 1i128, -1i128, i128::MIN, i128::MAX],
698            usize => [0usize, 1usize, usize::MAX],
699            isize => [0isize, 1isize, -1isize, isize::MIN, isize::MAX],
700        );
701    }
702
703    #[test]
704    fn test_int_arrays() {
705        test_int_arrays!(
706            u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize
707        );
708    }
709
710    #[test]
711    fn test_strings() {
712        let mut encrusted = Encrusted::new(TEST_STRING.to_owned(), SEED);
713        assert_ne!(encrusted.data, TEST_STRING.as_bytes());
714
715        {
716            let decrusted = encrusted.decrust();
717            assert_eq!(&*decrusted, TEST_STRING);
718        }
719
720        assert_ne!(encrusted.data, TEST_STRING.as_bytes());
721    }
722
723    #[test]
724    fn test_string_lengths_around_u8_chunk_boundaries() {
725        for len in [0, 1, 2, 7, 8, 9, 15, 16, 17, 45] {
726            let string = "a".repeat(len);
727            let mut encrusted = Encrusted::new(string.clone(), SEED);
728
729            {
730                let decrusted = encrusted.decrust();
731                assert_eq!(&*decrusted, string);
732            }
733
734            {
735                let decrusted = encrusted.decrust();
736                assert_eq!(&*decrusted, string);
737            }
738        }
739    }
740
741    #[cfg(feature = "macros")]
742    #[test]
743    fn test_strings_from_encrusted() {
744        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
745
746        let mut encrusted_string = TEST_STRING.to_owned().into_bytes();
747
748        // SAFETY: Testing `from_encrusted_data` requires pre-encrusted data. The storage below is
749        // toggled exactly once with the same seed before it is passed to `from_encrusted_data`.
750        let mut encrusted = unsafe {
751            <String as Encrust>::toggle_encrust(&mut encrusted_string, &mut encrust_rng);
752            Encrusted::<String>::from_encrusted_data(encrusted_string, SEED)
753        };
754
755        assert_ne!(encrusted.data, TEST_STRING.as_bytes());
756
757        {
758            let decrusted = encrusted.decrust();
759            assert_eq!(&*decrusted, TEST_STRING);
760        }
761
762        assert_ne!(encrusted.data, TEST_STRING.as_bytes());
763    }
764
765    #[test]
766    fn test_arrays() {
767        let orig_array: [u8; 45] = [
768            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
769            24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
770        ];
771
772        let mut encrusted = Encrusted::new(orig_array, SEED);
773        assert_ne!(encrusted.data, orig_array);
774
775        {
776            let decrusted = encrusted.decrust();
777            assert_eq!(*decrusted, orig_array);
778        }
779
780        assert_ne!(encrusted.data, orig_array);
781    }
782
783    #[test]
784    fn test_arrays_from_encrusted() {
785        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
786        let orig_array: [u8; 45] = [
787            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
788            24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
789        ];
790
791        let mut encrusted_array = orig_array;
792
793        // SAFETY: Testing `from_encrusted_data` requires pre-encrusted data. The storage below is
794        // toggled exactly once with the same seed before it is passed to `from_encrusted_data`.
795        let mut encrusted = unsafe {
796            <[u8; 45] as Encrust>::toggle_encrust(&mut encrusted_array, &mut encrust_rng);
797            Encrusted::<[u8; 45]>::from_encrusted_data(encrusted_array, SEED)
798        };
799
800        assert_ne!(encrusted.data, orig_array);
801
802        {
803            let decrusted = encrusted.decrust();
804            assert_eq!(*decrusted, orig_array);
805        }
806
807        assert_ne!(encrusted.data, orig_array);
808    }
809
810    #[test]
811    fn test_vecs() {
812        let orig_vec = TEST_STRING.as_bytes().to_vec();
813
814        let mut encrusted = Encrusted::new(orig_vec.clone(), SEED);
815        assert_ne!(encrusted.data, orig_vec);
816
817        {
818            let decrusted = encrusted.decrust();
819            assert_eq!(*decrusted, orig_vec);
820        }
821
822        assert_ne!(encrusted.data, orig_vec);
823    }
824
825    #[cfg(feature = "macros")]
826    #[test]
827    fn test_vecs_from_encrusted() {
828        let mut encrust_rng = Xoshiro256PlusPlus::seed_from_u64(SEED);
829        let orig_vec = TEST_STRING.as_bytes().to_vec();
830
831        let mut encrusted_vec = orig_vec.clone();
832
833        // SAFETY: Testing `from_encrusted_data` requires pre-encrusted data. The storage below is
834        // toggled exactly once with the same seed before it is passed to `from_encrusted_data`.
835        let mut encrusted = unsafe {
836            <Vec<u8> as Encrust>::toggle_encrust(&mut encrusted_vec, &mut encrust_rng);
837            Encrusted::<Vec<u8>>::from_encrusted_data(encrusted_vec, SEED)
838        };
839
840        assert_ne!(encrusted.data, orig_vec);
841
842        {
843            let decrusted = encrusted.decrust();
844            assert_eq!(*decrusted, orig_vec);
845        }
846
847        assert_ne!(encrusted.data, orig_vec);
848    }
849
850    #[test]
851    fn test_reseed() {
852        let num = 828_627_825_u64;
853        let mut encrusted = Encrusted::new(num, SEED);
854        let new_seed = SEED ^ 0xffff_ffff_ffff_ffff;
855
856        encrusted.reseed(new_seed);
857
858        {
859            let decrusted = encrusted.decrust();
860            assert_eq!(*decrusted, num);
861        }
862    }
863
864    #[derive(Clone, Copy, Debug, PartialEq, Eq, Zeroize)]
865    struct CustomInPlaceEncrust(u32);
866
867    impl InPlaceEncrust for CustomInPlaceEncrust {
868        fn toggle_encrust(&mut self, encrust_rng: &mut impl Rng) {
869            self.0 ^= encrust_rng.next_u32();
870        }
871    }
872
873    #[test]
874    fn vec_of_custom_type_uses_fallback_slice_path() {
875        let values = vec![
876            CustomInPlaceEncrust(0),
877            CustomInPlaceEncrust(1),
878            CustomInPlaceEncrust(u32::MAX),
879            CustomInPlaceEncrust(0xfeed_beef),
880        ];
881
882        let values_clone = values.clone();
883
884        let mut encrusted = Encrusted::new(values, SEED);
885
886        assert_ne!(encrusted.data, values_clone);
887
888        let decrusted = encrusted.decrust();
889        assert_eq!(&*decrusted, &values_clone);
890    }
891
892    /// Test to make sure that a previously encrusted object can be decrusted with the current
893    /// version of `encrust`.
894    #[cfg(feature = "macros")]
895    #[test]
896    fn ensure_encrust_has_not_changed() {
897        // SAFETY: This storage was captured from a valid `String` encrusted once with the matching
898        // seed. It is only accessed after `decrust` restores the valid UTF-8 bytes.
899        let mut test_string = unsafe {
900            Encrusted::<String>::from_encrusted_data(
901                vec![
902                    114u8, 87u8, 102u8, 107u8, 117u8, 113u8, 32u8, 110u8, 32u8, 97u8, 33u8, 84u8,
903                    128u8, 118u8, 99u8, 105u8, 112u8, 92u8, 110u8, 106u8, 32u8, 120u8, 128u8,
904                    109u8, 142u8, 87u8, 56u8, 91u8, 124u8, 16u8, 130u8, 93u8, 119u8, 84u8, 24u8,
905                    125u8, 91u8, 121u8, 122u8, 40u8, 106u8, 96u8, 161u8, 175u8, 224u8, 94u8, 146u8,
906                ],
907                #[allow(
908                    clippy::unreadable_literal,
909                    reason = "Arbitrary number chosen at random with no further meaning."
910                )]
911                5233902475398815152u64,
912            )
913        };
914
915        let decrusted_test_string = test_string.decrust();
916        assert_eq!(decrusted_test_string.as_bytes(), TEST_STRING.as_bytes());
917    }
918}