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.

Choose Fixed for short-lived secrets passed by reference. Inline storage means a Fixed can live on the stack, and moving one by value copies its bytes and leaves the old slot unwiped: drop zeroizes only where the value ends up. Build it in place with new_with, pass &Fixed rather than Fixed, and for a secret held a long time prefer Dynamic (one heap address) or, with the sealed feature, SealedFixed. SECURITY.md §1 has the details.

§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
try_new_with(f)—Scoped, fallible fill; zeroizes the partial write on Err
From<[u8; N]>—Equivalent to new
TryFrom<&[u8]>—Length-checked slice conversion
try_from_hexencoding-hexConstant-time hex decoding
try_from_base32encoding-base32Constant-time Base32 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-bech32HRP-validated Bech32m decoding
try_from_bech32m_uncheckedencoding-bech32Bech32m without HRP check
from_random()randSystem RNG
from_rng(rng)randCustom RNG

§What T may be

Fixed::new requires FixedStorage on the inner type: a type that owns no heap allocation. This is what makes the documented claim that Fixed has no reallocation surface true rather than aspirational. It used to be bounded only by Zeroize, so Fixed<Vec<u8>>, Fixed<String> and even Fixed<[Vec<u8>; 2]> compiled and abandoned an unwiped buffer holding the whole secret on any capacity change. Arrays, the primitives, tuples, Option and boxed slices satisfy the marker already; a custom inner type writes one line. For a growable payload use Dynamic, which documents the residue and offers one safe growth path.

The bound is on the constructor rather than on the struct, so naming Fixed<Vec<u8>> still compiles and no value of it can exist — the same shape as the zero-size guard below.

§Zero-size

A value of zero size has nothing to protect, so a Fixed cannot be built around one. new and new_with each assert a non-zero size in a const, and every other constructor in the table above funnels through those two, so Fixed<[u8; 0]> — and any other zero-sized inner type — is a compile error however it is reached. The assertion is evaluated at monomorphization rather than at the declaration, so it fires at the first concrete zero-sized construction, including one reached through generic code; generic code that is never instantiated with a zero-sized T is unaffected, and naming such a type without constructing one still compiles.

The diagnostic names the offending type in the failing constant’s path — for example Fixed::<[u8; 0]>::NON_ZERO_SIZED — and a while instantiating note repeats the span of the new call the monomorphization reached — the error’s own span is the assertion here, not in your code. The note does not name the instantiation that caused it: for a generic fn build<const N: usize>(), calling build::<0>() reports against the Fixed::new line inside build, and the ::<0> call site appears nowhere.

Two limits on when it fires, both measured. It is raised during codegen, so cargo check does not report it. And it only fires for a codegen root: a non-generic #[inline] function in a library is not one, and every method the newtype macros generate carries an inline attribute, so a library whose only zero-sized constructions sit behind #[inline] or generic code builds, tests and publishes clean — the error then surfaces in each downstream crate that instantiates it, blaming this crate and the dependency’s macro invocation rather than the consumer’s call.

Root-ness is a property of the compiler and the profile, not of the attribute alone, so removing #[inline] does not reliably restore the error. Measured: on 1.85 a library exporting a plain non-generic pub fn empty() -> Empty { Empty::new([]) } fails cargo build and cargo test but passes cargo build --release, because the cross-crate-inlining heuristic added in 1.75 drops a small function from the exported root set in an optimized build; on 1.70 the same crate fails in both profiles. Treat instantiating the type in a test or a binary as the only reliable check.

Stated precisely: no value of a zero-sized Fixed can exist at runtime, because nothing can construct one, and a binary or test that tries fails to compile. The guard does not stop a library from exporting an unusable zero-sized API. Neither limit is a choice — a condition on a generic parameter has nothing to evaluate until that parameter is known, and nothing on stable Rust moves it earlier.

Dynamic has no compile-time counterpart, for a reason about its payload rather than its own size: whether a Vec or String is empty is a runtime property, so there is nothing for a compile-time check to decide. A statically zero-sized inner type is the gap that leaves — Dynamic<Zst> constructs where Fixed<Zst> does not.

§RustCrypto integration

RustCrypto’s BlockEncrypt/BlockDecrypt take &mut GenericArray<u8, U16>, and GenericArray::from_mut_slice borrows its argument instead of copying it. That is what lets a block cipher run inside the wrapper, so the plaintext never materializes in memory nothing is obliged to wipe:

