secure_gate/random/
dynamic_rng.rs

1// secure-gate/src/random/dynamic_rng.rs
2use crate::Dynamic;
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/// Heap-allocated cryptographically secure random bytes with encoding methods.
19///
20/// This is a newtype over `Dynamic<Vec<u8>>` for semantic clarity.
21/// Like `FixedRng`, guarantees freshness via RNG construction.
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/// ```
30/// # #[cfg(feature = "rand")]
31/// # {
32/// use secure_gate::random::DynamicRng;
33/// let random = DynamicRng::generate(64);
34/// assert_eq!(random.len(), 64);
35/// # }
36/// ```
37pub struct DynamicRng(Dynamic<Vec<u8>>);
38
39impl DynamicRng {
40    /// Generate fresh random bytes of the specified length.
41    ///
42    /// Panics if the RNG fails.
43    ///
44    /// # Example
45    ///
46    /// ```
47    /// # #[cfg(feature = "rand")]
48    /// # {
49    /// use secure_gate::random::DynamicRng;
50    /// let random = DynamicRng::generate(128);
51    /// # }
52    /// ```
53    pub fn generate(len: usize) -> Self {
54        let mut bytes = vec![0u8; len];
55        OsRng
56            .try_fill_bytes(&mut bytes)
57            .expect("OsRng failed — this should never happen on supported platforms");
58        Self(Dynamic::from(bytes))
59    }
60
61    /// Try to generate fresh random bytes of the specified length.
62    ///
63    /// Returns an error if the RNG fails.
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// # #[cfg(feature = "rand")]
69    /// # {
70    /// use secure_gate::random::DynamicRng;
71    /// let random: Result<DynamicRng, rand::rand_core::OsError> = DynamicRng::try_generate(64);
72    /// assert!(random.is_ok());
73    /// # }
74    /// ```
75    pub fn try_generate(len: usize) -> Result<Self, OsError> {
76        let mut bytes = vec![0u8; len];
77        OsRng
78            .try_fill_bytes(&mut bytes)
79            .map(|_| Self(Dynamic::from(bytes)))
80    }
81
82    /// Expose the random bytes for read-only access.
83    #[inline(always)]
84    pub fn expose_secret(&self) -> &[u8] {
85        self.0.expose_secret()
86    }
87
88    /// Returns the length in bytes.
89    #[inline(always)]
90    pub const fn len(&self) -> usize {
91        self.0.len()
92    }
93
94    /// Returns `true` if empty.
95    #[inline(always)]
96    pub const fn is_empty(&self) -> bool {
97        self.0.is_empty()
98    }
99
100    /// Consume and return the inner `Dynamic<Vec<u8>>`.
101    #[inline(always)]
102    pub fn into_inner(self) -> Dynamic<Vec<u8>> {
103        self.0
104    }
105}
106
107impl core::fmt::Debug for DynamicRng {
108    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109        f.write_str("[REDACTED]")
110    }
111}
112
113#[cfg(all(feature = "rand", feature = "encoding-bech32"))]
114impl DynamicRng {
115    /// Consume self and return the random bytes as a validated Bech32 string with the specified HRP.
116    ///
117    /// The raw bytes are zeroized immediately after encoding (via drop of `self`).
118    ///
119    /// # Panics
120    ///
121    /// Panics if the HRP is invalid or encoding fails (should never happen for valid random bytes).
122    pub fn into_bech32(self, hrp: &str) -> crate::encoding::bech32::Bech32String {
123        let hrp = Hrp::parse(hrp).expect("invalid HRP");
124        let encoded = bech32::encode::<Bech32>(hrp, self.expose_secret())
125            .expect("encoding valid random bytes cannot fail");
126
127        crate::encoding::bech32::Bech32String::new_unchecked(
128            encoded,
129            crate::encoding::bech32::EncodingVariant::Bech32,
130        )
131    }
132
133    /// Borrow and encode to Bech32 (raw bytes remain available).
134    pub fn to_bech32(&self, hrp: &str) -> crate::encoding::bech32::Bech32String {
135        let hrp = Hrp::parse(hrp).expect("invalid HRP");
136        let encoded = bech32::encode::<Bech32>(hrp, self.expose_secret())
137            .expect("encoding valid random bytes cannot fail");
138
139        crate::encoding::bech32::Bech32String::new_unchecked(
140            encoded,
141            crate::encoding::bech32::EncodingVariant::Bech32,
142        )
143    }
144
145    /// Consume self and return the random bytes as a validated Bech32m string with the specified HRP.
146    ///
147    /// The raw bytes are zeroized immediately after encoding (via drop of `self`).
148    ///
149    /// # Panics
150    ///
151    /// Panics if the HRP is invalid or encoding fails (should never happen for valid random bytes).
152    pub fn into_bech32m(self, hrp: &str) -> crate::encoding::bech32::Bech32String {
153        let hrp = Hrp::parse(hrp).expect("invalid HRP");
154        let encoded = bech32::encode::<Bech32m>(hrp, self.expose_secret())
155            .expect("encoding valid random bytes cannot fail");
156
157        crate::encoding::bech32::Bech32String::new_unchecked(
158            encoded,
159            crate::encoding::bech32::EncodingVariant::Bech32m,
160        )
161    }
162
163    /// Borrow and encode to Bech32m (raw bytes remain available).
164    pub fn to_bech32m(&self, hrp: &str) -> crate::encoding::bech32::Bech32String {
165        let hrp = Hrp::parse(hrp).expect("invalid HRP");
166        let encoded = bech32::encode::<Bech32m>(hrp, self.expose_secret())
167            .expect("encoding valid random bytes cannot fail");
168
169        crate::encoding::bech32::Bech32String::new_unchecked(
170            encoded,
171            crate::encoding::bech32::EncodingVariant::Bech32m,
172        )
173    }
174}
175
176#[cfg(all(feature = "rand", feature = "encoding-hex"))]
177impl DynamicRng {
178    /// Consume self and return the random bytes as a validated hex string.
179    ///
180    /// The raw bytes are zeroized immediately after encoding.
181    pub fn into_hex(self) -> crate::encoding::hex::HexString {
182        let hex_str = hex::encode(self.expose_secret());
183        crate::encoding::hex::HexString::new_unchecked(hex_str)
184    }
185
186    /// Encode to hex without consuming self, for cases where raw is still needed briefly.
187    pub fn to_hex(&self) -> crate::encoding::hex::HexString {
188        let hex_str = hex::encode(self.expose_secret());
189        crate::encoding::hex::HexString::new_unchecked(hex_str)
190    }
191}
192
193#[cfg(all(feature = "rand", feature = "encoding-base64"))]
194impl DynamicRng {
195    /// Consume self and return the random bytes as a validated base64 string.
196    ///
197    /// The raw bytes are zeroized immediately after encoding.
198    pub fn into_base64(self) -> crate::encoding::base64::Base64String {
199        let encoded = URL_SAFE_NO_PAD.encode(self.expose_secret());
200        crate::encoding::base64::Base64String::new_unchecked(encoded)
201    }
202
203    /// Encode to base64 without consuming self, for cases where raw is still needed briefly.
204    pub fn to_base64(&self) -> crate::encoding::base64::Base64String {
205        let encoded = URL_SAFE_NO_PAD.encode(self.expose_secret());
206        crate::encoding::base64::Base64String::new_unchecked(encoded)
207    }
208}
209
210impl From<DynamicRng> for Dynamic<Vec<u8>> {
211    /// Convert a `DynamicRng` to `Dynamic`, transferring ownership.
212    ///
213    /// This preserves all security guarantees. The `DynamicRng` type
214    /// ensures the value came from secure RNG, and this conversion
215    /// transfers that value to `Dynamic` without exposing bytes.
216    ///
217    /// # Example
218    ///
219    /// ```
220    /// # #[cfg(feature = "rand")]
221    /// # {
222    /// use secure_gate::{Dynamic, random::DynamicRng};
223    /// let random: Dynamic<Vec<u8>> = DynamicRng::generate(64).into();
224    /// # }
225    /// ```
226    #[inline(always)]
227    fn from(rng: DynamicRng) -> Self {
228        rng.into_inner()
229    }
230}