pub struct Dynamic<T: ?Sized + Zeroize> { /* private fields */ }Expand description
Heap-allocated secret wrapper with explicit access and automatic zeroization on drop.
Variable-length secrets (passwords, API keys, ciphertexts). Inner type must implement
Zeroize. Secret bytes live on the heap only — never on the stack. Requires alloc.
See Fixed<T> for the stack-allocated alternative.
use secure_gate::{Dynamic, RevealSecret};
let pw: Dynamic<String> = Dynamic::new(String::from("hunter2"));
assert_eq!(pw.with_secret(|s: &String| s.len()), 7);
assert_eq!(format!("{:?}", pw), "[REDACTED]");Zero-cost heap-allocated wrapper for variable-length secrets.
Dynamic<T> stores a T: Zeroize value in a Box<T> and unconditionally zeroizes
it on drop (including Vec/String spare capacity). There is no Deref, AsRef,
or Copy — every access is explicit through RevealSecret
or RevealSecretMut.
This is not Fixed<T> — it is the heap-allocated alternative for variable-length
secrets. Secret bytes never reside on the stack.
§Examples
use secure_gate::{Dynamic, RevealSecret};
let pw: Dynamic<String> = Dynamic::new(String::from("hunter2"));
assert_eq!(pw.with_secret(|s: &String| s.len()), 7);
assert_eq!(format!("{:?}", pw), "[REDACTED]");§Constructors for Dynamic<Vec<u8>>
| Constructor | Feature | Notes |
|---|---|---|
new(value) | — | Accepts Vec<u8>, &[u8], Box<Vec<u8>> |
new_with(f) | — | Scoped closure construction |
try_from_hex(s) | encoding-hex | Constant-time hex decoding |
try_from_base64url(s) | encoding-base64 | Constant-time Base64url decoding |
try_from_bech32(s, hrp) | encoding-bech32 | HRP-validated Bech32 |
try_from_bech32_unchecked(s) | encoding-bech32 | Bech32 without HRP check |
try_from_bech32m(s, hrp) | encoding-bech32m | HRP-validated Bech32m |
try_from_bech32m_unchecked(s) | encoding-bech32m | Bech32m without HRP check |
from_random(len) | rand | System RNG |
from_rng(len, rng) | rand | Custom RNG |
§See also
RevealSecret/RevealSecretMut— the 3-tier access traits.Fixed<T>— stack-allocated alternative.
Implementations§
Source§impl<T: ?Sized + Zeroize> Dynamic<T>
impl<T: ?Sized + Zeroize> Dynamic<T>
Sourcepub fn new<U>(value: U) -> Self
pub fn new<U>(value: U) -> Self
Wraps value in a Box<T> and returns a Dynamic<T>.
Accepts any type that implements Into<Box<T>> — including owned values,
Box<T>, String, Vec<u8>, &str (via the blanket From<&str> impl), etc.
Equivalent to Dynamic::from(value) — #[doc(alias = "from")] is set so both
names appear in docs.rs search.
Requires the alloc feature (which Dynamic<T> itself always requires).
Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn to_hex_upper(&self) -> String
pub fn to_hex_upper(&self) -> String
Encodes the secret bytes as an uppercase hex string.
Sourcepub fn to_hex_zeroizing(&self) -> EncodedSecret
pub fn to_hex_zeroizing(&self) -> EncodedSecret
Encodes the secret bytes as a lowercase hex string, returning
EncodedSecret to preserve zeroization.
Sourcepub fn to_hex_upper_zeroizing(&self) -> EncodedSecret
pub fn to_hex_upper_zeroizing(&self) -> EncodedSecret
Encodes the secret bytes as an uppercase hex string, returning
EncodedSecret to preserve zeroization.
Sourcepub fn try_from_hex(s: &str) -> Result<Self, HexError>
pub fn try_from_hex(s: &str) -> Result<Self, HexError>
Decodes a hex string (lowercase, uppercase, or mixed) into Dynamic<Vec<u8>>.
The decoded buffer is kept inside a Zeroizing wrapper until after the
Box allocation completes, guaranteeing zeroization even on OOM panic.
Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn to_base64url(&self) -> String
pub fn to_base64url(&self) -> String
Encodes the secret bytes as an unpadded Base64url string (RFC 4648, URL-safe alphabet).
Sourcepub fn to_base64url_zeroizing(&self) -> EncodedSecret
pub fn to_base64url_zeroizing(&self) -> EncodedSecret
Encodes the secret bytes as an unpadded Base64url string, returning
EncodedSecret to preserve zeroization.
Sourcepub fn try_from_base64url(s: &str) -> Result<Self, Base64Error>
pub fn try_from_base64url(s: &str) -> Result<Self, Base64Error>
Decodes a Base64url (unpadded) string into Dynamic<Vec<u8>>.
The decoded buffer is kept inside a Zeroizing wrapper until after the
Box allocation completes, guaranteeing zeroization even on OOM panic.
Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn try_to_bech32(&self, hrp: &str) -> Result<String, Bech32Error>
pub fn try_to_bech32(&self, hrp: &str) -> Result<String, Bech32Error>
Encodes the secret bytes as a Bech32 (BIP-173) string with the given HRP.
Sourcepub fn try_to_bech32_zeroizing(
&self,
hrp: &str,
) -> Result<EncodedSecret, Bech32Error>
pub fn try_to_bech32_zeroizing( &self, hrp: &str, ) -> Result<EncodedSecret, Bech32Error>
Encodes the secret bytes as a Bech32 string, returning
EncodedSecret to preserve zeroization.
Sourcepub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>
pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, Bech32Error>
Decodes a Bech32 (BIP-173) string into Dynamic<Vec<u8>>, validating the HRP
(case-insensitive).
The decoded buffer is kept inside a Zeroizing wrapper until after the
Box allocation completes, guaranteeing zeroization even on OOM panic.
HRP comparison is non-constant-time — this is intentional, as the HRP is public metadata, not secret material.
Sourcepub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>
pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, Bech32Error>
Decodes a Bech32 (BIP-173) string into Dynamic<Vec<u8>> without validating the HRP.
Use try_from_bech32 in security-critical code to prevent
cross-protocol confusion attacks.
Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn try_to_bech32m(&self, hrp: &str) -> Result<String, Bech32Error>
pub fn try_to_bech32m(&self, hrp: &str) -> Result<String, Bech32Error>
Encodes the secret bytes as a Bech32m (BIP-350) string with the given HRP.
Sourcepub fn try_to_bech32m_zeroizing(
&self,
hrp: &str,
) -> Result<EncodedSecret, Bech32Error>
pub fn try_to_bech32m_zeroizing( &self, hrp: &str, ) -> Result<EncodedSecret, Bech32Error>
Encodes the secret bytes as a Bech32m string, returning
EncodedSecret to preserve zeroization.
Sourcepub fn try_from_bech32m(
s: &str,
expected_hrp: &str,
) -> Result<Self, Bech32Error>
pub fn try_from_bech32m( s: &str, expected_hrp: &str, ) -> Result<Self, Bech32Error>
Decodes a Bech32m (BIP-350) string into Dynamic<Vec<u8>>, validating the HRP
(case-insensitive).
The decoded buffer is kept inside a Zeroizing wrapper until after the
Box allocation completes, guaranteeing zeroization even on OOM panic.
Sourcepub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>
pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, Bech32Error>
Decodes a Bech32m (BIP-350) string into Dynamic<Vec<u8>> without validating the HRP.
Use try_from_bech32m in security-critical code.
Source§impl Dynamic<String>
impl Dynamic<String>
Sourcepub fn new_with<F>(f: F) -> Self
pub fn new_with<F>(f: F) -> Self
Closure-based constructor for consistent API with Fixed::new_with.
The actual secret data is allocated on the heap; this method exists
for consistent security-first construction idiom across the crate.
Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn from_random(len: usize) -> Self
pub fn from_random(len: usize) -> Self
Fills a new Vec<u8> with len cryptographically secure random bytes and wraps it.
Uses the system RNG (SysRng). Requires the rand feature (and
alloc, which Dynamic<Vec<u8>> always needs).
§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::{Dynamic, RevealSecret};
let nonce: Dynamic<Vec<u8>> = Dynamic::from_random(24);
assert_eq!(nonce.len(), 24);Sourcepub fn from_rng<R: TryRng + TryCryptoRng>(
len: usize,
rng: &mut R,
) -> Result<Self, R::Error>
pub fn from_rng<R: TryRng + TryCryptoRng>( len: usize, rng: &mut R, ) -> Result<Self, R::Error>
Allocates a Vec<u8> of length len, fills it from rng, and wraps it.
Accepts any TryCryptoRng + TryRng — for example,
a seeded StdRng for deterministic tests. Requires the rand
feature and alloc (implicit — Dynamic<T> itself requires it).
§Errors
Returns R::Error if try_fill_bytes fails.
§Examples
use rand::rngs::StdRng;
use rand::SeedableRng;
use secure_gate::Dynamic;
let mut rng = StdRng::from_seed([9u8; 32]);
let nonce: Dynamic<Vec<u8>> = Dynamic::from_rng(24, &mut rng).expect("rng fill");Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn as_reader(&self) -> DynamicReader<'_> ⓘ
pub fn as_reader(&self) -> DynamicReader<'_> ⓘ
Returns a DynamicReader that implements std::io::Read.
This replaces the common with_secret + Cursor boilerplate:
use std::io;
use secure_gate::Dynamic;
let secret = Dynamic::<Vec<u8>>::new(vec![1, 2, 3, 4]);
// Before: awkward closure + Cursor dance
// secret.with_secret(|b| io::copy(&mut Cursor::new(b), &mut encryptor))?;
// After: pipe directly into an encrypted writer — no intermediate buffer
let mut encryptor = io::sink(); // stand-in for a real encryptor
io::copy(&mut secret.as_reader(), &mut encryptor).unwrap();§Security
Each read() call copies secret bytes into the caller’s buffer.
Prefer piping directly into encrypted writers rather than reading
into intermediate buffers. The caller is responsible for zeroizing
any destination buffer.
Source§impl Dynamic<Vec<u8>>
impl Dynamic<Vec<u8>>
Sourcepub fn deserialize_with_limit<'de, D>(
deserializer: D,
limit: usize,
) -> Result<Self, D::Error>where
D: Deserializer<'de>,
pub fn deserialize_with_limit<'de, D>(
deserializer: D,
limit: usize,
) -> Result<Self, D::Error>where
D: Deserializer<'de>,
Deserializes into Dynamic<Vec<u8>>, rejecting payloads larger than limit bytes.
The standard serde::Deserialize impl calls this with MAX_DESERIALIZE_BYTES.
Use this method directly when you need a tighter or looser ceiling.
The intermediate buffer is kept inside a Zeroizing wrapper until after the Box
allocation completes, guaranteeing zeroization even on OOM panic. Oversized buffers
are also zeroized before the error is returned.
Important: this limit is enforced after the upstream deserializer has fully materialized the payload. It is a result-length acceptance bound, not a pre-allocation DoS guard. For untrusted input, enforce size limits at the transport or parser layer upstream.
Source§impl Dynamic<String>
impl Dynamic<String>
Sourcepub fn deserialize_with_limit<'de, D>(
deserializer: D,
limit: usize,
) -> Result<Self, D::Error>where
D: Deserializer<'de>,
pub fn deserialize_with_limit<'de, D>(
deserializer: D,
limit: usize,
) -> Result<Self, D::Error>where
D: Deserializer<'de>,
Deserializes into Dynamic<String>, rejecting payloads larger than limit bytes.
The standard serde::Deserialize impl calls this with MAX_DESERIALIZE_BYTES.
Use this method directly when you need a tighter or looser ceiling.
The intermediate buffer is kept inside a Zeroizing wrapper until after the Box
allocation completes, guaranteeing zeroization even on OOM panic. Oversized buffers
are also zeroized before the error is returned.
Important: this limit is enforced after the upstream deserializer has fully materialized the payload. It is a result-length acceptance bound, not a pre-allocation DoS guard. For untrusted input, enforce size limits at the transport or parser layer upstream.
Trait Implementations§
Source§impl<T: Zeroize + CloneableSecret> Clone for Dynamic<T>
Available on crate feature cloneable only.Opt-in cloning — requires cloneable feature and CloneableSecret
marker. Each clone is independently zeroized on drop, but cloning increases exposure surface.
impl<T: Zeroize + CloneableSecret> Clone for Dynamic<T>
cloneable only.Opt-in cloning — requires cloneable feature and CloneableSecret
marker. Each clone is independently zeroized on drop, but cloning increases exposure surface.
Source§impl<T> ConstantTimeEq for Dynamic<T>
Available on crate feature ct-eq only.Constant-time equality for Dynamic<T> — routes through expose_secret().
impl<T> ConstantTimeEq for Dynamic<T>
ct-eq only.Constant-time equality for Dynamic<T> — routes through expose_secret().
== is deliberately not implemented. Always use ct_eq.
Source§impl<T: ?Sized + Zeroize> Debug for Dynamic<T>
Always prints [REDACTED] — secrets never appear in debug output.
impl<T: ?Sized + Zeroize> Debug for Dynamic<T>
Always prints [REDACTED] — secrets never appear in debug output.
Source§impl<'de> Deserialize<'de> for Dynamic<String>
Available on crate feature serde-deserialize only.
impl<'de> Deserialize<'de> for Dynamic<String>
serde-deserialize only.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<'de> Deserialize<'de> for Dynamic<Vec<u8>>
Available on crate feature serde-deserialize only.
impl<'de> Deserialize<'de> for Dynamic<Vec<u8>>
serde-deserialize only.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: ?Sized + Zeroize> Drop for Dynamic<T>
Unconditionally zeroizes the inner value when the wrapper is dropped.
impl<T: ?Sized + Zeroize> Drop for Dynamic<T>
Unconditionally zeroizes the inner value when the wrapper is dropped.
Warning: Drop does not run under panic = "abort".
Source§impl<T: ?Sized + Zeroize> From<Box<T>> for Dynamic<T>
Zero-copy wrapping of an already-boxed value.
impl<T: ?Sized + Zeroize> From<Box<T>> for Dynamic<T>
Zero-copy wrapping of an already-boxed value.
Source§impl RevealSecret for Dynamic<String>
impl RevealSecret for Dynamic<String>
Source§fn into_inner(self) -> InnerSecret<String>
fn into_inner(self) -> InnerSecret<String>
Consumes self and returns the inner String wrapped in crate::InnerSecret.
Allocation note: allocates one small Box<String> sentinel (24 bytes on
64-bit) before the swap. If that allocation panics (OOM), self.inner is
unchanged and Dynamic::drop zeroizes the real secret during unwind —
confidentiality is preserved. This is the same OOM-safety pattern used by
from_protected_bytes and deserialize_with_limit.
See RevealSecret::into_inner for full documentation including the
redacted Debug behavior.
Source§fn with_secret<F, R>(&self, f: F) -> R
fn with_secret<F, R>(&self, f: F) -> R
Source§fn expose_secret(&self) -> &String
fn expose_secret(&self) -> &String
Source§impl<T: Zeroize> RevealSecret for Dynamic<Vec<T>>
impl<T: Zeroize> RevealSecret for Dynamic<Vec<T>>
Source§fn into_inner(self) -> InnerSecret<Vec<T>>
fn into_inner(self) -> InnerSecret<Vec<T>>
Consumes self and returns the inner Vec<T> wrapped in crate::InnerSecret.
Allocation note: allocates one small Box<Vec<T>> sentinel (24 bytes on
64-bit) before the swap. If that allocation panics (OOM), self.inner is
unchanged and Dynamic::drop zeroizes the real secret during unwind —
confidentiality is preserved. This is the same OOM-safety pattern used by
from_protected_bytes and deserialize_with_limit.
See RevealSecret::into_inner for full documentation including the
redacted Debug behavior.
Source§fn with_secret<F, R>(&self, f: F) -> R
fn with_secret<F, R>(&self, f: F) -> R
Source§fn expose_secret(&self) -> &Vec<T>
fn expose_secret(&self) -> &Vec<T>
Source§impl RevealSecretMut for Dynamic<String>
impl RevealSecretMut for Dynamic<String>
Source§impl<T: Zeroize + SerializableSecret> Serialize for Dynamic<T>
Available on crate feature serde-serialize only.Opt-in serialization — requires serde-serialize feature and
SerializableSecret marker. Serialization exposes the
full secret — audit every impl.
impl<T: Zeroize + SerializableSecret> Serialize for Dynamic<T>
serde-serialize only.Opt-in serialization — requires serde-serialize feature and
SerializableSecret marker. Serialization exposes the
full secret — audit every impl.
Source§impl Write for Dynamic<Vec<u8>>
Available on crate feature std only.Streams bytes directly into the protected buffer via RevealSecretMut.
impl Write for Dynamic<Vec<u8>>
std only.Streams bytes directly into the protected buffer via RevealSecretMut.
Data flows into the wrapper — this is a pure security improvement over
accumulating plaintext in a bare Vec<u8> before wrapping.
§Example
use std::io::Write;
use secure_gate::Dynamic;
let mut secret = Dynamic::<Vec<u8>>::new(vec![]);
secret.write_all(b"decrypted payload").unwrap();
// Secret material was protected from the first byte —
// no intermediate unprotected buffer ever existed.Source§fn write(&mut self, buf: &[u8]) -> Result<usize>
fn write(&mut self, buf: &[u8]) -> Result<usize>
Source§fn flush(&mut self) -> Result<()>
fn flush(&mut self) -> Result<()>
Source§fn is_write_vectored(&self) -> bool
fn is_write_vectored(&self) -> bool
can_vector)1.0.0 · Source§fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
Source§fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
write_all_vectored)Source§impl<T: ?Sized + Zeroize> Zeroize for Dynamic<T>
Zeroizes the inner value (including Vec/String spare capacity).
impl<T: ?Sized + Zeroize> Zeroize for Dynamic<T>
Zeroizes the inner value (including Vec/String spare capacity).
Warning: does not run under panic = "abort".
impl<T: ?Sized + Zeroize> ZeroizeOnDrop for Dynamic<T>
Marker confirming that Dynamic<T> always zeroizes on drop.