use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit};
use aes::Aes128;
use secure_gate::{Fixed, RevealSecretMut};

let cipher = Aes128::new(GenericArray::from_slice(&[0x42u8; 16]));
let mut block: Fixed<[u8; 16]> = Fixed::new([0u8; 16]);

// `from_mut_slice` reborrows the wrapper's own storage, so zeroize-on-drop still
// covers every path out of this scope — early returns and panics included.
block.with_secret_mut(|b| cipher.decrypt_block(GenericArray::from_mut_slice(b)));

The shape type inference nudges you toward is the footgun. It compiles, it is the easy thing to reach for, and it defeats the wrapper:

// Copies the secret OUT. `copy` is an ordinary stack value: no zeroization on
// drop, and it holds plaintext-equivalent bytes for the rest of the scope.
let mut copy = block.with_secret(|b| aes::Block::from(*b));
cipher.decrypt_block(&mut copy);

The same reasoning applies to Dynamic<Vec<u8>> through with_secret_mut, and to the slice-at-a-time APIs (decrypt_blocks, apply_keystream), which take &mut [Block] and &mut [u8] and so borrow just as well.

§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
where T: FixedStorage,

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.

Prefer new_with when T: SentinelValue and minimizing stack residue matters: new may leave an intermediate copy of value on the caller’s stack frame.

A zero-sized T is rejected at compile time; see Zero-size.

§Examples
use secure_gate::{Fixed, RevealSecret, SecretLen};

let secret = Fixed::new([0u8; 32]);
assert_eq!(secret.len(), 32);
Source

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 slot is initialized to T::sentinel_value() before the closure runs. Prefer this over new(value) when minimizing stack residue matters (long-lived keys, high-assurance environments).

§The slot’s starting content is a contract, not an accident

For T = [E; N] with E: Default — every array this crate ever hands you — SentinelValue’s own contract fixes that starting value at [E::default(); N], all-zero for the byte and integer arrays this type is built around. That is a promise SentinelValue makes on purpose (see its “Provided implementations” contract), not a side effect of how the sentinel happens to be built today, so a caller may depend on it exactly as it depends on any other documented behavior.

Concretely: a closure that writes fewer than N elements — a SlotWriter doing RFC 9180-style concatenation that falls short of the slot, a key derivation shorter than its buffer — leaves the untouched tail at zero rather than at whatever bytes happened to be on the stack. Code that treats that tail as zero-padding, instead of re-zeroing it itself before returning, is relying on this guarantee; the SlotWriter documentation’s “40-bit key, zero-padded to 16” example is exactly that reliance, asserted in a doctest.

Requires FixedStorage and SentinelValue on T. Arrays of a Default element already have both; a custom inner type needs the same pair into_inner already asks for.

§T must be inferable at the call site

This constructor is generic over T, so the closure’s parameter type is not known until T is. A return type alone does not resolve it in time: fn k() -> Fixed<[u8; 32]> { Fixed::new_with(|a| a.fill(1)) } fails with E0282, type annotations needed for &mut _, because the closure body is checked before the expected return type reaches T. Name the type in either position — both compile, pick whichever reads better:

use secure_gate::Fixed;

// On the type:
let a = Fixed::<[u8; 32]>::new_with(|x| x.fill(1));
// Or on the closure parameter:
let b = Fixed::new_with(|x: &mut [u8; 32]| x.fill(1));

Before 0.9.0-rc.10 this method was inherent to Fixed<[u8; N]>, where the parameter was always a byte array and inference had nothing to resolve. A newtype built by fixed_newtype! is unaffected in both arms: its generated new_with has a concrete parameter type.

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

A non-byte array uses the same constructor:

use secure_gate::{Fixed, RevealSecret};

let poly = Fixed::<[i16; 4]>::new_with(|c| c.fill(7));
assert_eq!(poly.expose_secret(), &[7i16; 4]);
§See also
  • try_new_with — the same thing when the fill can fail, rather than routing the error out through a captured local.
  • Dynamic::new_with — the heap-allocated equivalent (requires alloc).

If the secret will outlive the current function, prefer Dynamic<T> over moving Fixed<T> around — each move-by-value leaves residue in the previous stack slot.

