Skip to main content

Fixed

Struct Fixed 

Source
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

ConstructorFeatureNotes
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_hexencoding-hexConstant-time hex decoding
try_from_base64urlencoding-base64Constant-time Base64url decoding
try_from_bech32encoding-bech32HRP-validated Bech32 decoding
try_from_bech32_uncheckedencoding-bech32Bech32 without HRP check
try_from_bech32mencoding-bech32mHRP-validated Bech32m decoding
try_from_bech32m_uncheckedencoding-bech32mBech32m without HRP check
from_random()randSystem RNG
from_rng(rng)randCustom RNG

§See also

§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>

Source

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]>.

Source

pub fn new_with<F>(f: F) -> Self
where F: FnOnce(&mut [u8; N]),

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
Source§

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.

Source

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");
Source

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");
Source

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.
Source

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");
Source

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 a Zeroizing<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 a Zeroizing<[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 equal N.
§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]>.

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.

Source

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");
Source

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.
Source

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 a Zeroizing<Vec<u8>> then copies onto the stack.
  • Without alloc: decodes directly into a Zeroizing<[u8; N]> stack buffer.
§Errors
  • [Base64Error::InvalidBase64] — non-base64 characters or invalid padding.
  • [Base64Error::InvalidLength] — decoded byte count does not equal N.
§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]>.

Uses the extended Bech32Large checksum variant (~5 KB payload limit) rather than the 90-character standard limit. For Bitcoin address formats use ToBech32m.

Source

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.

Source

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.

Source

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.

Source

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]>.

Uses the standard BIP-350 payload limit (~90 bytes). For large secrets (ciphertexts, recipients) use ToBech32 / Bech32Large instead.

Source

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.

Source

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.

Source

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.

Source

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]>

Source

pub fn from_random() -> Self

Fills a new [u8; N] with cryptographically secure random bytes and wraps it.

Uses the system RNG (SysRng). Requires the rand feature. Heap-free and works in no_std / no_alloc builds.

§Panics

Panics if the system RNG fails to provide bytes (TryRng::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);
Source

pub fn from_rng<R: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self, R::Error>

Fills a new [u8; N] from rng and wraps it.

Accepts any TryCryptoRng + TryRng — 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.

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<T> ConstantTimeEq for Fixed<T>
where T: ConstantTimeEq + Zeroize, Self: RevealSecret<Inner = T>,

Available on crate feature 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§

fn ct_eq(&self, other: &Self) -> bool

Performs equality comparison in constant time. Read more
Source§

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§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
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.

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

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§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

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§

fn from(arr: [u8; N]) -> Self

Converts to this type from the input type.
Source§

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]>
where Self: Sized, Self::Inner: Sized + Default + Zeroize,

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§

type Inner = [T; N]

The inner secret type being revealed. Read more
Source§

fn with_secret<F, R>(&self, f: F) -> R
where F: FnOnce(&[T; N]) -> R,

Provides scoped (recommended) read-only access to the secret. Read more
Source§

fn expose_secret(&self) -> &[T; N]

Returns a direct (auditable) read-only reference to the secret. Read more
Source§

fn len(&self) -> usize

Returns the length of the secret in bytes. Read more
Source§

fn is_empty(&self) -> bool

Returns true if the secret is empty. Read more
Source§

impl<const N: usize, T: Zeroize> RevealSecretMut for Fixed<[T; N]>

Explicit access to mutable [Fixed<[T; N]>] contents.

Source§

fn with_secret_mut<F, R>(&mut self, f: F) -> R
where F: FnOnce(&mut [T; N]) -> R,

Provides scoped (recommended) mutable access to the secret. Read more
Source§

fn expose_secret_mut(&mut self) -> &mut [T; N]

Returns a direct (auditable) mutable reference to the secret. Read more
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.

Source§

fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,

Serialize this value into the given Serde serializer. Read more
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.

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§

type Error = FromSliceError

The type returned in the event of a conversion error.
Source§

fn try_from(slice: &[u8]) -> Result<Self, Self::Error>

Performs the conversion.
Source§

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".

Source§

fn zeroize(&mut self)

Zero out this object from memory using Rust intrinsics which ensure the zeroization operation is not “optimized away” by the compiler.
Source§

impl<T: Zeroize> ZeroizeOnDrop for Fixed<T>

Marker confirming that Fixed<T> always zeroizes on drop.

Auto Trait Implementations§

§

impl<T> Freeze for Fixed<T>
where T: Freeze,

§

impl<T> RefUnwindSafe for Fixed<T>
where T: RefUnwindSafe,

§

impl<T> Send for Fixed<T>
where T: Send,

§

impl<T> Sync for Fixed<T>
where T: Sync,

§

impl<T> Unpin for Fixed<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for Fixed<T>
where T: UnsafeUnpin,

§

impl<T> UnwindSafe for Fixed<T>
where T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,