Skip to main content

dryoc/
types.rs

1#[cfg(feature = "alloc")]
2use alloc::vec::Vec;
3use core::fmt;
4use core::ops::{Deref, DerefMut};
5
6use subtle::ConstantTimeEq;
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9use crate::rng::copy_randombytes;
10use crate::utils::zeroize_bytes;
11
12/// A stack-allocated fixed-length byte array for working with data, with
13/// optional [Serde](https://serde.rs) features.
14#[derive(Clone)]
15pub struct StackByteArray<const LENGTH: usize>([u8; LENGTH]);
16
17impl<const LENGTH: usize> Zeroize for StackByteArray<LENGTH> {
18    fn zeroize(&mut self) {
19        zeroize_bytes(&mut self.0);
20    }
21}
22
23impl<const LENGTH: usize> Drop for StackByteArray<LENGTH> {
24    fn drop(&mut self) {
25        self.zeroize();
26    }
27}
28
29impl<const LENGTH: usize> ZeroizeOnDrop for StackByteArray<LENGTH> {}
30
31impl<const LENGTH: usize> fmt::Debug for StackByteArray<LENGTH> {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        f.debug_struct("StackByteArray")
34            .field("len", &LENGTH)
35            .field("contents", &"[REDACTED]")
36            .finish()
37    }
38}
39
40impl<const LENGTH: usize> PartialEq for StackByteArray<LENGTH> {
41    fn eq(&self, other: &Self) -> bool {
42        self.0.ct_eq(&other.0).into()
43    }
44}
45
46impl<const LENGTH: usize> Eq for StackByteArray<LENGTH> {}
47
48/// Fixed-length byte array.
49///
50/// Implemented only by fixed-size storage such as `[u8; LENGTH]`,
51/// [`StackByteArray`], and the protected heap arrays, so the length is checked
52/// at compile time. Convert runtime-length input with
53/// [`StackByteArray::try_from`] or `<[u8; LENGTH]>::try_from`, which reject any
54/// length other than `LENGTH`.
55///
56/// Slices and vectors are not fixed-length byte arrays:
57#[cfg_attr(feature = "alloc", doc = "```compile_fail,E0277")]
58#[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
59/// use dryoc::dryocsecretbox::{DryocSecretBox, Nonce};
60/// use dryoc::types::*;
61///
62/// let key = vec![0u8; 32];
63/// let nonce = Nonce::generate();
64/// DryocSecretBox::encrypt_to_vecbox(b"hello", &nonce, &key);
65/// ```
66///
67#[cfg_attr(feature = "alloc", doc = "```compile_fail,E0277")]
68#[cfg_attr(not(feature = "alloc"), doc = "```ignore")]
69/// use dryoc::dryocsecretbox::{DryocSecretBox, Nonce};
70/// use dryoc::types::*;
71///
72/// let key: &[u8] = &[0u8; 32];
73/// let nonce = Nonce::generate();
74/// DryocSecretBox::encrypt_to_vecbox(b"hello", &nonce, &key);
75/// ```
76pub trait ByteArray<const LENGTH: usize>: Bytes {
77    /// Returns a reference to the underlying fixed-length byte array.
78    fn as_array(&self) -> &[u8; LENGTH];
79}
80
81/// Arbitrary-length array of bytes.
82pub trait Bytes {
83    /// Returns a slice of the underlying bytes.
84    fn as_slice(&self) -> &[u8];
85    /// Shorthand to retrieve the underlying length of the byte array.
86    fn len(&self) -> usize;
87    /// Returns true if the array is empty.
88    fn is_empty(&self) -> bool;
89}
90
91/// Fixed-length mutable byte array.
92pub trait MutByteArray<const LENGTH: usize>: ByteArray<LENGTH> + MutBytes {
93    /// Returns a mutable reference to the underlying fixed-length byte array.
94    fn as_mut_array(&mut self) -> &mut [u8; LENGTH];
95}
96
97/// Fixed-length byte array that can be created and initialized.
98pub trait NewByteArray<const LENGTH: usize>: MutByteArray<LENGTH> + NewBytes {
99    /// Returns a new fixed-length byte array, initialized with zeroes.
100    #[must_use]
101    fn new_byte_array() -> Self;
102    /// Returns a new fixed-length byte array, filled with random values.
103    #[must_use]
104    fn generate() -> Self;
105}
106
107/// Arbitrary-length array of mutable bytes.
108pub trait MutBytes: Bytes {
109    /// Returns a mutable slice to the underlying bytes.
110    fn as_mut_slice(&mut self) -> &mut [u8];
111    /// Copies into the underlying slice from `other`. Panics if lengths do not
112    /// match.
113    fn copy_from_slice(&mut self, other: &[u8]);
114}
115
116/// Arbitrary-length byte array that can be created and initialized.
117pub trait NewBytes: MutBytes {
118    /// Returns a new byte array. Fixed-length types (such as
119    /// [`StackByteArray`], `[u8; LENGTH]` and `HeapByteArray`) hold `LENGTH`
120    /// zero bytes; resizable types (such as `Vec<u8>` and `HeapBytes`) are
121    /// empty.
122    #[must_use]
123    fn new_bytes() -> Self;
124}
125
126/// A byte array which can be resized.
127pub trait ResizableBytes {
128    /// Resizes `self` with `new_len` elements, populating new values with
129    /// `value`.
130    fn resize(&mut self, new_len: usize, value: u8);
131}
132
133/// Returns a new byte buffer filled with random data.
134pub(crate) fn gen_bytes<B: NewBytes + MutBytes>() -> B {
135    let mut res = B::new_bytes();
136    copy_randombytes(res.as_mut_slice());
137    res
138}
139
140/// Returns a new `Output` holding `prefix || data`.
141pub(crate) fn concat_bytes<Output: NewBytes + ResizableBytes>(
142    prefix: &[u8],
143    data: &[u8],
144) -> Output {
145    let mut out = Output::new_bytes();
146    out.resize(prefix.len() + data.len(), 0);
147    let s = out.as_mut_slice();
148    s[..prefix.len()].copy_from_slice(prefix);
149    s[prefix.len()..].copy_from_slice(data);
150    out
151}
152
153impl<const LENGTH: usize> ByteArray<LENGTH> for StackByteArray<LENGTH> {
154    #[inline]
155    fn as_array(&self) -> &[u8; LENGTH] {
156        &self.0
157    }
158}
159
160impl<const LENGTH: usize> Bytes for StackByteArray<LENGTH> {
161    #[inline]
162    fn as_slice(&self) -> &[u8] {
163        &self.0
164    }
165
166    #[inline]
167    fn len(&self) -> usize {
168        self.0.len()
169    }
170
171    #[inline]
172    fn is_empty(&self) -> bool {
173        self.0.is_empty()
174    }
175}
176
177impl<const LENGTH: usize> NewBytes for StackByteArray<LENGTH> {
178    fn new_bytes() -> Self {
179        Self::default()
180    }
181}
182
183impl<const LENGTH: usize> NewByteArray<LENGTH> for StackByteArray<LENGTH> {
184    fn new_byte_array() -> Self {
185        Self::default()
186    }
187
188    /// Returns a new byte array filled with random data.
189    fn generate() -> Self {
190        gen_bytes()
191    }
192}
193
194impl<const LENGTH: usize> MutByteArray<LENGTH> for StackByteArray<LENGTH> {
195    #[inline]
196    fn as_mut_array(&mut self) -> &mut [u8; LENGTH] {
197        &mut self.0
198    }
199}
200
201impl<const LENGTH: usize> MutBytes for StackByteArray<LENGTH> {
202    #[inline]
203    fn as_mut_slice(&mut self) -> &mut [u8] {
204        &mut self.0
205    }
206
207    fn copy_from_slice(&mut self, other: &[u8]) {
208        self.0.copy_from_slice(other)
209    }
210}
211
212impl<const LENGTH: usize> NewBytes for [u8; LENGTH] {
213    fn new_bytes() -> Self {
214        [0u8; LENGTH]
215    }
216}
217
218impl<const LENGTH: usize> NewByteArray<LENGTH> for [u8; LENGTH] {
219    fn new_byte_array() -> Self {
220        [0u8; LENGTH]
221    }
222
223    /// Returns a new byte array filled with random data.
224    fn generate() -> Self {
225        gen_bytes()
226    }
227}
228
229impl<const LENGTH: usize> MutByteArray<LENGTH> for [u8; LENGTH] {
230    #[inline]
231    fn as_mut_array(&mut self) -> &mut [u8; LENGTH] {
232        self
233    }
234}
235
236impl<const LENGTH: usize> MutBytes for [u8; LENGTH] {
237    #[inline]
238    fn as_mut_slice(&mut self) -> &mut [u8] {
239        self
240    }
241
242    fn copy_from_slice(&mut self, other: &[u8]) {
243        <[u8]>::copy_from_slice(self, other)
244    }
245}
246
247#[cfg(feature = "alloc")]
248impl Bytes for Vec<u8> {
249    #[inline]
250    fn as_slice(&self) -> &[u8] {
251        self.as_slice()
252    }
253
254    #[inline]
255    fn len(&self) -> usize {
256        <[u8]>::len(self)
257    }
258
259    #[inline]
260    fn is_empty(&self) -> bool {
261        <[u8]>::is_empty(self)
262    }
263}
264
265#[cfg(feature = "alloc")]
266impl NewBytes for Vec<u8> {
267    fn new_bytes() -> Self {
268        vec![]
269    }
270}
271
272#[cfg(feature = "alloc")]
273impl MutBytes for Vec<u8> {
274    #[inline]
275    fn as_mut_slice(&mut self) -> &mut [u8] {
276        self.as_mut_slice()
277    }
278
279    fn copy_from_slice(&mut self, other: &[u8]) {
280        <[u8]>::copy_from_slice(self, other)
281    }
282}
283
284#[cfg(feature = "alloc")]
285impl ResizableBytes for Vec<u8> {
286    fn resize(&mut self, new_len: usize, value: u8) {
287        self.resize(new_len, value);
288    }
289}
290
291/// Implements [`Bytes`] for a slice-like type by delegating to `[u8]`.
292///
293/// Shared by `[u8]`, `&[u8]`, and `&mut [u8]`, whose bodies are identical:
294/// each derefs to a byte slice for every method.
295macro_rules! impl_bytes_for_slice {
296    ($($t:ty),*) => {
297        $(
298            impl Bytes for $t {
299                #[inline]
300                fn as_slice(&self) -> &[u8] {
301                    self
302                }
303
304                #[inline]
305                fn len(&self) -> usize {
306                    <[u8]>::len(self)
307                }
308
309                #[inline]
310                fn is_empty(&self) -> bool {
311                    <[u8]>::is_empty(self)
312                }
313            }
314        )*
315    };
316}
317
318impl_bytes_for_slice!([u8], &[u8], &mut [u8]);
319
320/// Implements [`Bytes`] for a fixed-size byte array (or reference to one),
321/// delegating to `$this` (e.g. `a` or `a.deref()`).
322macro_rules! impl_bytes_for_array {
323    ($($(#[$meta:meta])* $t:ty, |$a:ident| $this:expr;)*) => {$(
324        $(#[$meta])*
325        impl<const LENGTH: usize> Bytes for $t {
326            #[inline]
327            fn as_slice(&self) -> &[u8] {
328                let $a = self;
329                $this
330            }
331
332            #[inline]
333            fn len(&self) -> usize {
334                let $a = self;
335                <[u8]>::len($this)
336            }
337
338            #[inline]
339            fn is_empty(&self) -> bool {
340                let $a = self;
341                <[u8]>::is_empty($this)
342            }
343        }
344    )*};
345}
346
347impl_bytes_for_array! {
348    [u8; LENGTH], |a| a;
349    #[allow(suspicious_double_ref_op)] &[u8; LENGTH], |a| a.deref();
350}
351
352impl<const LENGTH: usize> ByteArray<LENGTH> for [u8; LENGTH] {
353    #[inline]
354    fn as_array(&self) -> &[u8; LENGTH] {
355        self
356    }
357}
358
359impl MutBytes for [u8] {
360    #[inline]
361    fn as_mut_slice(&mut self) -> &mut [u8] {
362        self
363    }
364
365    fn copy_from_slice(&mut self, other: &[u8]) {
366        self.copy_from_slice(other)
367    }
368}
369
370impl<const LENGTH: usize> core::convert::AsRef<[u8; LENGTH]> for StackByteArray<LENGTH> {
371    fn as_ref(&self) -> &[u8; LENGTH] {
372        &self.0
373    }
374}
375
376impl<const LENGTH: usize> core::convert::AsMut<[u8; LENGTH]> for StackByteArray<LENGTH> {
377    fn as_mut(&mut self) -> &mut [u8; LENGTH] {
378        &mut self.0
379    }
380}
381
382impl<const LENGTH: usize> core::convert::AsRef<[u8]> for StackByteArray<LENGTH> {
383    fn as_ref(&self) -> &[u8] {
384        self.0.as_ref()
385    }
386}
387
388impl<const LENGTH: usize> core::convert::AsMut<[u8]> for StackByteArray<LENGTH> {
389    fn as_mut(&mut self) -> &mut [u8] {
390        self.0.as_mut()
391    }
392}
393
394impl<const LENGTH: usize> Deref for StackByteArray<LENGTH> {
395    type Target = [u8];
396
397    fn deref(&self) -> &Self::Target {
398        &self.0
399    }
400}
401
402impl<const LENGTH: usize> DerefMut for StackByteArray<LENGTH> {
403    fn deref_mut(&mut self) -> &mut Self::Target {
404        &mut self.0
405    }
406}
407
408/// Implements `Index`/`IndexMut` for a byte container: the `usize` impls
409/// yielding `u8` and the six range impls yielding `[u8]`, delegating to the
410/// `$get`/`$get_mut` expressions for the backing bytes (e.g. `s.0` or
411/// `s.as_slice()`).
412macro_rules! impl_slice_index {
413    (impl[$($generics:tt)*] $ty:ty, |$s:ident| $get:expr, |$sm:ident| $get_mut:expr) => {
414        impl<$($generics)*> core::ops::Index<usize> for $ty {
415            type Output = u8;
416
417            #[inline]
418            fn index(&self, index: usize) -> &Self::Output {
419                let $s = self;
420                &$get[index]
421            }
422        }
423        impl<$($generics)*> core::ops::IndexMut<usize> for $ty {
424            #[inline]
425            fn index_mut(&mut self, index: usize) -> &mut Self::Output {
426                let $sm = self;
427                &mut $get_mut[index]
428            }
429        }
430        impl_slice_index!(@ranges impl[$($generics)*] $ty, |$s| $get, |$sm| $get_mut);
431    };
432    (@ranges impl[$($generics:tt)*] $ty:ty, |$s:ident| $get:expr, |$sm:ident| $get_mut:expr) => {
433        impl_slice_index!(@range impl[$($generics)*] $ty, core::ops::Range<usize>, |$s| $get, |$sm| $get_mut);
434        impl_slice_index!(@range impl[$($generics)*] $ty, core::ops::RangeFull, |$s| $get, |$sm| $get_mut);
435        impl_slice_index!(@range impl[$($generics)*] $ty, core::ops::RangeFrom<usize>, |$s| $get, |$sm| $get_mut);
436        impl_slice_index!(@range impl[$($generics)*] $ty, core::ops::RangeInclusive<usize>, |$s| $get, |$sm| $get_mut);
437        impl_slice_index!(@range impl[$($generics)*] $ty, core::ops::RangeTo<usize>, |$s| $get, |$sm| $get_mut);
438        impl_slice_index!(@range impl[$($generics)*] $ty, core::ops::RangeToInclusive<usize>, |$s| $get, |$sm| $get_mut);
439    };
440    (@range impl[$($generics:tt)*] $ty:ty, $range:ty, |$s:ident| $get:expr, |$sm:ident| $get_mut:expr) => {
441        impl<$($generics)*> core::ops::Index<$range> for $ty {
442            type Output = [u8];
443
444            #[inline]
445            fn index(&self, index: $range) -> &Self::Output {
446                let $s = self;
447                &$get[index]
448            }
449        }
450        impl<$($generics)*> core::ops::IndexMut<$range> for $ty {
451            #[inline]
452            fn index_mut(&mut self, index: $range) -> &mut Self::Output {
453                let $sm = self;
454                &mut $get_mut[index]
455            }
456        }
457    };
458}
459
460// Only `protected.rs` imports this macro; it is compiled out on targets
461// without the protected feature.
462#[cfg(any(
463    all(feature = "protected", any(unix, windows)),
464    all(doc, not(doctest), feature = "std")
465))]
466pub(crate) use impl_slice_index;
467
468impl_slice_index!(impl[const LENGTH: usize] StackByteArray<LENGTH>, |s| s.0, |s| s.0);
469
470impl<const LENGTH: usize> Default for StackByteArray<LENGTH> {
471    fn default() -> Self {
472        Self([0u8; LENGTH])
473    }
474}
475
476impl<const LENGTH: usize> From<&[u8; LENGTH]> for StackByteArray<LENGTH> {
477    fn from(src: &[u8; LENGTH]) -> Self {
478        let mut arr = Self::default();
479        arr.0.copy_from_slice(src);
480        arr
481    }
482}
483
484impl<const LENGTH: usize> From<[u8; LENGTH]> for StackByteArray<LENGTH> {
485    fn from(src: [u8; LENGTH]) -> Self {
486        Self::from(&src)
487    }
488}
489
490impl<const LENGTH: usize> TryFrom<&[u8]> for StackByteArray<LENGTH> {
491    type Error = crate::error::Error;
492
493    fn try_from(src: &[u8]) -> Result<Self, Self::Error> {
494        validate_length!(exact LENGTH, src.len(), crate::ErrorContext::Slice);
495        let mut arr = Self::default();
496        arr.0.copy_from_slice(src);
497        Ok(arr)
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504    use crate::utils::test_util::assert_exact_slice_length_error;
505
506    const SRC: [u8; 6] = [10, 20, 30, 40, 50, 60];
507
508    #[test]
509    #[cfg(feature = "alloc")]
510    fn bytes_views_agree_with_the_source_for_every_container() {
511        fn check<B: Bytes + ?Sized>(bytes: &B, expected: &[u8]) {
512            assert_eq!(bytes.as_slice(), expected);
513            assert_eq!(bytes.len(), expected.len());
514            assert_eq!(bytes.is_empty(), expected.is_empty());
515        }
516
517        check(&SRC[..], &SRC);
518        check(&&SRC[..], &SRC);
519        let mut copy = SRC;
520        check(&&mut copy[..], &SRC);
521        check(&SRC.to_vec(), &SRC);
522        check(&SRC, &SRC);
523        check(&&SRC, &SRC);
524        check(&StackByteArray::from(SRC), &SRC);
525
526        check(&[][..], &[]);
527        check(&Vec::<u8>::new(), &[]);
528        check(&[0u8; 0], &[]);
529        check(&StackByteArray::<0>::default(), &[]);
530    }
531
532    #[test]
533    #[cfg(feature = "alloc")]
534    fn mut_bytes_copy_from_slice_and_mutation_reach_the_source() {
535        fn check<B: MutBytes + ?Sized>(bytes: &mut B) {
536            bytes.copy_from_slice(&SRC);
537            assert_eq!(bytes.as_slice(), &SRC);
538            bytes.as_mut_slice()[2] ^= 0xff;
539            assert_eq!(bytes.as_slice()[2], SRC[2] ^ 0xff);
540            assert_eq!(&bytes.as_slice()[3..], &SRC[3..]);
541        }
542
543        let mut vec = vec![0u8; 6];
544        check(&mut vec);
545        assert_eq!(vec[2], SRC[2] ^ 0xff);
546
547        let mut array = [0u8; 6];
548        check(&mut array);
549        assert_eq!(array[2], SRC[2] ^ 0xff);
550
551        let mut slice_backing = [0u8; 6];
552        check(&mut slice_backing[..]);
553        assert_eq!(slice_backing[2], SRC[2] ^ 0xff);
554
555        let mut stack = StackByteArray::<6>::default();
556        check(&mut stack);
557        assert_eq!(stack[2], SRC[2] ^ 0xff);
558    }
559
560    #[test]
561    #[cfg(feature = "alloc")]
562    fn new_bytes_and_new_byte_array_start_zeroed() {
563        assert!(<Vec<u8> as NewBytes>::new_bytes().is_empty());
564        assert_eq!(<[u8; 4] as NewBytes>::new_bytes(), [0; 4]);
565        assert_eq!(<[u8; 4] as NewByteArray<4>>::new_byte_array(), [0; 4]);
566        assert_eq!(
567            <StackByteArray<4> as NewBytes>::new_bytes().as_slice(),
568            &[0; 4]
569        );
570        assert_eq!(
571            <StackByteArray<4> as NewByteArray<4>>::new_byte_array().as_slice(),
572            &[0; 4]
573        );
574    }
575
576    #[test]
577    #[cfg(feature = "alloc")]
578    fn generate_fills_the_whole_fixed_length_array() {
579        let array = <[u8; 32] as NewByteArray<32>>::generate();
580        assert_ne!(array, [0; 32]);
581
582        let stack = <StackByteArray<32> as NewByteArray<32>>::generate();
583        assert_ne!(stack, StackByteArray::<32>::default());
584        assert_ne!(stack.as_slice(), array.as_slice());
585    }
586
587    #[test]
588    #[cfg(feature = "alloc")]
589    fn resizable_vec_concat_preserves_prefix_then_data() {
590        let out: Vec<u8> = concat_bytes(b"ab", b"cde");
591        assert_eq!(out, b"abcde");
592
593        let out: Vec<u8> = concat_bytes(b"", b"xyz");
594        assert_eq!(out, b"xyz");
595
596        let out: Vec<u8> = concat_bytes(b"xyz", b"");
597        assert_eq!(out, b"xyz");
598
599        let mut vec = vec![1u8, 2, 3];
600        ResizableBytes::resize(&mut vec, 5, 9);
601        assert_eq!(vec, [1, 2, 3, 9, 9]);
602        ResizableBytes::resize(&mut vec, 1, 0);
603        assert_eq!(vec, [1]);
604    }
605
606    #[test]
607    fn stack_byte_array_try_from_requires_exact_length() {
608        let ok = StackByteArray::<3>::try_from(&SRC[..3]).expect("exact length");
609        assert_eq!(ok.as_slice(), &SRC[..3]);
610
611        assert_exact_slice_length_error(StackByteArray::<3>::try_from(&SRC[..2]), 2, 3);
612        assert_exact_slice_length_error(StackByteArray::<3>::try_from(&SRC[..4]), 4, 3);
613        assert_exact_slice_length_error(StackByteArray::<3>::try_from(&[][..]), 0, 3);
614        assert_exact_slice_length_error(StackByteArray::<0>::try_from(&SRC[..1]), 1, 0);
615    }
616
617    #[test]
618    fn stack_byte_array_conversions_and_views() {
619        let from_owned = StackByteArray::from(SRC);
620        let from_ref = StackByteArray::from(&SRC);
621        assert_eq!(from_owned, from_ref);
622        assert_eq!(AsRef::<[u8; 6]>::as_ref(&from_owned), &SRC);
623        assert_eq!(AsRef::<[u8]>::as_ref(&from_owned), &SRC);
624        assert_eq!(&*from_owned, &SRC);
625        assert_eq!(from_owned[1], SRC[1]);
626        assert_eq!(&from_owned[1..3], &SRC[1..3]);
627        assert_eq!(&from_owned[..], &SRC);
628        assert_eq!(&from_owned[4..], &SRC[4..]);
629
630        let mut mutated = from_owned.clone();
631        AsMut::<[u8; 6]>::as_mut(&mut mutated)[0] = 1;
632        AsMut::<[u8]>::as_mut(&mut mutated)[1] = 2;
633        mutated[2] = 3;
634        mutated[3..5].copy_from_slice(&[4, 5]);
635        (*mutated)[5] = 6;
636        assert_eq!(mutated.as_slice(), &[1, 2, 3, 4, 5, 6]);
637        assert_ne!(mutated, from_owned);
638        assert_eq!(from_owned.as_slice(), &SRC);
639    }
640
641    #[test]
642    fn stack_byte_array_equality_is_by_value() {
643        let a = StackByteArray::from([1u8, 2, 3, 4]);
644        let mut b = StackByteArray::from([1u8, 2, 3, 4]);
645        assert_eq!(a, b);
646        b[3] = 5;
647        assert_ne!(a, b);
648        assert_ne!(StackByteArray::<4>::default(), a);
649        assert_eq!(
650            StackByteArray::<0>::default(),
651            StackByteArray::<0>::default()
652        );
653    }
654
655    #[test]
656    fn stack_byte_array_zeroize_clears_contents() {
657        let mut bytes = StackByteArray::from([0xabu8; 8]);
658        bytes.zeroize();
659        assert_eq!(bytes.as_slice(), &[0; 8]);
660    }
661
662    #[test]
663    fn stack_byte_array_debug_redacts_contents() {
664        let bytes = StackByteArray::from([0xabu8; 4]);
665        let debug = format!("{bytes:?}");
666
667        assert!(debug.contains("[REDACTED]"));
668        assert!(!debug.contains("171"));
669    }
670}