Expand description
Secure wrappers for secrets with explicit access and mandatory zeroization — a
no_std-compatible, zero-overhead library with audit-friendly access patterns.
Secrets are automatically zeroized on drop (the inner type must implement
Zeroize). No Deref, no accidental leaks — callers access
the inner secret only via RevealSecret / RevealSecretMut. Debug always
prints [REDACTED]. All access follows a 3-tier model: scoped closures (preferred),
direct references (escape hatch), and owned extraction (consumption).
§Which type should I use?
| Type | Allocation | Use case | Feature |
|---|---|---|---|
Fixed<T> | Stack | Keys, nonces, tokens — compile-time-known size | Always available |
Dynamic<T> | Heap | Passwords, API keys, ciphertexts — variable length | alloc (default) |
Both types share the same RevealSecret / RevealSecretMut access API.
§Quick start
use secure_gate::{Fixed, RevealSecret};
// Wrap a 32-byte key
let key = Fixed::new([0x42u8; 32]);
// Tier 1 — scoped access (preferred): secret ref cannot escape the closure
let first = key.with_secret(|bytes| bytes[0]);
assert_eq!(first, 0x42);
// Tier 2 — direct reference (escape hatch for FFI / third-party APIs)
assert_eq!(key.expose_secret().len(), 32);
// Debug is always redacted
assert_eq!(format!("{:?}", key), "[REDACTED]");
// key is zeroized when droppeduse secure_gate::{Dynamic, RevealSecret};
let password: Dynamic<String> = Dynamic::new(String::from("hunter2"));
let len = password.with_secret(|s: &String| s.len());
assert_eq!(len, 7);use secure_gate::{fixed_alias, RevealSecret};
fixed_alias!(pub Aes256Key, 32);
let key: Aes256Key = [0xABu8; 32].into();
key.with_secret(|b| assert_eq!(b.len(), 32));§Module structure
secure_gate (lib.rs)
├── Fixed<T> ← always available, stack-allocated
├── Dynamic<T> ← requires `alloc`, heap-allocated
├── traits/
│ ├── RevealSecret ← immutable access (always available)
│ ├── RevealSecretMut ← mutable access (always available)
│ ├── revealed_secrets/
│ │ ├── InnerSecret<T> ← owned extraction wrapper
│ │ └── EncodedSecret ← zeroizing encoded string wrapper (alloc)
│ ├── ConstantTimeEq ← ct-eq feature
│ ├── CloneableSecret ← cloneable feature
│ ├── SerializableSecret← serde-serialize feature
│ ├── encoding/ ← ToHex, ToBase64Url, ToBech32, ToBech32m
│ └── decoding/ ← FromHexStr, FromBase64UrlStr, FromBech32Str, FromBech32mStr
├── macros/ ← fixed_alias!, dynamic_alias!, etc.
└── error ← FromSliceError, HexError, Base64Error, Bech32Error, DecodingErrorAll public items are re-exported at the crate root. Use secure_gate::Fixed,
not secure_gate::fixed::Fixed.
§Import paths
// ✅ Correct — always import from the crate root
use secure_gate::{Fixed, RevealSecret};
// ❌ Wrong — these internal paths compile but are not the public API
// use secure_gate::traits::reveal_secret::RevealSecret;
// use secure_gate::traits::encoding::hex::ToHex;§Method resolution: wrapper methods vs trait methods
Encoding methods exist at two levels — both produce identical results:
| Call style | Example | Appears in audit sweep? |
|---|---|---|
| Wrapper inherent (ergonomic) | key.to_hex() | No — grep for to_hex directly |
| Trait via scoped access (audit-friendly) | key.with_secret(|b| b.to_hex()) | Yes — with_secret is grep-able |
The wrapper methods (Fixed::to_hex, Dynamic::to_hex) internally call
self.with_secret(|s| s.to_hex()) — they are convenience shorthands, not
separate implementations.
§Feature flags
| Feature | Default | Description |
|---|---|---|
alloc | yes | Heap types (Dynamic<T>), Vec/String zeroization |
std | no | Full std support (implies alloc) |
| Cryptographic | ||
ct-eq | no | ConstantTimeEq via subtle — timing-safe comparison |
rand | no | from_random() / from_rng() — no_std for Fixed |
| Serialization | ||
serde-serialize | no | Serde Serialize (requires SerializableSecret marker) |
serde-deserialize | no | Serde Deserialize with 1 MiB default limit |
serde | no | Both directions |
| Encoding | ||
encoding-hex | no | ToHex / FromHexStr via base16ct (constant-time) |
encoding-base64 | no | ToBase64Url / FromBase64UrlStr via base64ct (constant-time) |
encoding-bech32 | no | ToBech32 / FromBech32Str — BIP-173, extended ~5 KB limit |
encoding-bech32m | no | ToBech32m / FromBech32mStr — BIP-350, standard 90-byte limit |
encoding | no | All encoding features |
| Meta | ||
cloneable | no | CloneableSecret opt-in cloning |
full | no | Everything |
§What’s available without alloc?
With default-features = false:
Fixed<T>,RevealSecret,RevealSecretMut,InnerSecretFixed::try_from_hex,Fixed::try_from_base64url,Fixed::try_from_bech32,Fixed::try_from_bech32m(no-alloc stack-based decoding)fixed_alias!,fixed_generic_alias!FromSliceError
Not available without alloc: Dynamic<T>, EncodedSecret,
encoding traits (ToHex, etc.), decoding traits (FromHexStr, etc.),
dynamic_alias!, dynamic_generic_alias!, serde support.
§no_std
no_std compatible. Fixed<T> works without alloc. Enable alloc (default) for
Dynamic<T>. For pure stack / embedded builds, use default-features = false.
MSRV: 1.85 (Rust edition 2024).
§Security
This crate has not undergone an independent security audit. No unsafe code —
enforced with #![forbid(unsafe_code)]. Prefer scoped access (RevealSecret::with_secret)
over direct references. Prefer zeroizing encoding variants (to_hex_zeroizing, etc.)
when the encoded form is sensitive. See
SECURITY.md
for the full threat model.
See the README and SECURITY.md for full details.
Re-exports§
pub use traits::CloneableSecret;pub use traits::ConstantTimeEq;pub use traits::RevealSecret;pub use traits::RevealSecretMut;pub use traits::InnerSecret;pub use traits::EncodedSecret;pub use traits::SerializableSecret;pub use traits::FromBase64UrlStr;pub use traits::FromBech32Str;pub use traits::FromBech32mStr;pub use traits::FromHexStr;pub use traits::ToBase64Url;pub use traits::ToBech32;pub use traits::ToBech32m;pub use traits::ToHex;pub use traits::SecureDecoding;pub use traits::SecureEncoding;
Modules§
- traits
- Core traits for wrapper polymorphism - always available. Traits for polymorphic secret handling.
Macros§
- dynamic_
alias - Creates a type alias for
Dynamic<T>. - dynamic_
generic_ alias - Creates a generic type alias
Name<T>forDynamic<T>. - fixed_
alias - Creates a type alias for
Fixed<[u8; N]>. - fixed_
generic_ alias - Creates a const-generic type alias
Name<const N: usize>forFixed<[u8; N]>.
Structs§
- Dynamic
- Heap-allocated secret wrapper with explicit access and automatic zeroization on drop.
- Dynamic
Reader - Cursor-like reader over
Dynamic<Vec<u8>>— seeDynamic::as_reader. Cursor-like reader over aDynamic<Vec<u8>>. - Fixed
- Stack-allocated secret wrapper with explicit access and automatic zeroization on drop.
Enums§
- Base64
Error - Errors from Base64url decoding. Debug builds include detailed context; release builds use generic messages. Errors produced when decoding base64url strings.
- Bech32
Error - Errors from Bech32 (BIP-173) and Bech32m (BIP-350) decoding. Debug builds include detailed context; release builds use generic messages. Errors produced when decoding Bech32 (BIP-173) or Bech32m (BIP-350) strings.
- Decoding
Error - Unified error type wrapping format-specific decoding errors (
HexError,Base64Error,Bech32Error). Always available; variants depend on enabled features. Unified error type for multi-format decoding operations. - From
Slice Error - Error returned when a byte slice cannot be converted to
Fixed<[u8; N]>due to length mismatch. Produced byFixed::try_from(&[u8]). Error returned when a byte slice cannot be converted to a fixed-size array. - HexError
- Errors from hex decoding. Debug builds include detailed context; release builds use generic messages. Errors produced when decoding hexadecimal strings.
Constants§
- MAX_
DESERIALIZE_ BYTES - Default maximum byte length for
Dynamic<Vec<u8>>/Dynamic<String>deserialization (1 MiB).