1use crate::RevealSecret;
19use crate::RevealSecretMut;
20
21#[cfg(feature = "encoding-base64")]
22use crate::traits::encoding::base64_url::ToBase64Url;
23#[cfg(feature = "encoding-hex")]
24use crate::traits::encoding::hex::ToHex;
25
26#[cfg(feature = "rand")]
27use rand::{rngs::OsRng, TryCryptoRng, TryRngCore};
28use zeroize::Zeroize;
29
30#[cfg(feature = "encoding-base64")]
31use crate::traits::decoding::base64_url::FromBase64UrlStr;
32#[cfg(feature = "encoding-bech32")]
33use crate::traits::decoding::bech32::FromBech32Str;
34#[cfg(feature = "encoding-bech32m")]
35use crate::traits::decoding::bech32m::FromBech32mStr;
36#[cfg(feature = "encoding-hex")]
37use crate::traits::decoding::hex::FromHexStr;
38
39pub struct Fixed<T: zeroize::Zeroize> {
48 inner: T,
49}
50
51impl<T: zeroize::Zeroize> Fixed<T> {
52 #[inline(always)]
54 pub const fn new(value: T) -> Self {
55 Fixed { inner: value }
56 }
57}
58
59impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]> {
60 #[inline(always)]
61 fn from(arr: [u8; N]) -> Self {
62 Self::new(arr)
63 }
64}
65
66impl<const N: usize> core::convert::TryFrom<&[u8]> for Fixed<[u8; N]> {
67 type Error = crate::error::FromSliceError;
68
69 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
70 if slice.len() != N {
71 #[cfg(debug_assertions)]
72 return Err(crate::error::FromSliceError::InvalidLength {
73 actual: slice.len(),
74 expected: N,
75 });
76 #[cfg(not(debug_assertions))]
77 return Err(crate::error::FromSliceError::InvalidLength);
78 }
79 let mut arr = [0u8; N];
80 arr.copy_from_slice(slice);
81 Ok(Self::new(arr))
82 }
83}
84
85impl<const N: usize> Fixed<[u8; N]> {
87 #[cfg(feature = "encoding-hex")]
88 #[inline]
89 pub fn to_hex(&self) -> alloc::string::String {
90 self.with_secret(|s: &[u8; N]| s.to_hex())
91 }
92
93 #[cfg(feature = "encoding-hex")]
94 #[inline]
95 pub fn to_hex_upper(&self) -> alloc::string::String {
96 self.with_secret(|s: &[u8; N]| s.to_hex_upper())
97 }
98
99 #[cfg(feature = "encoding-base64")]
100 #[inline]
101 pub fn to_base64url(&self) -> alloc::string::String {
102 self.with_secret(|s: &[u8; N]| s.to_base64url())
103 }
104}
105
106impl<const N: usize, T: zeroize::Zeroize> RevealSecret for Fixed<[T; N]> {
108 type Inner = [T; N];
109
110 #[inline(always)]
111 fn with_secret<F, R>(&self, f: F) -> R
112 where
113 F: FnOnce(&[T; N]) -> R,
114 {
115 f(&self.inner)
116 }
117
118 #[inline(always)]
119 fn expose_secret(&self) -> &[T; N] {
120 &self.inner
121 }
122
123 #[inline(always)]
124 fn len(&self) -> usize {
125 N * core::mem::size_of::<T>()
126 }
127}
128
129impl<const N: usize, T: zeroize::Zeroize> RevealSecretMut for Fixed<[T; N]> {
131 #[inline(always)]
132 fn with_secret_mut<F, R>(&mut self, f: F) -> R
133 where
134 F: FnOnce(&mut [T; N]) -> R,
135 {
136 f(&mut self.inner)
137 }
138
139 #[inline(always)]
140 fn expose_secret_mut(&mut self) -> &mut [T; N] {
141 &mut self.inner
142 }
143}
144
145#[cfg(feature = "rand")]
146impl<const N: usize> Fixed<[u8; N]> {
147 #[inline]
157 pub fn from_random() -> Self {
158 let mut bytes = [0u8; N];
159 OsRng
160 .try_fill_bytes(&mut bytes)
161 .expect("OsRng failure is a program error");
162 Self::from(bytes)
163 }
164
165 #[inline]
174 pub fn from_rng<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self, R::Error> {
175 let mut bytes = [0u8; N];
176 rng.try_fill_bytes(&mut bytes)?;
177 Ok(Self::from(bytes))
178 }
179}
180
181#[cfg(feature = "encoding-hex")]
182impl<const N: usize> Fixed<[u8; N]> {
183 pub fn try_from_hex(hex: &str) -> Result<Self, crate::error::HexError> {
184 let bytes = zeroize::Zeroizing::new(hex.try_from_hex()?);
185 if bytes.len() != N {
186 #[cfg(debug_assertions)]
187 return Err(crate::error::HexError::InvalidLength {
188 expected: N,
189 got: bytes.len(),
190 });
191 #[cfg(not(debug_assertions))]
192 return Err(crate::error::HexError::InvalidLength);
193 }
194 let mut arr = [0u8; N];
195 arr.copy_from_slice(&bytes);
196 Ok(Self::new(arr))
197 }
198}
199
200#[cfg(feature = "encoding-base64")]
201impl<const N: usize> Fixed<[u8; N]> {
202 pub fn try_from_base64url(s: &str) -> Result<Self, crate::error::Base64Error> {
203 let bytes = zeroize::Zeroizing::new(s.try_from_base64url()?);
204 if bytes.len() != N {
205 #[cfg(debug_assertions)]
206 return Err(crate::error::Base64Error::InvalidLength {
207 expected: N,
208 got: bytes.len(),
209 });
210 #[cfg(not(debug_assertions))]
211 return Err(crate::error::Base64Error::InvalidLength);
212 }
213 let mut arr = [0u8; N];
214 arr.copy_from_slice(&bytes);
215 Ok(Self::new(arr))
216 }
217}
218
219#[cfg(feature = "encoding-bech32")]
220impl<const N: usize> Fixed<[u8; N]> {
221 pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
229 let (_hrp, bytes_raw) = s.try_from_bech32_unchecked()?;
230 let bytes = zeroize::Zeroizing::new(bytes_raw);
231 if bytes.len() != N {
232 #[cfg(debug_assertions)]
233 return Err(crate::error::Bech32Error::InvalidLength {
234 expected: N,
235 got: bytes.len(),
236 });
237 #[cfg(not(debug_assertions))]
238 return Err(crate::error::Bech32Error::InvalidLength);
239 }
240 let mut arr = [0u8; N];
241 arr.copy_from_slice(&bytes);
242 Ok(Self::new(arr))
243 }
244
245 pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
251 let bytes_raw = s.try_from_bech32(expected_hrp)?;
252 let bytes = zeroize::Zeroizing::new(bytes_raw);
253 if bytes.len() != N {
254 #[cfg(debug_assertions)]
255 return Err(crate::error::Bech32Error::InvalidLength {
256 expected: N,
257 got: bytes.len(),
258 });
259 #[cfg(not(debug_assertions))]
260 return Err(crate::error::Bech32Error::InvalidLength);
261 }
262 let mut arr = [0u8; N];
263 arr.copy_from_slice(&bytes);
264 Ok(Self::new(arr))
265 }
266}
267
268#[cfg(feature = "encoding-bech32m")]
269impl<const N: usize> Fixed<[u8; N]> {
270 pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
278 let (_hrp, bytes_raw) = s.try_from_bech32m_unchecked()?;
279 let bytes = zeroize::Zeroizing::new(bytes_raw);
280 if bytes.len() != N {
281 #[cfg(debug_assertions)]
282 return Err(crate::error::Bech32Error::InvalidLength {
283 expected: N,
284 got: bytes.len(),
285 });
286 #[cfg(not(debug_assertions))]
287 return Err(crate::error::Bech32Error::InvalidLength);
288 }
289 let mut arr = [0u8; N];
290 arr.copy_from_slice(&bytes);
291 Ok(Self::new(arr))
292 }
293
294 pub fn try_from_bech32m(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
300 let bytes_raw = s.try_from_bech32m(expected_hrp)?;
301 let bytes = zeroize::Zeroizing::new(bytes_raw);
302 if bytes.len() != N {
303 #[cfg(debug_assertions)]
304 return Err(crate::error::Bech32Error::InvalidLength {
305 expected: N,
306 got: bytes.len(),
307 });
308 #[cfg(not(debug_assertions))]
309 return Err(crate::error::Bech32Error::InvalidLength);
310 }
311 let mut arr = [0u8; N];
312 arr.copy_from_slice(&bytes);
313 Ok(Self::new(arr))
314 }
315}
316
317#[cfg(feature = "ct-eq")]
318impl<T: zeroize::Zeroize> crate::ConstantTimeEq for Fixed<T>
319where
320 T: crate::ConstantTimeEq,
321{
322 fn ct_eq(&self, other: &Self) -> bool {
323 self.inner.ct_eq(&other.inner)
324 }
325}
326
327impl<T: zeroize::Zeroize> core::fmt::Debug for Fixed<T> {
328 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
329 f.write_str("[REDACTED]")
330 }
331}
332
333#[cfg(feature = "cloneable")]
334impl<T: zeroize::Zeroize + crate::CloneableSecret> Clone for Fixed<T> {
335 fn clone(&self) -> Self {
336 Self::new(self.inner.clone())
337 }
338}
339
340#[cfg(feature = "serde-serialize")]
341impl<T: zeroize::Zeroize + crate::SerializableSecret> serde::Serialize for Fixed<T> {
342 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
343 where
344 S: serde::Serializer,
345 {
346 self.inner.serialize(serializer)
347 }
348}
349
350#[cfg(feature = "serde-deserialize")]
351impl<'de, const N: usize> serde::Deserialize<'de> for Fixed<[u8; N]> {
352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
353 where
354 D: serde::Deserializer<'de>,
355 {
356 use core::fmt;
357 use serde::de::Visitor;
358 struct FixedVisitor<const M: usize>;
359 impl<'de, const M: usize> Visitor<'de> for FixedVisitor<M> {
360 type Value = Fixed<[u8; M]>;
361 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
362 write!(formatter, "a byte array of length {}", M)
363 }
364 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
365 where
366 A: serde::de::SeqAccess<'de>,
367 {
368 let mut vec: zeroize::Zeroizing<alloc::vec::Vec<u8>> =
369 zeroize::Zeroizing::new(alloc::vec::Vec::with_capacity(M));
370 while let Some(value) = seq.next_element()? {
371 vec.push(value);
372 }
373 if vec.len() != M {
374 #[cfg(debug_assertions)]
375 return Err(serde::de::Error::invalid_length(
376 vec.len(),
377 &M.to_string().as_str(),
378 ));
379 #[cfg(not(debug_assertions))]
380 return Err(serde::de::Error::custom("decoded length mismatch"));
381 }
382 let mut arr = [0u8; M];
383 arr.copy_from_slice(&vec);
384 Ok(Fixed::new(arr))
385 }
386 }
387 deserializer.deserialize_seq(FixedVisitor::<N>)
388 }
389}
390
391impl<T: zeroize::Zeroize> zeroize::Zeroize for Fixed<T> {
393 fn zeroize(&mut self) {
394 self.inner.zeroize();
395 }
396}
397
398impl<T: zeroize::Zeroize> Drop for Fixed<T> {
399 fn drop(&mut self) {
400 self.zeroize();
401 }
402}
403
404impl<T: zeroize::Zeroize> zeroize::ZeroizeOnDrop for Fixed<T> {}