secure_gate/fixed.rs
1// src/fixed.rs
2//! Stack-allocated, zero-cost secure wrappers for fixed-size secrets.
3//!
4//! `Fixed<T>` is a transparent wrapper around any type `T` that lives entirely on the stack.
5//! It provides:
6//! - Zero-cost abstraction (`Deref`/`DerefMut`)
7//! - Automatic redaction in `Debug`
8//! - Full `expose_secret()` API (the canonical way to access the secret)
9//! - Special ergonomics for `[u8; N]` arrays (crypto keys, nonces, etc.)
10
11use core::convert::From;
12use core::ops::{Deref, DerefMut};
13
14/// A zero-cost, stack-allocated wrapper for sensitive data.
15///
16/// `Fixed<T>` stores its value directly in the struct (no heap allocation).
17/// It behaves exactly like `T` thanks to `Deref`/`DerefMut`, but:
18/// - Prints as `[REDACTED]` in debug output
19/// - Provides `.expose_secret()` as the explicit, loud way to access the secret
20/// - Works perfectly with `fixed_alias!` for beautiful type aliases
21///
22/// # Examples
23///
24/// ```
25/// use secure_gate::{Fixed, fixed_alias};
26///
27/// // Define a beautiful type alias (this is the recommended pattern)
28/// fixed_alias!(Aes256Key, 32);
29///
30/// // Generate a random key and convert it directly
31/// let raw_key = [42u8; 32]; // In real code: use rand::Rng::gen()
32/// let key: Aes256Key = raw_key.into();
33///
34/// // Access the bytes
35/// let bytes: &[u8] = key.expose_secret();
36/// assert_eq!(bytes.len(), 32);
37/// ```
38pub struct Fixed<T>(pub T);
39
40impl<T> Fixed<T> {
41 /// Create a new `Fixed` wrapper around a value.
42 ///
43 /// This is usually not called directly — prefer `fixed_alias!` + `.into()`.
44 #[inline(always)]
45 pub const fn new(value: T) -> Self {
46 Fixed(value)
47 }
48}
49
50impl<T> Deref for Fixed<T> {
51 type Target = T;
52
53 #[inline(always)]
54 fn deref(&self) -> &T {
55 &self.0
56 }
57}
58
59impl<T> DerefMut for Fixed<T> {
60 #[inline(always)]
61 fn deref_mut(&mut self) -> &mut T {
62 &mut self.0
63 }
64}
65
66/// Convert a byte slice into a fixed-size secret.
67///
68/// Panics if the slice length doesn't match exactly.
69///
70/// # Panics
71///
72/// Panics with "slice length mismatch" if `bytes.len() != N`.
73impl<const N: usize> Fixed<[u8; N]> {
74 #[inline]
75 pub fn from_slice(bytes: &[u8]) -> Self {
76 assert_eq!(bytes.len(), N, "slice length mismatch");
77 let mut arr = [0u8; N];
78 arr.copy_from_slice(&bytes[..N]);
79 Self::new(arr)
80 }
81}
82
83/// Convert a raw array into a fixed-size secret.
84///
85/// This enables the beautiful `let key: Aes256Key = rng.gen().into();` pattern.
86impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]> {
87 #[inline(always)]
88 fn from(arr: [u8; N]) -> Self {
89 Self::new(arr)
90 }
91}
92
93/// Borrow as a byte slice — useful for crypto APIs.
94impl<const N: usize> AsRef<[u8]> for Fixed<[u8; N]> {
95 #[inline(always)]
96 fn as_ref(&self) -> &[u8] {
97 &self.0
98 }
99}
100
101/// Mutably borrow as a byte slice — e.g. for key scheduling.
102impl<const N: usize> AsMut<[u8]> for Fixed<[u8; N]> {
103 #[inline(always)]
104 fn as_mut(&mut self) -> &mut [u8] {
105 &mut self.0
106 }
107}
108
109/// All `Fixed<T>` values print as `[REDACTED]` to prevent accidental leakage.
110impl<T> core::fmt::Debug for Fixed<T> {
111 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112 f.write_str("[REDACTED]")
113 }
114}
115
116impl<T> Fixed<T> {
117 /// Access the secret value immutably.
118 ///
119 /// This is the **canonical** way to read the secret — loud and clear.
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use secure_gate::{Fixed, fixed_alias};
125 ///
126 /// fixed_alias!(Aes256Key, 32);
127 ///
128 /// let key: Aes256Key = [1u8; 32].into();
129 /// let bytes: &[u8] = key.expose_secret();
130 /// assert_eq!(bytes[0], 1);
131 /// ```
132 #[inline(always)]
133 pub fn expose_secret(&self) -> &T {
134 &self.0
135 }
136
137 /// Access the secret value mutably.
138 ///
139 /// Use this for in-place operations like key derivation.
140 #[inline(always)]
141 pub fn expose_secret_mut(&mut self) -> &mut T {
142 &mut self.0
143 }
144
145 /// **Deprecated**: Use [`expose_secret`] instead.
146 ///
147 /// Kept for backward compatibility with v0.5.x.
148 #[deprecated(since = "0.5.5", note = "use `expose_secret` instead")]
149 #[doc(hidden)]
150 #[inline(always)]
151 pub fn view(&self) -> &T {
152 self.expose_secret()
153 }
154
155 /// **Deprecated**: Use [`expose_secret_mut`] instead.
156 #[deprecated(since = "0.5.5", note = "use `expose_secret_mut` instead")]
157 #[doc(hidden)]
158 #[inline(always)]
159 pub fn view_mut(&mut self) -> &mut T {
160 self.expose_secret_mut()
161 }
162
163 /// Consume the wrapper and return the inner value.
164 ///
165 /// This is useful when you need to pass the secret to a function that takes ownership.
166 #[inline(always)]
167 pub fn into_inner(self) -> T {
168 self.0
169 }
170}
171
172/// `Clone` is implemented when the inner type is `Clone`.
173impl<T: Clone> Clone for Fixed<T> {
174 #[inline(always)]
175 fn clone(&self) -> Self {
176 Self(self.0.clone())
177 }
178}
179
180/// `Copy` is implemented for small fixed-size byte arrays.
181impl<const N: usize> Copy for Fixed<[u8; N]> where [u8; N]: Copy {}