secure_gate/random/fixed_rng.rs
1// secure-gate/src/random/fixed_rng.rs
2use crate::Fixed;
3use rand::rand_core::OsError;
4use rand::rngs::OsRng;
5use rand::TryRngCore;
6
7#[cfg(any(feature = "encoding-hex", feature = "encoding-base64"))]
8use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
9#[cfg(any(
10 feature = "encoding-hex",
11 feature = "encoding-base64",
12 feature = "encoding-bech32"
13))]
14use bech32::{Bech32, Bech32m, Hrp};
15#[cfg(feature = "encoding-hex")]
16use hex;
17
18/// Fixed-length cryptographically secure random value with encoding methods.
19///
20/// This is a newtype over `Fixed<[u8; N]>` that enforces construction only via secure RNG.
21/// Guarantees freshness — cannot be created from arbitrary bytes.
22///
23/// Requires the "rand" feature.
24///
25/// Supports direct encoding to Hex, Base64, Bech32, and Bech32m via convenience methods.
26///
27/// # Examples
28///
29/// Basic usage:
30/// ```
31/// # #[cfg(feature = "rand")]
32/// # {
33/// use secure_gate::random::FixedRng;
34/// let random: FixedRng<32> = FixedRng::generate();
35/// assert_eq!(random.len(), 32);
36/// # }
37/// ```
38///
39/// With alias:
40/// ```
41/// # #[cfg(feature = "rand")]
42/// # {
43/// use secure_gate::fixed_alias_rng;
44/// fixed_alias_rng!(Nonce, 24);
45/// let nonce = Nonce::generate();
46/// # }
47/// ```
48pub struct FixedRng<const N: usize>(Fixed<[u8; N]>);
49
50impl<const N: usize> FixedRng<N> {
51 /// Generate fresh random bytes using the OS RNG.
52 ///
53 /// Uses `rand::rngs::OsRng` directly for maximum throughput.
54 /// Panics if the RNG fails (rare, but correct for crypto code).
55 ///
56 /// # Example
57 ///
58 /// ```
59 /// # #[cfg(feature = "rand")]
60 /// # {
61 /// use secure_gate::random::FixedRng;
62 /// let random = FixedRng::<16>::generate();
63 /// assert!(!random.is_empty());
64 /// # }
65 /// ```
66 pub fn generate() -> Self {
67 let mut bytes = [0u8; N];
68 OsRng
69 .try_fill_bytes(&mut bytes)
70 .expect("OsRng failed — this should never happen on supported platforms");
71 Self(Fixed::new(bytes))
72 }
73
74 /// Try to generate fresh random bytes using the OS RNG.
75 ///
76 /// Returns an error if the RNG fails.
77 ///
78 /// # Example
79 ///
80 /// ```
81 /// # #[cfg(feature = "rand")]
82 /// # {
83 /// use secure_gate::random::FixedRng;
84 /// let random: Result<FixedRng<32>, rand::rand_core::OsError> = FixedRng::try_generate();
85 /// assert!(random.is_ok());
86 /// # }
87 /// ```
88 pub fn try_generate() -> Result<Self, OsError> {
89 let mut bytes = [0u8; N];
90 OsRng
91 .try_fill_bytes(&mut bytes)
92 .map(|_| Self(Fixed::new(bytes)))
93 }
94
95 /// Expose the random bytes for read-only access.
96 ///
97 /// # Example
98 ///
99 /// ```
100 /// # #[cfg(feature = "rand")]
101 /// # {
102 /// use secure_gate::random::FixedRng;
103 /// let random = FixedRng::<4>::generate();
104 /// let bytes = random.expose_secret();
105 /// # }
106 /// ```
107 #[inline(always)]
108 pub fn expose_secret(&self) -> &[u8; N] {
109 self.0.expose_secret()
110 }
111
112 /// Returns the fixed length in bytes.
113 #[inline(always)]
114 pub const fn len(&self) -> usize {
115 N
116 }
117
118 /// Returns `true` if the length is zero.
119 #[inline(always)]
120 pub const fn is_empty(&self) -> bool {
121 N == 0
122 }
123
124 /// Consume the wrapper and return the inner `Fixed<[u8; N]>`.
125 ///
126 /// This transfers ownership without exposing the secret bytes.
127 /// The returned `Fixed` retains all security guarantees (zeroize, etc.).
128 ///
129 /// # Example
130 ///
131 /// ```
132 /// # #[cfg(feature = "rand")]
133 /// # {
134 /// use secure_gate::{Fixed, random::FixedRng};
135 /// let random = FixedRng::<32>::generate();
136 /// let fixed: Fixed<[u8; 32]> = random.into_inner();
137 /// // Can now use fixed.expose_secret() as needed
138 /// # }
139 /// ```
140 #[inline(always)]
141 pub fn into_inner(self) -> Fixed<[u8; N]> {
142 self.0
143 }
144}
145
146impl<const N: usize> core::fmt::Debug for FixedRng<N> {
147 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
148 f.write_str("[REDACTED]")
149 }
150}
151
152#[cfg(all(feature = "rand", feature = "encoding-bech32"))]
153impl<const N: usize> FixedRng<N> {
154 /// Consume self and return the random bytes as a validated Bech32 string with the specified HRP.
155 ///
156 /// The raw bytes are zeroized immediately after encoding (via drop of `self`).
157 ///
158 /// # Panics
159 ///
160 /// Panics if the HRP is invalid or encoding fails (should never happen for valid random bytes).
161 pub fn into_bech32(self, hrp: &str) -> crate::encoding::bech32::Bech32String {
162 let hrp = Hrp::parse(hrp).expect("invalid HRP");
163 let encoded = bech32::encode::<Bech32>(hrp, self.expose_secret())
164 .expect("encoding valid random bytes cannot fail");
165
166 crate::encoding::bech32::Bech32String::new_unchecked(
167 encoded,
168 crate::encoding::bech32::EncodingVariant::Bech32,
169 )
170 }
171
172 /// Borrow and encode to Bech32 (raw bytes remain available).
173 pub fn to_bech32(&self, hrp: &str) -> crate::encoding::bech32::Bech32String {
174 let hrp = Hrp::parse(hrp).expect("invalid HRP");
175 let encoded = bech32::encode::<Bech32>(hrp, self.expose_secret())
176 .expect("encoding valid random bytes cannot fail");
177
178 crate::encoding::bech32::Bech32String::new_unchecked(
179 encoded,
180 crate::encoding::bech32::EncodingVariant::Bech32,
181 )
182 }
183
184 /// Consume self and return the random bytes as a validated Bech32m string with the specified HRP.
185 ///
186 /// The raw bytes are zeroized immediately after encoding (via drop of `self`).
187 ///
188 /// # Panics
189 ///
190 /// Panics if the HRP is invalid or encoding fails (should never happen for valid random bytes).
191 pub fn into_bech32m(self, hrp: &str) -> crate::encoding::bech32::Bech32String {
192 let hrp = Hrp::parse(hrp).expect("invalid HRP");
193 let encoded = bech32::encode::<Bech32m>(hrp, self.expose_secret())
194 .expect("encoding valid random bytes cannot fail");
195
196 crate::encoding::bech32::Bech32String::new_unchecked(
197 encoded,
198 crate::encoding::bech32::EncodingVariant::Bech32m,
199 )
200 }
201
202 /// Borrow and encode to Bech32m (raw bytes remain available).
203 pub fn to_bech32m(&self, hrp: &str) -> crate::encoding::bech32::Bech32String {
204 let hrp = Hrp::parse(hrp).expect("invalid HRP");
205 let encoded = bech32::encode::<Bech32m>(hrp, self.expose_secret())
206 .expect("encoding valid random bytes cannot fail");
207
208 crate::encoding::bech32::Bech32String::new_unchecked(
209 encoded,
210 crate::encoding::bech32::EncodingVariant::Bech32m,
211 )
212 }
213}
214
215#[cfg(all(feature = "rand", feature = "encoding-hex"))]
216impl<const N: usize> FixedRng<N> {
217 /// Consume self and return the random bytes as a validated hex string.
218 ///
219 /// The raw bytes are zeroized immediately after encoding.
220 pub fn into_hex(self) -> crate::encoding::hex::HexString {
221 let hex_str = hex::encode(self.expose_secret());
222 crate::encoding::hex::HexString::new_unchecked(hex_str)
223 }
224
225 /// Encode to hex without consuming self, for cases where raw is still needed briefly.
226 pub fn to_hex(&self) -> crate::encoding::hex::HexString {
227 let hex_str = hex::encode(self.expose_secret());
228 crate::encoding::hex::HexString::new_unchecked(hex_str)
229 }
230}
231
232#[cfg(all(feature = "rand", feature = "encoding-base64"))]
233impl<const N: usize> FixedRng<N> {
234 /// Consume self and return the random bytes as a validated base64 string.
235 ///
236 /// The raw bytes are zeroized immediately after encoding.
237 pub fn into_base64(self) -> crate::encoding::base64::Base64String {
238 let encoded = URL_SAFE_NO_PAD.encode(self.expose_secret());
239 crate::encoding::base64::Base64String::new_unchecked(encoded)
240 }
241
242 /// Encode to base64 without consuming self, for cases where raw is still needed briefly.
243 pub fn to_base64(&self) -> crate::encoding::base64::Base64String {
244 let encoded = URL_SAFE_NO_PAD.encode(self.expose_secret());
245 crate::encoding::base64::Base64String::new_unchecked(encoded)
246 }
247}
248
249impl<const N: usize> From<FixedRng<N>> for Fixed<[u8; N]> {
250 /// Convert a `FixedRng` to `Fixed`, transferring ownership.
251 ///
252 /// This preserves all security guarantees. The `FixedRng` type
253 /// ensures the value came from secure RNG, and this conversion
254 /// transfers that value to `Fixed` without exposing bytes.
255 ///
256 /// # Example
257 ///
258 /// ```
259 /// # #[cfg(feature = "rand")]
260 /// # {
261 /// use secure_gate::{Fixed, random::FixedRng};
262 /// let key: Fixed<[u8; 32]> = FixedRng::<32>::generate().into();
263 /// # }
264 /// ```
265 #[inline(always)]
266 fn from(rng: FixedRng<N>) -> Self {
267 rng.into_inner()
268 }
269}