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
| Constructor | Feature | Notes |
|---|---|---|
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_hex | encoding-hex | Constant-time hex decoding |
try_from_base32 | encoding-base32 | Constant-time Base32 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-bech32 | HRP-validated Bech32m decoding |
try_from_bech32m_unchecked | encoding-bech32 | Bech32m without HRP check |
from_random() | rand | System RNG |
from_rng(rng) | rand | Custom 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
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) -> Selfwhere
T: FixedStorage,
pub const fn new(value: T) -> Selfwhere
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);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 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 (requiresalloc).
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.
Sourcepub fn try_new_with<F, E>(f: F) -> Result<Self, E>
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]>.
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 try_from_hex(hex: &str) -> Result<Self, HexError>
Available on crate feature encoding-hex only.
pub fn try_from_hex(hex: &str) -> Result<Self, HexError>
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 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, 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).
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.
Sourcepub fn try_from_base32(s: &str) -> Result<Self, Base32Error>
Available on crate feature encoding-base32 only.
pub fn try_from_base32(s: &str) -> Result<Self, Base32Error>
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 aZeroizing<Vec<u8>>then copies onto the stack. - Without
alloc: decodes directly into aZeroizing<[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. Withoutallocan input that decodes to more thanNbytes also lands here rather than inInvalidLength, because the fixed stack buffer cannot hold the overflow to measure it.Base32Error::InvalidLength— decoded byte count does not equalN.
§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]>.
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 try_from_base64url(s: &str) -> Result<Self, Base64Error>
Available on crate feature encoding-base64 only.
pub fn try_from_base64url(s: &str) -> Result<Self, Base64Error>
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 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, 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]>.
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.
Sourcepub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>
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.
Sourcepub fn try_from_bech32_sized<const C: usize>(
s: &str,
expected_hrp: &str,
) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32_sized<const C: usize>( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>
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.
Sourcepub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>
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.
Sourcepub fn try_from_bech32_unchecked_sized<const C: usize>(
s: &str,
) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32_unchecked_sized<const C: usize>( s: &str, ) -> Result<Self, Bech32Error>
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]>.
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.
Sourcepub fn try_from_bech32m(
s: &str,
expected_hrp: &str,
) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32m( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>
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.
Sourcepub fn try_from_bech32m_sized<const C: usize>(
s: &str,
expected_hrp: &str,
) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32m_sized<const C: usize>( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>
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.
Sourcepub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>
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.
Sourcepub fn try_from_bech32m_unchecked_sized<const C: usize>(
s: &str,
) -> Result<Self, Bech32Error>
Available on crate feature encoding-bech32 only.
pub fn try_from_bech32m_unchecked_sized<const C: usize>( s: &str, ) -> Result<Self, Bech32Error>
encoding-bech32 only.Like try_from_bech32m_unchecked, accepting
strings up to C characters.
Source§impl<const N: usize> Fixed<[u8; N]>
impl<const N: usize> Fixed<[u8; N]>
Sourcepub fn from_random() -> Self
Available on crate feature rand only.
pub fn from_random() -> Self
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);Sourcepub fn from_rng<R: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self, R::Error>
Available on crate feature rand only.
pub fn from_rng<R: TryRng + TryCryptoRng>(rng: &mut R) -> Result<Self, R::Error>
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.
impl<T: Zeroize + CloneableSecret + FixedStorage> 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.
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>,
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".
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).
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<T: Zeroize> RevealSecret for Fixed<T>
Explicit access to immutable [Fixed<[T; N]>] contents.
impl<T: Zeroize> RevealSecret for Fixed<T>
Explicit access to immutable [Fixed<[T; N]>] contents.
Source§fn into_inner(self) -> T
fn into_inner(self) -> T
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§fn with_secret<F, R>(&self, f: F) -> R
fn with_secret<F, R>(&self, f: F) -> R
Source§fn expose_secret(&self) -> &T
fn expose_secret(&self) -> &T
Source§impl<T: Zeroize> RevealSecretMut for Fixed<T>
Explicit access to mutable [Fixed<[T; N]>] contents.
impl<T: Zeroize> RevealSecretMut for Fixed<T>
Explicit access to mutable [Fixed<[T; N]>] contents.
Source§impl<const N: usize, T: Zeroize> SecretLen for Fixed<[T; N]>
Length is only meaningful for array-shaped secrets.
impl<const N: usize, T: Zeroize> SecretLen for Fixed<[T; N]>
Length is only meaningful for array-shaped secrets.
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> ToBase32 for Fixed<[u8; N]>
Available on crate features alloc and encoding-base32 only.Base32 encoding for Fixed<[u8; N]>; delegates via with_secret.
impl<const N: usize> ToBase32 for Fixed<[u8; N]>
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
fn to_base32(&self) -> EncodedSecret
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.
impl<const N: usize> ToBase64Url for Fixed<[u8; N]>
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
fn to_base64url(&self) -> EncodedSecret
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.
impl<const N: usize> ToBech32 for Fixed<[u8; N]>
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>
fn try_to_bech32( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>
Source§fn try_to_bech32_sized<const C: usize>(
&self,
hrp: &str,
case: Case,
) -> Result<EncodedSecret, Bech32Error>
fn try_to_bech32_sized<const C: usize>( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>
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.
impl<const N: usize> ToBech32m for Fixed<[u8; N]>
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>
fn try_to_bech32m( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>
Source§fn try_to_bech32m_sized<const C: usize>(
&self,
hrp: &str,
case: Case,
) -> Result<EncodedSecret, Bech32Error>
fn try_to_bech32m_sized<const C: usize>( &self, hrp: &str, case: Case, ) -> Result<EncodedSecret, Bech32Error>
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.
impl<const N: usize> ToHex for Fixed<[u8; N]>
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
fn to_hex(&self) -> EncodedSecret
Source§fn to_hex_upper(&self) -> EncodedSecret
fn to_hex_upper(&self) -> EncodedSecret
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.
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> RevealScoped for Twhere
T: RevealSecret + ?Sized,
impl<T> RevealScoped for Twhere
T: RevealSecret + ?Sized,
Source§type Inner = <T as RevealSecret>::Inner
type Inner = <T as RevealSecret>::Inner
sealed only.Source§fn with_secret<F, R>(&self, f: F) -> R
fn with_secret<F, R>(&self, f: F) -> R
sealed only.f with a reference to the secret that cannot outlive the call.