pub struct Fixed<T: Zeroize> { /* private fields */ }Expand description
Stack-allocated secret wrapper with explicit access and automatic zeroization on drop.
Fixed-size secrets (keys, nonces, tokens). Inner type must implement Zeroize.
Always available — works without alloc. Prefer new_with over
new when minimizing stack residue matters.
See Dynamic<T> for the heap-allocated alternative.
use secure_gate::{Fixed, RevealSecret};
let key = Fixed::new([0xABu8; 32]);
key.with_secret(|b| assert_eq!(b[0], 0xAB));
assert_eq!(format!("{:?}", key), "[REDACTED]");Zero-cost stack-allocated wrapper for fixed-size secrets.
Fixed<T> stores a T: Zeroize value inline and unconditionally zeroizes it
on drop. There is no Deref, AsRef, or Copy — every access is explicit
through RevealSecret or RevealSecretMut.
§Examples
use secure_gate::{Fixed, RevealSecret};
// Create a secret key.
let key = Fixed::new([0xABu8; 32]);
// Scoped access — the borrow cannot escape the closure.
let first = key.with_secret(|k| k[0]);
assert_eq!(first, 0xAB);
// Debug is always redacted.
assert_eq!(format!("{:?}", key), "[REDACTED]");§Constructors
| Constructor | Feature | Notes |
|---|---|---|
new(value) | — | const fn, ergonomic default |
new_with(f) | — | Scoped; preferred for stack-residue minimization |
From<[u8; N]> | — | Equivalent to new |
TryFrom<&[u8]> | — | Length-checked slice conversion |
try_from_hex | encoding-hex | Constant-time hex decoding |
try_from_base64url | encoding-base64 | Constant-time Base64url decoding |
try_from_bech32 | encoding-bech32 | HRP-validated Bech32 decoding |
try_from_bech32_unchecked | encoding-bech32 | Bech32 without HRP check |
try_from_bech32m | encoding-bech32m | HRP-validated Bech32m decoding |
try_from_bech32m_unchecked | encoding-bech32m | Bech32m without HRP check |
from_random() | rand | System RNG |
from_rng(rng) | rand | Custom RNG |
§See also
RevealSecret/RevealSecretMut— the 3-tier access traits.new_with— scoped constructor preferred overnew.
§Note
const fn new compiles in static position, but must not be used there
because Drop does not run on statics, which means zeroization is skipped.
Implementations§
Source§impl<T: Zeroize> Fixed<T>
impl<T: Zeroize> Fixed<T>
Sourcepub const fn new(value: T) -> Self
pub const fn new(value: T) -> Self
Creates a new Fixed<T> by wrapping a value.
This is a const fn, so it can be evaluated at compile time. However,
do not use it to initialize static items — Drop does not run on
statics, so zeroization would be skipped.
For Fixed<[u8; N]>, prefer new_with when minimizing
stack residue matters, as new may leave an intermediate copy of value
on the caller’s stack frame.
§Examples
use secure_gate::{Fixed, RevealSecret};
let secret = Fixed::new([0u8; 32]);
assert_eq!(secret.len(), 32);Source§impl<const N: usize> Fixed<[u8; N]>
Construction and ergonomic encoding helpers for Fixed<[u8; N]>.
impl<const N: usize> Fixed<[u8; N]>
Construction and ergonomic encoding helpers for Fixed<[u8; N]>.
Sourcepub fn new_with<F>(f: F) -> Self
pub fn new_with<F>(f: F) -> Self
Writes directly into the wrapper’s storage via a user-supplied closure,
eliminating the intermediate stack copy that new may produce.
The array is zero-initialized before the closure runs. Prefer this over
new(value) when minimizing stack residue matters
(long-lived keys, high-assurance environments).
§Security rationale
With Fixed::new(value), the caller first builds value on
its own stack frame, then moves it into the wrapper. The compiler may
elide the copy, but this is not guaranteed — leaving a plaintext residue
on the stack. new_with avoids this by giving the closure a mutable
reference to the wrapper’s own storage, so the secret is never placed
anywhere else.
§Examples
use secure_gate::{Fixed, RevealSecret};
// Fill from a closure — no intermediate stack copy.
let secret = Fixed::<[u8; 4]>::new_with(|arr| arr.fill(0xAB));
assert_eq!(secret.expose_secret(), &[0xAB; 4]);
// Copy from an existing slice.
let src = [1u8, 2, 3, 4];
let secret = Fixed::<[u8; 4]>::new_with(|arr| arr.copy_from_slice(&src));§See also
Dynamic::new_with— the heap-allocated equivalent (requiresalloc).
Source§impl<const N: usize> Fixed<[u8; N]>
Hex encoding and decoding for Fixed<[u8; N]>.
impl<const N: usize> Fixed<[u8; N]>
Hex encoding and decoding for Fixed<[u8; N]>.
Encoding uses a constant-time backend (base16ct). Decoding works with or without
the alloc feature — on no-alloc targets the bytes are decoded directly into a
Zeroizing<[u8; N]> stack buffer.
Sourcepub fn to_hex(&self) -> String
pub fn to_hex(&self) -> String
Encodes the secret bytes as a lowercase hex string.
Requires the encoding-hex and alloc features.
§Examples
use secure_gate::Fixed;
let secret = Fixed::new([0xDE, 0xAD]);
assert_eq!(secret.to_hex(), "dead");Sourcepub fn to_hex_upper(&self) -> String
pub fn to_hex_upper(&self) -> String
Encodes the secret bytes as an uppercase hex string.
Requires the encoding-hex and alloc features.
§Examples
use secure_gate::Fixed;
let secret = Fixed::new([0xDE, 0xAD]);
assert_eq!(secret.to_hex_upper(), "DEAD");Sourcepub fn to_hex_zeroizing(&self) -> EncodedSecret
pub fn to_hex_zeroizing(&self) -> EncodedSecret
Encodes the secret bytes as a lowercase hex string, returning
EncodedSecret to preserve zeroization.
Prefer this over to_hex when the encoded form should
still be treated as sensitive (e.g. private keys). The returned
EncodedSecret is zeroized on drop.
Requires the encoding-hex and alloc features.
§Examples
use secure_gate::{Fixed, RevealSecret};
let secret = Fixed::new([0xCA, 0xFE]);
let encoded = secret.to_hex_zeroizing();
assert_eq!(&*encoded, "cafe");
// `encoded` is zeroized when it goes out of scope.Sourcepub fn to_hex_upper_zeroizing(&self) -> EncodedSecret
pub fn to_hex_upper_zeroizing(&self) -> EncodedSecret
Encodes the secret bytes as an uppercase hex string, returning
EncodedSecret to preserve zeroization.
Requires the encoding-hex and alloc features.
§Examples
use secure_gate::{Fixed, RevealSecret};
let secret = Fixed::new([0xCA, 0xFE]);
let encoded = secret.to_hex_upper_zeroizing();
assert_eq!(&*encoded, "CAFE");Sourcepub fn try_from_hex(hex: &str) -> Result<Self, HexError>
pub fn try_from_hex(hex: &str) -> Result<Self, HexError>
Decodes a hex string (lowercase, uppercase, or mixed) into Fixed<[u8; N]>.
Uses a constant-time backend (base16ct) for both paths.
- With
alloc: decodes into aZeroizing<Vec<u8>>then copies onto the stack. The temporary heap buffer is zeroed on drop even if an error occurs. - Without
alloc: decodes directly into aZeroizing<[u8; N]>stack buffer. No heap allocation occurs.
§Errors
- [
HexError::InvalidHex] — non-hex characters or odd-length input. - [
HexError::InvalidLength] — decoded byte count does not equalN.
§Examples
use secure_gate::{Fixed, RevealSecret};
// Round-trip: encode then decode.
let original = Fixed::new([0xDE, 0xAD, 0xBE, 0xEF]);
let hex_str = original.to_hex();
let decoded = Fixed::<[u8; 4]>::try_from_hex(&hex_str).unwrap();
assert_eq!(decoded.expose_secret(), &[0xDE, 0xAD, 0xBE, 0xEF]);
// Wrong length fails.
assert!(Fixed::<[u8; 2]>::try_from_hex("deadbeef").is_err());Source§impl<const N: usize> Fixed<[u8; N]>
Base64url encoding and decoding for Fixed<[u8; N]>.
impl<const N: usize> Fixed<[u8; N]>
Base64url encoding and decoding for Fixed<[u8; N]>.
Encoding uses a constant-time backend (base64ct). Decoding works with or without
the alloc feature — on no-alloc targets the bytes are decoded directly into a
Zeroizing<[u8; N]> stack buffer.
Sourcepub fn to_base64url(&self) -> String
pub fn to_base64url(&self) -> String
Encodes the secret bytes as an unpadded Base64url string (RFC 4648, URL-safe alphabet).
Requires the encoding-base64 and alloc features.
§Examples
use secure_gate::Fixed;
let secret = Fixed::new([0xDE, 0xAD, 0xBE, 0xEF]);
let encoded = secret.to_base64url();
assert_eq!(encoded, "3q2-7w");Sourcepub fn to_base64url_zeroizing(&self) -> EncodedSecret
pub fn to_base64url_zeroizing(&self) -> EncodedSecret
Encodes the secret bytes as an unpadded Base64url string, returning
EncodedSecret to preserve zeroization.
Prefer this over to_base64url when the encoded
form should still be treated as sensitive. The returned
EncodedSecret is zeroized on drop.
Requires the encoding-base64 and alloc features.
§Examples
use secure_gate::{Fixed, RevealSecret};
let secret = Fixed::new([0xDE, 0xAD, 0xBE, 0xEF]);
let encoded = secret.to_base64url_zeroizing();
assert_eq!(&*encoded, "3q2-7w");
// `encoded` is zeroized when it goes out of scope.Sourcepub fn try_from_base64url(s: &str) -> Result<Self, Base64Error>
pub fn try_from_base64url(s: &str) -> Result<Self, Base64Error>
Decodes an unpadded Base64url string (RFC 4648, URL-safe alphabet) into
Fixed<[u8; N]>.
Uses a constant-time backend (base64ct) on both paths.
- With
alloc: decodes into aZeroizing<Vec<u8>>then copies onto the stack. - Without
alloc: decodes directly into aZeroizing<[u8; N]>stack buffer.
§Errors
- [
Base64Error::InvalidBase64] — non-base64 characters or invalid padding. - [
Base64Error::InvalidLength] — decoded byte count does not equalN.
§Examples
use secure_gate::{Fixed, RevealSecret};
// Round-trip.
let original = Fixed::new([0xDE, 0xAD, 0xBE, 0xEF]);
let encoded = original.to_base64url();
let decoded = Fixed::<[u8; 4]>::try_from_base64url(&encoded).unwrap();
assert_eq!(decoded.expose_secret(), &[0xDE, 0xAD, 0xBE, 0xEF]);Source§impl<const N: usize> Fixed<[u8; N]>
Bech32 (BIP-173) encoding and decoding for Fixed<[u8; N]>.
impl<const N: usize> Fixed<[u8; N]>
Bech32 (BIP-173) encoding and decoding for Fixed<[u8; N]>.
Uses the extended Bech32Large checksum variant (~5 KB payload limit) rather than
the 90-character standard limit. For Bitcoin address formats use ToBech32m.
Sourcepub fn try_to_bech32(&self, hrp: &str) -> Result<String, Bech32Error>
pub fn try_to_bech32(&self, hrp: &str) -> Result<String, Bech32Error>
Encodes the secret bytes as a Bech32 (BIP-173) string with the given HRP.
Requires the encoding-bech32 and alloc features.
Sourcepub fn try_to_bech32_zeroizing(
&self,
hrp: &str,
) -> Result<EncodedSecret, Bech32Error>
pub fn try_to_bech32_zeroizing( &self, hrp: &str, ) -> Result<EncodedSecret, Bech32Error>
Encodes the secret bytes as a Bech32 string, returning
EncodedSecret to preserve zeroization.
Requires the encoding-bech32 and alloc features.
Sourcepub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>
pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>
Decodes a Bech32 (BIP-173) string into Fixed<[u8; N]>, validating that the HRP
matches expected_hrp (case-insensitive).
HRP comparison is non-constant-time — this is intentional, as the HRP is public
metadata, not secret material. Timing leaks on HRP mismatch are acceptable because
the HRP is not secret. Prefer this over
try_from_bech32_unchecked to prevent
cross-protocol confusion attacks.
Works without alloc — decodes into a stack-allocated Zeroizing<[u8; N]> buffer.
Sourcepub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>
pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>
Decodes a Bech32 (BIP-173) string into Fixed<[u8; N]> without validating the HRP.
Any valid HRP is accepted as long as the checksum is valid and the payload length
equals N. Use try_from_bech32 in security-critical code
to prevent cross-protocol confusion attacks.
Works without alloc — decodes into a stack-allocated Zeroizing<[u8; N]> buffer.
Source§impl<const N: usize> Fixed<[u8; N]>
Bech32m (BIP-350) encoding and decoding for Fixed<[u8; N]>.
impl<const N: usize> Fixed<[u8; N]>
Bech32m (BIP-350) encoding and decoding for Fixed<[u8; N]>.
Uses the standard BIP-350 payload limit (~90 bytes). For large secrets
(ciphertexts, recipients) use ToBech32 / Bech32Large instead.
Sourcepub fn try_to_bech32m(&self, hrp: &str) -> Result<String, Bech32Error>
pub fn try_to_bech32m(&self, hrp: &str) -> Result<String, Bech32Error>
Encodes the secret bytes as a Bech32m (BIP-350) string with the given HRP.
Requires the encoding-bech32m and alloc features.
Sourcepub fn try_to_bech32m_zeroizing(
&self,
hrp: &str,
) -> Result<EncodedSecret, Bech32Error>
pub fn try_to_bech32m_zeroizing( &self, hrp: &str, ) -> Result<EncodedSecret, Bech32Error>
Encodes the secret bytes as a Bech32m string, returning
EncodedSecret to preserve zeroization.
Requires the encoding-bech32m and alloc features.
Sourcepub fn try_from_bech32m(
s: &str,
expected_hrp: &str,
) -> Result<Self, Bech32Error>
pub fn try_from_bech32m( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>
Decodes a Bech32m (BIP-350) string into Fixed<[u8; N]>, validating that the HRP
matches expected_hrp (case-insensitive).
HRP comparison is non-constant-time — this is intentional, as the HRP is public
metadata, not secret material. Timing leaks on HRP mismatch are acceptable because
the HRP is not secret. Prefer this over
try_from_bech32m_unchecked to prevent
cross-protocol confusion attacks.
Works without alloc — decodes into a stack-allocated Zeroizing<[u8; N]> buffer.
Sourcepub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>
pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>
Decodes a Bech32m (BIP-350) string into Fixed<[u8; N]> without validating the HRP.
Any valid HRP is accepted as long as the checksum is valid and the payload length
equals N. Use try_from_bech32m in security-critical
code to prevent cross-protocol confusion attacks.
Works without alloc — decodes into a stack-allocated Zeroizing<[u8; N]> buffer.
Source§impl<const N: usize> Fixed<[u8; N]>
impl<const N: usize> Fixed<[u8; N]>
Sourcepub fn from_random() -> Self
pub fn from_random() -> Self
Fills a new [u8; N] with cryptographically secure random bytes and wraps it.
Uses the system RNG (OsRng) via TryRngCore::try_fill_bytes.
In rand 0.9, OsRng is a zero-sized handle to the OS generator (not user-seedable). Requires the rand
feature. Heap-free and works in no_std / no_alloc builds.
§Panics
Panics if the system RNG fails to provide bytes (TryRngCore::try_fill_bytes
returns Err). This is treated as a fatal environment error.
§Examples
use secure_gate::{Fixed, RevealSecret};
let key: Fixed<[u8; 32]> = Fixed::from_random();
assert_eq!(key.len(), 32);Sourcepub fn from_rng<R: TryRngCore + TryCryptoRng>(
rng: &mut R,
) -> Result<Self, R::Error>
pub fn from_rng<R: TryRngCore + TryCryptoRng>( rng: &mut R, ) -> Result<Self, R::Error>
Fills a new [u8; N] from rng and wraps it.
Accepts any TryCryptoRng + TryRngCore — for example,
a seeded StdRng for deterministic tests. Requires the rand
feature. Heap-free.
§Errors
Returns R::Error if try_fill_bytes fails.
§Examples
use rand::rngs::StdRng;
use rand::SeedableRng;
use secure_gate::Fixed;
let mut rng = StdRng::from_seed([1u8; 32]);
let key: Fixed<[u8; 16]> = Fixed::from_rng(&mut rng).expect("rng fill");Trait Implementations§
Source§impl<T: Zeroize + CloneableSecret> Clone for Fixed<T>
Available on crate feature cloneable only.Opt-in cloning — requires cloneable feature and CloneableSecret
marker on the inner type. Each clone is independently zeroized on drop, but cloning
increases the in-memory exposure surface. Use sparingly.
impl<T: Zeroize + CloneableSecret> Clone for Fixed<T>
cloneable only.Opt-in cloning — requires cloneable feature and CloneableSecret
marker on the inner type. Each clone is independently zeroized on drop, but cloning
increases the in-memory exposure surface. Use sparingly.
Source§impl<T> ConstantTimeEq for Fixed<T>
Available on crate feature ct-eq only.Constant-time equality for Fixed<T> — routes through expose_secret().
impl<T> ConstantTimeEq for Fixed<T>
ct-eq only.Constant-time equality for Fixed<T> — routes through expose_secret().
== is deliberately not implemented on Fixed. Always use ct_eq.
use secure_gate::{Fixed, ConstantTimeEq};
let a = Fixed::new([1u8; 4]);
let b = Fixed::new([1u8; 4]);
let c = Fixed::new([2u8; 4]);
assert!(a.ct_eq(&b));
assert!(!a.ct_eq(&c));Source§impl<T: Zeroize> Debug for Fixed<T>
Always prints [REDACTED] — secrets never appear in debug output.
impl<T: Zeroize> Debug for Fixed<T>
Always prints [REDACTED] — secrets never appear in debug output.
use secure_gate::Fixed;
let key = Fixed::new([0xABu8; 32]);
assert_eq!(format!("{:?}", key), "[REDACTED]");Source§impl<'de, const N: usize> Deserialize<'de> for Fixed<[u8; N]>
Available on crate feature serde-deserialize only.Deserialization uses Zeroizing-wrapped temporary buffers — zeroized even on rejection.
impl<'de, const N: usize> Deserialize<'de> for Fixed<[u8; N]>
serde-deserialize only.Deserialization uses Zeroizing-wrapped temporary buffers — zeroized even on rejection.
Source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
Source§impl<T: Zeroize> Drop for Fixed<T>
Unconditionally zeroizes the inner value when the wrapper is dropped.
impl<T: Zeroize> Drop for Fixed<T>
Unconditionally zeroizes the inner value when the wrapper is dropped.
Warning: Drop does not run for static items or under panic = "abort".
Source§impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]>
Converts a byte array into a Fixed wrapper (equivalent to Fixed::new).
impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]>
Converts a byte array into a Fixed wrapper (equivalent to Fixed::new).
§Examples
use secure_gate::Fixed;
let secret: Fixed<[u8; 4]> = [1u8, 2, 3, 4].into();Source§impl<const N: usize, T: Zeroize> RevealSecret for Fixed<[T; N]>
Explicit access to immutable [Fixed<[T; N]>] contents.
impl<const N: usize, T: Zeroize> RevealSecret for Fixed<[T; N]>
Explicit access to immutable [Fixed<[T; N]>] contents.
Source§fn into_inner(self) -> InnerSecret<[T; N]>
fn into_inner(self) -> InnerSecret<[T; N]>
Consumes self and returns the inner [T; N] wrapped in crate::InnerSecret.
Zero cost — no allocation. The sentinel placed in self.inner is
[T::default(); N] (already zeroed for u8), so Fixed::drop zeroizes
an already-zero array — a harmless no-op.
See RevealSecret::into_inner for full documentation including the
Default bound rationale and redacted Debug behavior.
Source§fn with_secret<F, R>(&self, f: F) -> R
fn with_secret<F, R>(&self, f: F) -> R
Source§fn expose_secret(&self) -> &[T; N]
fn expose_secret(&self) -> &[T; N]
Source§impl<const N: usize, T: Zeroize> RevealSecretMut for Fixed<[T; N]>
Explicit access to mutable [Fixed<[T; N]>] contents.
impl<const N: usize, T: Zeroize> RevealSecretMut for Fixed<[T; N]>
Explicit access to mutable [Fixed<[T; N]>] contents.
Source§impl<T: Zeroize + SerializableSecret> Serialize for Fixed<T>
Available on crate feature serde-serialize only.Opt-in serialization — requires serde-serialize feature and
SerializableSecret marker on the inner type.
Serialization exposes the full secret — audit every impl.
impl<T: Zeroize + SerializableSecret> Serialize for Fixed<T>
serde-serialize only.Opt-in serialization — requires serde-serialize feature and
SerializableSecret marker on the inner type.
Serialization exposes the full secret — audit every impl.
Source§impl<const N: usize> TryFrom<&[u8]> for Fixed<[u8; N]>
Converts a byte slice into Fixed<[u8; N]>, failing if the length does not
match N.
impl<const N: usize> TryFrom<&[u8]> for Fixed<[u8; N]>
Converts a byte slice into Fixed<[u8; N]>, failing if the length does not
match N.
Internally uses Fixed::new_with so the secret is written directly into
the wrapper’s storage.
§Errors
Returns FromSliceError::InvalidLength when
slice.len() != N.
§Examples
use secure_gate::{Fixed, RevealSecret};
// Success — exact length.
let data = [0xFFu8; 4];
let secret = Fixed::<[u8; 4]>::try_from(data.as_slice()).unwrap();
assert_eq!(secret.expose_secret()[0], 0xFF);
// Failure — wrong length.
let short = [0u8; 2];
assert!(Fixed::<[u8; 4]>::try_from(short.as_slice()).is_err());Source§impl<T: Zeroize> Zeroize for Fixed<T>
Zeroizes the inner value. Called automatically by Drop.
impl<T: Zeroize> Zeroize for Fixed<T>
Zeroizes the inner value. Called automatically by Drop.
Warning: zeroization does not run for static items or under panic = "abort".
impl<T: Zeroize> ZeroizeOnDrop for Fixed<T>
Marker confirming that Fixed<T> always zeroizes on drop.