Source

pub fn try_new_with<F, E>(f: F) -> Result<Self, E>

Writes directly into the wrapper’s storage via a closure that may fail.

The fallible sibling of new_with, and identical to it in every respect but the closure’s return type. Reach for it whenever filling the secret can fail — reading from a file, a socket, a device — so that writing in place and handling the error stay one expression.

On Err the partially written slot is zeroized before the error is returned: the wrapper exists for the whole of the closure’s run, so Drop wipes whatever the closure managed to write. A failed fill leaves nothing behind, in memory or in the return value.

On Ok, the slot the closure saw started at the same value new_with’s does — T::sentinel_value(), which for T = [E; N] with E: Default is [E::default(); N], all-zero for the byte and integer arrays this type is built around. That starting value is a contract of SentinelValue’s, documented on new_with above, not an incidental one — so a closure here that only partially fills the slot before returning Ok leaves the same reliable zero tail behind, on the same guarantee.

Requires FixedStorage and SentinelValue on T, exactly as new_with does.

§Why try_, given the rest of this type

Elsewhere here try_ marks a constructor that parses untrusted input — try_from_hex, try_from_base32, TryFrom<&[u8]> — while from_rng is fallible and keeps from_. This method widens the prefix to its ordinary Rust meaning, the one try_reserve and try_lock carry: try_ marks a constructor that can fail, whether because it validates input or because the caller’s fill can.

§T must be inferable at the call site

Generic over T, so the closure’s parameter type is not known until T is — the same constraint new_with documents, with the same E0282 if nothing names it. A return type alone does not resolve it. Name the type in either position:

use secure_gate::Fixed;

// On the type:
let a = Fixed::<[u8; 32]>::try_new_with(|x| { x.fill(1); Ok::<(), ShortRead>(()) })?;
// Or on the closure parameter:
let b = Fixed::try_new_with(|x: &mut [u8; 32]| { x.fill(1); Ok::<(), ShortRead>(()) })?;
§Errors

Returns whatever the closure returns. The error type is the caller’s; this method neither inspects nor wraps it.

§Examples

Filling from a reader, which is the case this exists for — the bytes land in the wrapper’s own storage and never occupy a caller-owned buffer:

use secure_gate::{Fixed, RevealSecret};
use std::io::Read;

fn read_key<R: Read>(r: &mut R) -> std::io::Result<Fixed<[u8; 32]>> {
    Fixed::<[u8; 32]>::try_new_with(|slot| r.read_exact(slot))
}

let key = read_key(&mut [7u8; 32].as_slice()).unwrap();
assert_eq!(key.expose_secret(), &[7u8; 32]);

// A short read fails, and yields no secret at all.
assert!(read_key(&mut [7u8; 8].as_slice()).is_err());
§See also
  • new_with — the infallible form; prefer it when the fill cannot fail.
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 try_from_hex(hex: &str) -> Result<Self, HexError>

Available on crate feature encoding-hex only.

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
§Examples
use secure_gate::{Fixed, RevealSecret, ToHex};

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

Base32 encoding and decoding for Fixed<[u8; N]> (RFC 4648 §6, uppercase, unpadded).

Encoding uses a constant-time backend (base32ct). 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 try_from_base32(s: &str) -> Result<Self, Base32Error>

Available on crate feature encoding-base32 only.

Decodes an uppercase, unpadded Base32 string (RFC 4648 §6 alphabet) into Fixed<[u8; N]>.

Uses a constant-time backend (base32ct) 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.

Decoding is strict about the alphabet, case, padding and length, but lenient about non-canonical trailing bits: unused bits in the final group are ignored rather than rejected, so "MZ" and "MY" both decode to [0x66]. Decoding is therefore not injective — two distinct strings can yield the same bytes, and only encoder-produced strings are canonical. Do not use the decoded value to decide that two encoded strings were equal.

§Errors
  • Base32Error::InvalidBase32 — wrong alphabet or case, = padding, or a length that is impossible for unpadded Base32. Without alloc an input that decodes to more than N bytes also lands here rather than in InvalidLength, because the fixed stack buffer cannot hold the overflow to measure it.
  • Base32Error::InvalidLength — decoded byte count does not equal N.
§Examples
use secure_gate::{Fixed, RevealSecret, ToBase32};

