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::{TryCryptoRng, TryRng, rngs::SysRng};
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")]
118 #[inline]
119 pub fn to_hex(&self) -> alloc::string::String {
120 self.with_secret(|s: &[u8; N]| s.to_hex())
121 }
122
123 #[cfg(feature = "encoding-hex")]
128 #[inline]
129 pub fn to_hex_upper(&self) -> alloc::string::String {
130 self.with_secret(|s: &[u8; N]| s.to_hex_upper())
131 }
132
133 #[cfg(feature = "encoding-base64")]
138 #[inline]
139 pub fn to_base64url(&self) -> alloc::string::String {
140 self.with_secret(|s: &[u8; N]| s.to_base64url())
141 }
142}
143
144impl<const N: usize, T: zeroize::Zeroize> RevealSecret for Fixed<[T; N]> {
146 type Inner = [T; N];
147
148 #[inline(always)]
149 fn with_secret<F, R>(&self, f: F) -> R
150 where
151 F: FnOnce(&[T; N]) -> R,
152 {
153 f(&self.inner)
154 }
155
156 #[inline(always)]
157 fn expose_secret(&self) -> &[T; N] {
158 &self.inner
159 }
160
161 #[inline(always)]
162 fn len(&self) -> usize {
163 N * core::mem::size_of::<T>()
164 }
165}
166
167impl<const N: usize, T: zeroize::Zeroize> RevealSecretMut for Fixed<[T; N]> {
169 #[inline(always)]
170 fn with_secret_mut<F, R>(&mut self, f: F) -> R
171 where
172 F: FnOnce(&mut [T; N]) -> R,
173 {
174 f(&mut self.inner)
175 }
176
177 #[inline(always)]
178 fn expose_secret_mut(&mut self) -> &mut [T; N] {
179 &mut self.inner
180 }
181}
182
183#[cfg(feature = "rand")]
184impl<const N: usize> Fixed<[u8; N]> {
185 #[inline]
208 pub fn from_random() -> Self {
209 Self::new_with(|arr| {
210 SysRng
211 .try_fill_bytes(arr)
212 .expect("SysRng failure is a program error");
213 })
214 }
215
216 #[inline]
240 pub fn from_rng<R: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self, R::Error> {
241 let mut result = Ok(());
242 let this = Self::new_with(|arr| {
243 result = rng.try_fill_bytes(arr);
244 });
245 result.map(|_| this) }
247}
248
249#[cfg(feature = "encoding-hex")]
250impl<const N: usize> Fixed<[u8; N]> {
251 pub fn try_from_hex(hex: &str) -> Result<Self, crate::error::HexError> {
268 let bytes = zeroize::Zeroizing::new(hex.try_from_hex()?);
269 if bytes.len() != N {
270 #[cfg(debug_assertions)]
271 return Err(crate::error::HexError::InvalidLength {
272 expected: N,
273 got: bytes.len(),
274 });
275 #[cfg(not(debug_assertions))]
276 return Err(crate::error::HexError::InvalidLength);
277 }
278 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
279 }
280}
281
282#[cfg(feature = "encoding-base64")]
283impl<const N: usize> Fixed<[u8; N]> {
284 pub fn try_from_base64url(s: &str) -> Result<Self, crate::error::Base64Error> {
301 let bytes = zeroize::Zeroizing::new(s.try_from_base64url()?);
302 if bytes.len() != N {
303 #[cfg(debug_assertions)]
304 return Err(crate::error::Base64Error::InvalidLength {
305 expected: N,
306 got: bytes.len(),
307 });
308 #[cfg(not(debug_assertions))]
309 return Err(crate::error::Base64Error::InvalidLength);
310 }
311 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
312 }
313}
314
315#[cfg(feature = "encoding-bech32")]
316impl<const N: usize> Fixed<[u8; N]> {
317 pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
325 let (_hrp, bytes_raw) = s.try_from_bech32_unchecked()?;
326 let bytes = zeroize::Zeroizing::new(bytes_raw);
327 if bytes.len() != N {
328 #[cfg(debug_assertions)]
329 return Err(crate::error::Bech32Error::InvalidLength {
330 expected: N,
331 got: bytes.len(),
332 });
333 #[cfg(not(debug_assertions))]
334 return Err(crate::error::Bech32Error::InvalidLength);
335 }
336 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
337 }
338
339 pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
345 let bytes_raw = s.try_from_bech32(expected_hrp)?;
346 let bytes = zeroize::Zeroizing::new(bytes_raw);
347 if bytes.len() != N {
348 #[cfg(debug_assertions)]
349 return Err(crate::error::Bech32Error::InvalidLength {
350 expected: N,
351 got: bytes.len(),
352 });
353 #[cfg(not(debug_assertions))]
354 return Err(crate::error::Bech32Error::InvalidLength);
355 }
356 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
357 }
358}
359
360#[cfg(feature = "encoding-bech32m")]
361impl<const N: usize> Fixed<[u8; N]> {
362 pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
370 let (_hrp, bytes_raw) = s.try_from_bech32m_unchecked()?;
371 let bytes = zeroize::Zeroizing::new(bytes_raw);
372 if bytes.len() != N {
373 #[cfg(debug_assertions)]
374 return Err(crate::error::Bech32Error::InvalidLength {
375 expected: N,
376 got: bytes.len(),
377 });
378 #[cfg(not(debug_assertions))]
379 return Err(crate::error::Bech32Error::InvalidLength);
380 }
381 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
382 }
383
384 pub fn try_from_bech32m(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
390 let bytes_raw = s.try_from_bech32m(expected_hrp)?;
391 let bytes = zeroize::Zeroizing::new(bytes_raw);
392 if bytes.len() != N {
393 #[cfg(debug_assertions)]
394 return Err(crate::error::Bech32Error::InvalidLength {
395 expected: N,
396 got: bytes.len(),
397 });
398 #[cfg(not(debug_assertions))]
399 return Err(crate::error::Bech32Error::InvalidLength);
400 }
401 Ok(Self::new_with(|arr| arr.copy_from_slice(&bytes)))
402 }
403}
404
405#[cfg(feature = "ct-eq")]
406impl<T: zeroize::Zeroize> crate::ConstantTimeEq for Fixed<T>
407where
408 T: crate::ConstantTimeEq,
409{
410 fn ct_eq(&self, other: &Self) -> bool {
411 self.inner.ct_eq(&other.inner)
412 }
413}
414
415impl<T: zeroize::Zeroize> core::fmt::Debug for Fixed<T> {
416 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
417 f.write_str("[REDACTED]")
418 }
419}
420
421#[cfg(feature = "cloneable")]
422impl<T: zeroize::Zeroize + crate::CloneableSecret> Clone for Fixed<T> {
423 fn clone(&self) -> Self {
424 Self::new(self.inner.clone())
425 }
426}
427
428#[cfg(feature = "serde-serialize")]
429impl<T: zeroize::Zeroize + crate::SerializableSecret> serde::Serialize for Fixed<T> {
430 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
431 where
432 S: serde::Serializer,
433 {
434 self.inner.serialize(serializer)
435 }
436}
437
438#[cfg(feature = "serde-deserialize")]
439impl<'de, const N: usize> serde::Deserialize<'de> for Fixed<[u8; N]> {
440 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
441 where
442 D: serde::Deserializer<'de>,
443 {
444 use core::fmt;
445 use serde::de::Visitor;
446 struct FixedVisitor<const M: usize>;
447 impl<'de, const M: usize> Visitor<'de> for FixedVisitor<M> {
448 type Value = Fixed<[u8; M]>;
449 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
450 write!(formatter, "a byte array of length {}", M)
451 }
452 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
453 where
454 A: serde::de::SeqAccess<'de>,
455 {
456 let mut vec: zeroize::Zeroizing<alloc::vec::Vec<u8>> =
457 zeroize::Zeroizing::new(alloc::vec::Vec::with_capacity(M));
458 while let Some(value) = seq.next_element()? {
459 vec.push(value);
460 }
461 if vec.len() != M {
462 #[cfg(debug_assertions)]
463 return Err(serde::de::Error::invalid_length(
464 vec.len(),
465 &M.to_string().as_str(),
466 ));
467 #[cfg(not(debug_assertions))]
468 return Err(serde::de::Error::custom("decoded length mismatch"));
469 }
470 Ok(Fixed::new_with(|arr| arr.copy_from_slice(&vec)))
471 }
472 }
473 deserializer.deserialize_seq(FixedVisitor::<N>)
474 }
475}
476
477impl<T: zeroize::Zeroize> zeroize::Zeroize for Fixed<T> {
479 fn zeroize(&mut self) {
480 self.inner.zeroize();
481 }
482}
483
484impl<T: zeroize::Zeroize> Drop for Fixed<T> {
485 fn drop(&mut self) {
486 self.zeroize();
487 }
488}
489
490impl<T: zeroize::Zeroize> zeroize::ZeroizeOnDrop for Fixed<T> {}