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> {
52 inner: T,
53}
54
55impl<T: zeroize::Zeroize> Fixed<T> {
56 #[inline(always)]
58 pub const fn new(value: T) -> Self {
59 Fixed { inner: value }
60 }
61}
62
63impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]> {
64 #[inline(always)]
65 fn from(arr: [u8; N]) -> Self {
66 Self::new(arr)
67 }
68}
69
70impl<const N: usize> core::convert::TryFrom<&[u8]> for Fixed<[u8; N]> {
71 type Error = crate::error::FromSliceError;
72
73 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
74 if slice.len() != N {
75 #[cfg(debug_assertions)]
76 return Err(crate::error::FromSliceError::InvalidLength {
77 actual: slice.len(),
78 expected: N,
79 });
80 #[cfg(not(debug_assertions))]
81 return Err(crate::error::FromSliceError::InvalidLength);
82 }
83 Ok(Self::new_with(|arr| arr.copy_from_slice(slice)))
84 }
85}
86
87impl<const N: usize> Fixed<[u8; N]> {
89 #[inline(always)]
104 pub fn new_with<F>(f: F) -> Self
105 where
106 F: FnOnce(&mut [u8; N]),
107 {
108 let mut this = Self { inner: [0u8; N] };
109 f(&mut this.inner);
110 this
111 }
112
113 #[cfg(feature = "encoding-hex")]
114 #[inline]
115 pub fn to_hex(&self) -> alloc::string::String {
116 self.with_secret(|s: &[u8; N]| s.to_hex())
117 }
118
119 #[cfg(feature = "encoding-hex")]
120 #[inline]
121 pub fn to_hex_upper(&self) -> alloc::string::String {
122 self.with_secret(|s: &[u8; N]| s.to_hex_upper())
123 }
124
125 #[cfg(feature = "encoding-base64")]
126 #[inline]
127 pub fn to_base64url(&self) -> alloc::string::String {
128 self.with_secret(|s: &[u8; N]| s.to_base64url())
129 }
130}
131
132impl<const N: usize, T: zeroize::Zeroize> RevealSecret for Fixed<[T; N]> {
134 type Inner = [T; N];
135
136 #[inline(always)]
137 fn with_secret<F, R>(&self, f: F) -> R
138 where
139 F: FnOnce(&[T; N]) -> R,
140 {
141 f(&self.inner)
142 }
143
144 #[inline(always)]
145 fn expose_secret(&self) -> &[T; N] {
146 &self.inner
147 }
148
149 #[inline(always)]
150 fn len(&self) -> usize {
151 N * core::mem::size_of::<T>()
152 }
153}
154
155impl<const N: usize, T: zeroize::Zeroize> RevealSecretMut for Fixed<[T; N]> {
157 #[inline(always)]
158 fn with_secret_mut<F, R>(&mut self, f: F) -> R
159 where
160 F: FnOnce(&mut [T; N]) -> R,
161 {
162 f(&mut self.inner)
163 }
164
165 #[inline(always)]
166 fn expose_secret_mut(&mut self) -> &mut [T; N] {
167 &mut self.inner
168 }
169}
170
171#[cfg(feature = "rand")]
172impl<const N: usize> Fixed<[u8; N]> {
173 #[inline]
183 pub fn from_random() -> Self {
184 Self::new_with(|arr| {
185 OsRng
186 .try_fill_bytes(arr)
187 .expect("OsRng failure is a program error");
188 })
189 }
190
191 #[inline]
200 pub fn from_rng<R: TryRngCore + TryCryptoRng>(rng: &mut R) -> Result<Self, R::Error> {
201 let mut result = Ok(());
202 let this = Self::new_with(|arr| {
203 result = rng.try_fill_bytes(arr);
204 });
205 result.map(|_| this) }
207}
208
209#[cfg(feature = "encoding-hex")]
210impl<const N: usize> Fixed<[u8; N]> {
211 pub fn try_from_hex(hex: &str) -> Result<Self, crate::error::HexError> {
212 let bytes = zeroize::Zeroizing::new(hex.try_from_hex()?);
213 if bytes.len() != N {
214 #[cfg(debug_assertions)]
215 return Err(crate::error::HexError::InvalidLength {
216 expected: N,
217 got: bytes.len(),
218 });
219 #[cfg(not(debug_assertions))]
220 return Err(crate::error::HexError::InvalidLength);
221 }
222 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
223 }
224}
225
226#[cfg(feature = "encoding-base64")]
227impl<const N: usize> Fixed<[u8; N]> {
228 pub fn try_from_base64url(s: &str) -> Result<Self, crate::error::Base64Error> {
229 let bytes = zeroize::Zeroizing::new(s.try_from_base64url()?);
230 if bytes.len() != N {
231 #[cfg(debug_assertions)]
232 return Err(crate::error::Base64Error::InvalidLength {
233 expected: N,
234 got: bytes.len(),
235 });
236 #[cfg(not(debug_assertions))]
237 return Err(crate::error::Base64Error::InvalidLength);
238 }
239 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
240 }
241}
242
243#[cfg(feature = "encoding-bech32")]
244impl<const N: usize> Fixed<[u8; N]> {
245 pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
253 let (_hrp, bytes_raw) = s.try_from_bech32_unchecked()?;
254 let bytes = zeroize::Zeroizing::new(bytes_raw);
255 if bytes.len() != N {
256 #[cfg(debug_assertions)]
257 return Err(crate::error::Bech32Error::InvalidLength {
258 expected: N,
259 got: bytes.len(),
260 });
261 #[cfg(not(debug_assertions))]
262 return Err(crate::error::Bech32Error::InvalidLength);
263 }
264 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
265 }
266
267 pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
273 let bytes_raw = s.try_from_bech32(expected_hrp)?;
274 let bytes = zeroize::Zeroizing::new(bytes_raw);
275 if bytes.len() != N {
276 #[cfg(debug_assertions)]
277 return Err(crate::error::Bech32Error::InvalidLength {
278 expected: N,
279 got: bytes.len(),
280 });
281 #[cfg(not(debug_assertions))]
282 return Err(crate::error::Bech32Error::InvalidLength);
283 }
284 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
285 }
286}
287
288#[cfg(feature = "encoding-bech32m")]
289impl<const N: usize> Fixed<[u8; N]> {
290 pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
298 let (_hrp, bytes_raw) = s.try_from_bech32m_unchecked()?;
299 let bytes = zeroize::Zeroizing::new(bytes_raw);
300 if bytes.len() != N {
301 #[cfg(debug_assertions)]
302 return Err(crate::error::Bech32Error::InvalidLength {
303 expected: N,
304 got: bytes.len(),
305 });
306 #[cfg(not(debug_assertions))]
307 return Err(crate::error::Bech32Error::InvalidLength);
308 }
309 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
310 }
311
312 pub fn try_from_bech32m(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
318 let bytes_raw = s.try_from_bech32m(expected_hrp)?;
319 let bytes = zeroize::Zeroizing::new(bytes_raw);
320 if bytes.len() != N {
321 #[cfg(debug_assertions)]
322 return Err(crate::error::Bech32Error::InvalidLength {
323 expected: N,
324 got: bytes.len(),
325 });
326 #[cfg(not(debug_assertions))]
327 return Err(crate::error::Bech32Error::InvalidLength);
328 }
329 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
330 }
331}
332
333#[cfg(feature = "ct-eq")]
334impl<T: zeroize::Zeroize> crate::ConstantTimeEq for Fixed<T>
335where
336 T: crate::ConstantTimeEq,
337{
338 fn ct_eq(&self, other: &Self) -> bool {
339 self.inner.ct_eq(&other.inner)
340 }
341}
342
343impl<T: zeroize::Zeroize> core::fmt::Debug for Fixed<T> {
344 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
345 f.write_str("[REDACTED]")
346 }
347}
348
349#[cfg(feature = "cloneable")]
350impl<T: zeroize::Zeroize + crate::CloneableSecret> Clone for Fixed<T> {
351 fn clone(&self) -> Self {
352 Self::new(self.inner.clone())
353 }
354}
355
356#[cfg(feature = "serde-serialize")]
357impl<T: zeroize::Zeroize + crate::SerializableSecret> serde::Serialize for Fixed<T> {
358 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
359 where
360 S: serde::Serializer,
361 {
362 self.inner.serialize(serializer)
363 }
364}
365
366#[cfg(feature = "serde-deserialize")]
367impl<'de, const N: usize> serde::Deserialize<'de> for Fixed<[u8; N]> {
368 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
369 where
370 D: serde::Deserializer<'de>,
371 {
372 use core::fmt;
373 use serde::de::Visitor;
374 struct FixedVisitor<const M: usize>;
375 impl<'de, const M: usize> Visitor<'de> for FixedVisitor<M> {
376 type Value = Fixed<[u8; M]>;
377 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
378 write!(formatter, "a byte array of length {}", M)
379 }
380 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
381 where
382 A: serde::de::SeqAccess<'de>,
383 {
384 let mut vec: zeroize::Zeroizing<alloc::vec::Vec<u8>> =
385 zeroize::Zeroizing::new(alloc::vec::Vec::with_capacity(M));
386 while let Some(value) = seq.next_element()? {
387 vec.push(value);
388 }
389 if vec.len() != M {
390 #[cfg(debug_assertions)]
391 return Err(serde::de::Error::invalid_length(
392 vec.len(),
393 &M.to_string().as_str(),
394 ));
395 #[cfg(not(debug_assertions))]
396 return Err(serde::de::Error::custom("decoded length mismatch"));
397 }
398 Ok(Fixed::new_with(|arr| arr.copy_from_slice(&vec)))
399 }
400 }
401 deserializer.deserialize_seq(FixedVisitor::<N>)
402 }
403}
404
405impl<T: zeroize::Zeroize> zeroize::Zeroize for Fixed<T> {
407 fn zeroize(&mut self) {
408 self.inner.zeroize();
409 }
410}
411
412impl<T: zeroize::Zeroize> Drop for Fixed<T> {
413 fn drop(&mut self) {
414 self.zeroize();
415 }
416}
417
418impl<T: zeroize::Zeroize> zeroize::ZeroizeOnDrop for Fixed<T> {}