// Round-trip.
let original = Fixed::new([0xDE, 0xAD, 0xBE, 0xEF]);
let encoded = original.to_base32();
assert_eq!(&*encoded, "32W353Y");
let decoded = Fixed::<[u8; 4]>::try_from_base32(&encoded).unwrap();
assert_eq!(decoded.expose_secret(), &[0xDE, 0xAD, 0xBE, 0xEF]);
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 try_from_base64url(s: &str) -> Result<Self, Base64Error>

Available on crate feature encoding-base64 only.

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
§Examples
use secure_gate::{Fixed, RevealSecret, ToBase64Url};

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

The plain constructors accept strings up to BECH32_CODE_LENGTH; the _sized::<C> constructors take the code length as a parameter for larger payloads. For Bitcoin address formats use ToBech32m and stay within BIP-173’s 90-character convention.

Source

pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

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_sized<const C: usize>( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

Like try_from_bech32, accepting strings up to C characters.

C is the bech32 code length – the cap on the whole encoded string – and is independent of N, the decoded byte count this wrapper holds. Pass the C the string was encoded with, or any larger value. See Bech32Sized for what C above BECH32_CODE_LENGTH costs.

Works without alloc – decodes into a stack-allocated Zeroizing<[u8; N]> buffer.

Source

pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

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

pub fn try_from_bech32_unchecked_sized<const C: usize>( s: &str, ) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

Like try_from_bech32_unchecked, accepting strings up to C characters.

Source§

impl<const N: usize> Fixed<[u8; N]>

Bech32m (BIP-350) encoding and decoding for Fixed<[u8; N]>.

The plain constructors accept strings up to BECH32_CODE_LENGTH; the _sized::<C> constructors take the code length as a parameter. Bitcoin address tooling expects BIP-173’s 90-character cap, which is narrower than either and is the caller’s to respect.

Source

pub fn try_from_bech32m( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

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_sized<const C: usize>( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

Like try_from_bech32m, accepting strings up to C characters.

C is the bech32m code length, independent of N, the decoded byte count. See Bech32mSized for what C above BECH32_CODE_LENGTH costs.

Works without alloc – decodes into a stack-allocated Zeroizing<[u8; N]> buffer.

Source

pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

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

pub fn try_from_bech32m_unchecked_sized<const C: usize>( s: &str, ) -> Result<Self, Bech32Error>

Available on crate feature encoding-bech32 only.

Like try_from_bech32m_unchecked, accepting strings up to C characters.

Source§

impl<const N: usize> Fixed<[u8; N]>

Source

pub fn from_random() -> Self

Available on crate feature rand only.

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, SecretLen};

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>

Available on crate feature rand only.

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 + FixedStorage> 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 (const: unstable) · 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.

The sequence buffer is pre-allocated to exactly N bytes and never grows: an input sequence longer than N is rejected before the element that would trigger a reallocation is stored. This matters because a Vec reallocation frees the old buffer — which would already hold N secret bytes — without zeroizing it.

The entry point is deserialize_seq (unchanged wire format for non-self-describing formats such as postcard), but the visitor also accepts byte strings via visit_bytes / visit_byte_buf, so self-describing formats that encode byte arrays as byte strings (e.g. CBOR) round-trip too. Owned buffers handed over through visit_byte_buf are zeroized after the copy.

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§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl<T: Zeroize + FixedStorage> FixedStorage for Fixed<T>

A Fixed<T> holds T inline, so the wrapper nests: Fixed<Fixed<[u8; 32]>> is legal exactly when the payload qualifies. Without this impl the crate’s own wrapper could not be used as its own inner type.

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<T: Zeroize> RevealSecret for Fixed<T>

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

Source§

fn into_inner(self) -> T
where Self: Sized, Self::Inner: Sized + SentinelValue + Zeroize,

Consumes self and transfers ownership of the plain [T; N].

Zero cost — no allocation. The sentinel placed in self.inner is [T::default(); N] via crate::SentinelValue (already zeroed for u8), so Fixed::drop zeroizes an already-zero array — a harmless no-op. Works for any array length N (not limited to 32 like Default).

See RevealSecret::into_inner for the full contract, including the SentinelValue bound rationale. Protection ends at this call: the array you get back is plain — not wiped on drop, and its Debug is not redacted.

Source§

type Inner = T

The inner secret type being revealed. Read more
Source§

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

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

fn expose_secret(&self) -> &T

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

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

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) -> R,

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

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

Returns a direct (auditable) mutable reference to the secret. Read more
Source§

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

Length is only meaningful for array-shaped secrets.

Source§

fn len(&self) -> usize

Returns the number of elements in the secret, matching the underlying container’s len() — element count for Vec<T> and [T; N], byte count for String (where the element is a byte).
Source§

fn byte_len(&self) -> usize

Returns the total size of the secret in bytes. Read more
Source§

fn is_empty(&self) -> bool

Returns true if the secret is empty.
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> ToBase32 for Fixed<[u8; N]>

Available on crate features alloc and encoding-base32 only.

Base32 encoding for Fixed<[u8; N]>; delegates via with_secret.

Bring the trait into scope to call these: use secure_gate::ToBase32;.

use secure_gate::{Fixed, ToBase32};

let key = Fixed::new([0xABu8; 4]);
assert_eq!(&*key.to_base32(), "VOV2XKY");
Source§

fn to_base32(&self) -> EncodedSecret

Encode bytes as Base32 (RFC 4648 §6 alphabet, uppercase, no padding).
Source§

impl<const N: usize> ToBase64Url for Fixed<[u8; N]>

Available on crate features alloc and encoding-base64 only.

Base64url encoding for Fixed<[u8; N]>; delegates via with_secret.

Bring the trait into scope to call these: use secure_gate::ToBase64Url;.

use secure_gate::{Fixed, ToBase64Url};

let key = Fixed::new([0xABu8; 4]);
assert_eq!(&*key.to_base64url(), "q6urqw");
Source§

fn to_base64url(&self) -> EncodedSecret

Encode bytes as URL-safe base64 (no padding).
Source§

impl<const N: usize> ToBech32 for Fixed<[u8; N]>

Available on crate features alloc and encoding-bech32 only.

Bech32 encoding for Fixed<[u8; N]>; delegates via with_secret.

Bring the trait into scope to call these: use secure_gate::ToBech32;.

Source§

fn try_to_bech32( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>

Fallibly encodes bytes as a Bech32 (BIP-173) string with the given HRP. Read more
Source§

fn try_to_bech32_sized<const C: usize>( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>

Like try_to_bech32, with a caller-chosen code length N. Read more
Source§

impl<const N: usize> ToBech32m for Fixed<[u8; N]>

Available on crate features alloc and encoding-bech32 only.

Bech32m encoding for Fixed<[u8; N]>; delegates via with_secret.

Bring the trait into scope to call these: use secure_gate::ToBech32m;.

Source§

fn try_to_bech32m( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>

Fallibly encodes bytes as a Bech32m (BIP-350) string with the given HRP. Read more
Source§

fn try_to_bech32m_sized<const C: usize>( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>

Like try_to_bech32m, with a caller-chosen code length N. Read more
Source§

impl<const N: usize> ToHex for Fixed<[u8; N]>

Available on crate features alloc and encoding-hex only.

Hex encoding for Fixed<[u8; N]> — same constant-time backend as the ToHex blanket impl on the inner bytes; delegates via with_secret.

Bring the trait into scope to call these: use secure_gate::ToHex;.

use secure_gate::{Fixed, ToHex};

let key = Fixed::new([0xABu8; 4]);
assert_eq!(&*key.to_hex(), "abababab");
assert_eq!(&*key.to_hex_upper(), "ABABABAB");
// Both return an `EncodedSecret` — the encoded form wipes itself on drop.
Source§

fn to_hex(&self) -> EncodedSecret

Encode bytes as lowercase hexadecimal.
Source§

fn to_hex_upper(&self) -> EncodedSecret

Encode bytes as uppercase hexadecimal.
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> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

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> RevealScoped for T
where T: RevealSecret + ?Sized,

Source§

type Inner = <T as RevealSecret>::Inner

Available on crate feature sealed only.
The secret’s type as the closure sees it.
Source§

fn with_secret<F, R>(&self, f: F) -> R
where F: FnOnce(&<T as RevealScoped>::Inner) -> R,

Available on crate feature sealed only.
Calls f with a reference to the secret that cannot outlive the call.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
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 = !

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

fn try_from(value: U) -> Result<T, !>

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.