secure_gate/dynamic.rs
1//! Heap-allocated wrapper for variable-length secrets.
2//!
3//! Provides [`Dynamic<T>`], a zero-cost wrapper enforcing explicit access to sensitive data.
4//! Treat secrets as radioactive — minimize exposure surface.
5//!
6//! **Inner type must implement `Zeroize`** for automatic zeroization on drop (including spare capacity).
7//! Requires the `alloc` feature.
8//!
9//! # Examples
10//!
11//! ```rust
12//! # #[cfg(feature = "alloc")]
13//! use secure_gate::{Dynamic, RevealSecret};
14//!
15//! # #[cfg(feature = "alloc")]
16//! {
17//! let secret: Dynamic<Vec<u8>> = Dynamic::new(vec![1u8, 2, 3, 4]);
18//! let sum = secret.with_secret(|s| s.iter().sum::<u8>());
19//! assert_eq!(sum, 10);
20//! # }
21//! ```
22
23#[cfg(feature = "alloc")]
24extern crate alloc;
25use alloc::boxed::Box;
26use zeroize::Zeroize;
27
28#[cfg(any(feature = "encoding-hex", feature = "encoding-base64"))]
29use crate::RevealSecret;
30
31// Encoding traits
32#[cfg(feature = "encoding-base64")]
33use crate::traits::encoding::base64_url::ToBase64Url;
34#[cfg(feature = "encoding-hex")]
35use crate::traits::encoding::hex::ToHex;
36
37#[cfg(feature = "rand")]
38use rand::{TryCryptoRng, TryRng, rngs::SysRng};
39
40#[cfg(feature = "encoding-base64")]
41use crate::traits::decoding::base64_url::FromBase64UrlStr;
42#[cfg(feature = "encoding-bech32")]
43use crate::traits::decoding::bech32::FromBech32Str;
44#[cfg(feature = "encoding-bech32m")]
45use crate::traits::decoding::bech32m::FromBech32mStr;
46#[cfg(feature = "encoding-hex")]
47use crate::traits::decoding::hex::FromHexStr;
48
49/// Zero-cost heap-allocated wrapper for variable-length secrets.
50///
51/// Requires `alloc`. **Inner type must implement `Zeroize`** for automatic zeroization on drop
52/// (including spare capacity in `Vec`/`String`).
53///
54/// No `Deref`, `AsRef`, or `Copy` by default — all access requires
55/// [`expose_secret()`](crate::RevealSecret::expose_secret) or
56/// [`with_secret()`](crate::RevealSecret::with_secret) (scoped, preferred).
57/// For the common concrete types, [`Dynamic::<Vec<u8>>::new_with`](Dynamic::new_with) and
58/// [`Dynamic::<String>::new_with`](Dynamic::new_with) are the matching scoped constructors —
59/// closures that write directly into the wrapper. [`new(value)`](Dynamic::new) remains
60/// available as the ergonomic default. `Debug` always prints `[REDACTED]`.
61pub struct Dynamic<T: ?Sized + zeroize::Zeroize> {
62 inner: Box<T>,
63}
64
65impl<T: ?Sized + zeroize::Zeroize> Dynamic<T> {
66 /// Wraps `value` in a `Box<T>` and returns a `Dynamic<T>`.
67 ///
68 /// Accepts any type that implements `Into<Box<T>>` — including owned values,
69 /// `Box<T>`, `String`, `Vec<u8>`, `&str` (via the blanket `From<&str>` impl), etc.
70 ///
71 /// Equivalent to `Dynamic::from(value)` — `#[doc(alias = "from")]` is set so both
72 /// names appear in docs.rs search.
73 ///
74 /// Requires the `alloc` feature (which `Dynamic<T>` itself always requires).
75 #[doc(alias = "from")]
76 #[inline(always)]
77 pub fn new<U>(value: U) -> Self
78 where
79 U: Into<Box<T>>,
80 {
81 let inner = value.into();
82 Self { inner }
83 }
84}
85
86// From impls
87impl<T: ?Sized + zeroize::Zeroize> From<Box<T>> for Dynamic<T> {
88 #[inline(always)]
89 fn from(boxed: Box<T>) -> Self {
90 Self { inner: boxed }
91 }
92}
93
94impl From<&[u8]> for Dynamic<Vec<u8>> {
95 #[inline(always)]
96 fn from(slice: &[u8]) -> Self {
97 Self::new(slice.to_vec())
98 }
99}
100
101impl From<&str> for Dynamic<String> {
102 #[inline(always)]
103 fn from(input: &str) -> Self {
104 Self::new(input.to_string())
105 }
106}
107
108impl<T: 'static + zeroize::Zeroize> From<T> for Dynamic<T> {
109 #[inline(always)]
110 fn from(value: T) -> Self {
111 Self {
112 inner: Box::new(value),
113 }
114 }
115}
116
117// Encoding helpers for Dynamic<Vec<u8>>
118impl Dynamic<Vec<u8>> {
119 /// Encodes the secret bytes as a lowercase hex string.
120 ///
121 /// Delegates to [`ToHex::to_hex`](crate::ToHex::to_hex) on the inner `Vec<u8>`.
122 /// Requires the `encoding-hex` feature.
123 #[cfg(feature = "encoding-hex")]
124 #[inline]
125 pub fn to_hex(&self) -> alloc::string::String {
126 self.with_secret(|s: &Vec<u8>| s.to_hex())
127 }
128
129 /// Encodes the secret bytes as an uppercase hex string.
130 ///
131 /// Delegates to [`ToHex::to_hex_upper`](crate::ToHex::to_hex_upper) on the inner `Vec<u8>`.
132 /// Requires the `encoding-hex` feature.
133 #[cfg(feature = "encoding-hex")]
134 #[inline]
135 pub fn to_hex_upper(&self) -> alloc::string::String {
136 self.with_secret(|s: &Vec<u8>| s.to_hex_upper())
137 }
138
139 /// Encodes the secret bytes as an unpadded Base64url string.
140 ///
141 /// Delegates to [`ToBase64Url::to_base64url`](crate::ToBase64Url::to_base64url) on the inner `Vec<u8>`.
142 /// Requires the `encoding-base64` feature.
143 #[cfg(feature = "encoding-base64")]
144 #[inline]
145 pub fn to_base64url(&self) -> alloc::string::String {
146 self.with_secret(|s: &Vec<u8>| s.to_base64url())
147 }
148
149 /// Transfers `protected` bytes into a freshly boxed `Vec`, keeping
150 /// [`zeroize::Zeroizing`] alive across the only allocation that can panic.
151 ///
152 /// # Panic safety
153 ///
154 /// `Box::new(Vec::new())` is the sole allocation point — just the 24-byte
155 /// `Vec` header, no data buffer. If it panics (OOM), `protected` is still
156 /// in scope and `Zeroizing::drop` zeroes the secret bytes during unwind.
157 /// After the swap, `protected` holds an empty `Vec` (no-op to zeroize) and
158 /// `Dynamic::from(boxed)` is an infallible struct-field assignment.
159 ///
160 /// Note: `Box::new(*protected)` would be cleaner but does not compile —
161 /// `Zeroizing` implements `Deref` (returning `&T`), not a move-out, so
162 /// `*protected` yields a reference rather than an owned value (E0507).
163 #[cfg(any(
164 feature = "encoding-hex",
165 feature = "encoding-base64",
166 feature = "encoding-bech32",
167 feature = "encoding-bech32m",
168 ))]
169 #[inline(always)]
170 fn from_protected_bytes(mut protected: zeroize::Zeroizing<alloc::vec::Vec<u8>>) -> Self {
171 // Only fallible allocation; protected stays live across it for panic-safety
172 let mut boxed = Box::<alloc::vec::Vec<u8>>::default();
173 core::mem::swap(&mut *boxed, &mut *protected);
174 Self::from(boxed)
175 }
176
177 /// Closure-based constructor for consistent API with [`Fixed::new_with`](crate::Fixed::new_with).
178 /// The actual secret data is allocated on the heap; this method exists
179 /// for consistent security-first construction idiom across the crate.
180 #[inline(always)]
181 pub fn new_with<F>(f: F) -> Self
182 where
183 F: FnOnce(&mut alloc::vec::Vec<u8>),
184 {
185 let mut v = alloc::vec::Vec::new();
186 f(&mut v);
187 Self::new(v)
188 }
189}
190
191impl Dynamic<alloc::string::String> {
192 /// Closure-based constructor for consistent API with [`Fixed::new_with`](crate::Fixed::new_with).
193 /// The actual secret data is allocated on the heap; this method exists
194 /// for consistent security-first construction idiom across the crate.
195 #[inline(always)]
196 pub fn new_with<F>(f: F) -> Self
197 where
198 F: FnOnce(&mut alloc::string::String),
199 {
200 let mut s = alloc::string::String::new();
201 f(&mut s);
202 Self::new(s)
203 }
204}
205
206// RevealSecret
207impl crate::RevealSecret for Dynamic<String> {
208 type Inner = String;
209
210 #[inline(always)]
211 fn with_secret<F, R>(&self, f: F) -> R
212 where
213 F: FnOnce(&String) -> R,
214 {
215 f(&self.inner)
216 }
217
218 #[inline(always)]
219 fn expose_secret(&self) -> &String {
220 &self.inner
221 }
222
223 #[inline(always)]
224 fn len(&self) -> usize {
225 self.inner.len()
226 }
227}
228
229impl<T: zeroize::Zeroize> crate::RevealSecret for Dynamic<Vec<T>> {
230 type Inner = Vec<T>;
231
232 #[inline(always)]
233 fn with_secret<F, R>(&self, f: F) -> R
234 where
235 F: FnOnce(&Vec<T>) -> R,
236 {
237 f(&self.inner)
238 }
239
240 #[inline(always)]
241 fn expose_secret(&self) -> &Vec<T> {
242 &self.inner
243 }
244
245 #[inline(always)]
246 fn len(&self) -> usize {
247 self.inner.len() * core::mem::size_of::<T>()
248 }
249}
250
251// RevealSecretMut
252impl crate::RevealSecretMut for Dynamic<String> {
253 #[inline(always)]
254 fn with_secret_mut<F, R>(&mut self, f: F) -> R
255 where
256 F: FnOnce(&mut String) -> R,
257 {
258 f(&mut self.inner)
259 }
260
261 #[inline(always)]
262 fn expose_secret_mut(&mut self) -> &mut String {
263 &mut self.inner
264 }
265}
266
267impl<T: zeroize::Zeroize> crate::RevealSecretMut for Dynamic<Vec<T>> {
268 #[inline(always)]
269 fn with_secret_mut<F, R>(&mut self, f: F) -> R
270 where
271 F: FnOnce(&mut Vec<T>) -> R,
272 {
273 f(&mut self.inner)
274 }
275
276 #[inline(always)]
277 fn expose_secret_mut(&mut self) -> &mut Vec<T> {
278 &mut self.inner
279 }
280}
281
282// Random generation
283#[cfg(feature = "rand")]
284impl Dynamic<alloc::vec::Vec<u8>> {
285 /// Fills a new `Vec<u8>` with `len` cryptographically secure random bytes and wraps it.
286 ///
287 /// Uses the system RNG ([`SysRng`](rand::rngs::SysRng)). Requires the `rand` feature (and
288 /// `alloc`, which `Dynamic<Vec<u8>>` always needs).
289 ///
290 /// # Panics
291 ///
292 /// Panics if the system RNG fails to provide bytes ([`TryRng::try_fill_bytes`](rand::TryRng::try_fill_bytes)
293 /// returns `Err`). This is treated as a fatal environment error.
294 ///
295 /// # Examples
296 ///
297 /// ```rust
298 /// # #[cfg(all(feature = "alloc", feature = "rand"))]
299 /// use secure_gate::{Dynamic, RevealSecret};
300 ///
301 /// # #[cfg(all(feature = "alloc", feature = "rand"))]
302 /// # {
303 /// let nonce: Dynamic<Vec<u8>> = Dynamic::from_random(24);
304 /// assert_eq!(nonce.len(), 24);
305 /// # }
306 /// ```
307 #[inline]
308 pub fn from_random(len: usize) -> Self {
309 Self::new_with(|v| {
310 v.resize(len, 0u8);
311 SysRng
312 .try_fill_bytes(v)
313 .expect("SysRng failure is a program error");
314 })
315 }
316
317 /// Allocates a `Vec<u8>` of length `len`, fills it from `rng`, and wraps it.
318 ///
319 /// Accepts any [`TryCryptoRng`](rand::TryCryptoRng) + [`TryRng`](rand::TryRng) — for example,
320 /// a seeded [`StdRng`](rand::rngs::StdRng) for deterministic tests. Requires the `rand`
321 /// feature and `alloc` (implicit — [`Dynamic<T>`](crate::Dynamic) itself requires it).
322 ///
323 /// # Errors
324 ///
325 /// Returns `R::Error` if [`try_fill_bytes`](rand::TryRng::try_fill_bytes) fails.
326 ///
327 /// # Examples
328 ///
329 /// ```rust
330 /// # #[cfg(all(feature = "alloc", feature = "rand"))]
331 /// # {
332 /// use rand::rngs::StdRng;
333 /// use rand::SeedableRng;
334 /// use secure_gate::Dynamic;
335 ///
336 /// let mut rng = StdRng::from_seed([9u8; 32]);
337 /// let nonce: Dynamic<Vec<u8>> = Dynamic::from_rng(24, &mut rng).expect("rng fill");
338 /// # }
339 /// ```
340 #[inline]
341 pub fn from_rng<R: TryRng + TryCryptoRng>(len: usize, rng: &mut R) -> Result<Self, R::Error> {
342 let mut result = Ok(());
343 let this = Self::new_with(|v| {
344 v.resize(len, 0u8);
345 result = rng.try_fill_bytes(v);
346 });
347 result.map(|_| this)
348 }
349}
350
351// Decoding constructors
352#[cfg(feature = "encoding-hex")]
353impl Dynamic<alloc::vec::Vec<u8>> {
354 /// Decodes a lowercase hex string into `Dynamic<Vec<u8>>`.
355 ///
356 /// The decoded buffer is kept inside a `Zeroizing` wrapper until after the
357 /// `Box` allocation completes, guaranteeing zeroization even on OOM panic.
358 pub fn try_from_hex(s: &str) -> Result<Self, crate::error::HexError> {
359 Ok(Self::from_protected_bytes(zeroize::Zeroizing::new(
360 s.try_from_hex()?,
361 )))
362 }
363}
364
365#[cfg(feature = "encoding-base64")]
366impl Dynamic<alloc::vec::Vec<u8>> {
367 /// Decodes a Base64url (unpadded) string into `Dynamic<Vec<u8>>`.
368 ///
369 /// The decoded buffer is kept inside a `Zeroizing` wrapper until after the
370 /// `Box` allocation completes, guaranteeing zeroization even on OOM panic.
371 pub fn try_from_base64url(s: &str) -> Result<Self, crate::error::Base64Error> {
372 Ok(Self::from_protected_bytes(zeroize::Zeroizing::new(
373 s.try_from_base64url()?,
374 )))
375 }
376}
377
378#[cfg(feature = "encoding-bech32")]
379impl Dynamic<alloc::vec::Vec<u8>> {
380 /// Decodes a Bech32 (BIP-173) string into `Dynamic<Vec<u8>>`.
381 ///
382 /// The decoded buffer is kept inside a `Zeroizing` wrapper until after the
383 /// `Box` allocation completes, guaranteeing zeroization even on OOM panic.
384 ///
385 /// # Warning
386 ///
387 /// The HRP is **not validated** — any HRP will be accepted as long as the checksum
388 /// is valid. For security-critical code where cross-protocol confusion must be
389 /// prevented, use [`try_from_bech32`](Self::try_from_bech32).
390 pub fn try_from_bech32_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
391 let (_hrp, bytes) = s.try_from_bech32_unchecked()?;
392 Ok(Self::from_protected_bytes(zeroize::Zeroizing::new(bytes)))
393 }
394
395 /// Decodes a Bech32 (BIP-173) string into `Dynamic<Vec<u8>>`, validating that the HRP
396 /// matches `expected_hrp` (case-insensitive).
397 ///
398 /// The decoded buffer is kept inside a `Zeroizing` wrapper until after the
399 /// `Box` allocation completes, guaranteeing zeroization even on OOM panic.
400 ///
401 /// Prefer this over [`try_from_bech32_unchecked`](Self::try_from_bech32_unchecked) in
402 /// security-critical code to prevent cross-protocol confusion attacks.
403 pub fn try_from_bech32(s: &str, expected_hrp: &str) -> Result<Self, crate::error::Bech32Error> {
404 Ok(Self::from_protected_bytes(zeroize::Zeroizing::new(
405 s.try_from_bech32(expected_hrp)?,
406 )))
407 }
408}
409
410#[cfg(feature = "encoding-bech32m")]
411impl Dynamic<alloc::vec::Vec<u8>> {
412 /// Decodes a Bech32m (BIP-350) string into `Dynamic<Vec<u8>>`.
413 ///
414 /// The decoded buffer is kept inside a `Zeroizing` wrapper until after the
415 /// `Box` allocation completes, guaranteeing zeroization even on OOM panic.
416 ///
417 /// # Warning
418 ///
419 /// The HRP is **not validated** — any HRP will be accepted as long as the checksum
420 /// is valid. For security-critical code where cross-protocol confusion must be
421 /// prevented, use [`try_from_bech32m`](Self::try_from_bech32m).
422 pub fn try_from_bech32m_unchecked(s: &str) -> Result<Self, crate::error::Bech32Error> {
423 let (_hrp, bytes) = s.try_from_bech32m_unchecked()?;
424 Ok(Self::from_protected_bytes(zeroize::Zeroizing::new(bytes)))
425 }
426
427 /// Decodes a Bech32m (BIP-350) string into `Dynamic<Vec<u8>>`, validating that the HRP
428 /// matches `expected_hrp` (case-insensitive).
429 ///
430 /// The decoded buffer is kept inside a `Zeroizing` wrapper until after the
431 /// `Box` allocation completes, guaranteeing zeroization even on OOM panic.
432 ///
433 /// Prefer this over [`try_from_bech32m_unchecked`](Self::try_from_bech32m_unchecked) in
434 /// security-critical code to prevent cross-protocol confusion attacks.
435 pub fn try_from_bech32m(
436 s: &str,
437 expected_hrp: &str,
438 ) -> Result<Self, crate::error::Bech32Error> {
439 Ok(Self::from_protected_bytes(zeroize::Zeroizing::new(
440 s.try_from_bech32m(expected_hrp)?,
441 )))
442 }
443}
444
445// ConstantTimeEq
446#[cfg(feature = "ct-eq")]
447impl<T: ?Sized + zeroize::Zeroize> crate::ConstantTimeEq for Dynamic<T>
448where
449 T: crate::ConstantTimeEq,
450{
451 fn ct_eq(&self, other: &Self) -> bool {
452 self.inner.ct_eq(&other.inner)
453 }
454}
455
456// Debug
457impl<T: ?Sized + zeroize::Zeroize> core::fmt::Debug for Dynamic<T> {
458 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
459 f.write_str("[REDACTED]")
460 }
461}
462
463// Clone
464#[cfg(feature = "cloneable")]
465impl<T: zeroize::Zeroize + crate::CloneableSecret> Clone for Dynamic<T> {
466 fn clone(&self) -> Self {
467 Self::new(self.inner.clone())
468 }
469}
470
471// Serialize
472#[cfg(feature = "serde-serialize")]
473impl<T: zeroize::Zeroize + crate::SerializableSecret> serde::Serialize for Dynamic<T> {
474 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
475 where
476 S: serde::Serializer,
477 {
478 self.inner.serialize(serializer)
479 }
480}
481
482// Deserialize
483
484/// Default maximum byte length accepted when deserializing `Dynamic<Vec<u8>>` or
485/// `Dynamic<String>` via the standard `serde::Deserialize` impl (1 MiB).
486///
487/// Pass a custom value to [`Dynamic::deserialize_with_limit`] when a different
488/// ceiling is required.
489///
490/// **Important:** this limit is enforced *after* the upstream deserializer has fully
491/// materialized the payload. It is a **result-length acceptance bound**, not a
492/// pre-allocation DoS guard. For untrusted input, enforce size limits at the
493/// transport or parser layer upstream.
494#[cfg(feature = "serde-deserialize")]
495pub const MAX_DESERIALIZE_BYTES: usize = 1_048_576;
496
497#[cfg(feature = "serde-deserialize")]
498impl Dynamic<alloc::vec::Vec<u8>> {
499 /// Deserializes into `Dynamic<Vec<u8>>`, rejecting payloads larger than `limit` bytes.
500 ///
501 /// The standard [`serde::Deserialize`] impl calls this with [`MAX_DESERIALIZE_BYTES`].
502 /// Use this method directly when you need a tighter or looser ceiling.
503 ///
504 /// The intermediate buffer is kept inside a `Zeroizing` wrapper until after the `Box`
505 /// allocation completes, guaranteeing zeroization even on OOM panic. Oversized buffers
506 /// are also zeroized before the error is returned.
507 ///
508 /// **Important:** this limit is enforced *after* the upstream deserializer has fully
509 /// materialized the payload. It is a **result-length acceptance bound**, not a
510 /// pre-allocation DoS guard. For untrusted input, enforce size limits at the
511 /// transport or parser layer upstream.
512 pub fn deserialize_with_limit<'de, D>(deserializer: D, limit: usize) -> Result<Self, D::Error>
513 where
514 D: serde::Deserializer<'de>,
515 {
516 let mut buf: zeroize::Zeroizing<alloc::vec::Vec<u8>> =
517 zeroize::Zeroizing::new(serde::Deserialize::deserialize(deserializer)?);
518 if buf.len() > limit {
519 // buf drops here → Zeroizing zeros the oversized buffer before deallocation
520 return Err(serde::de::Error::custom(
521 "deserialized secret exceeds maximum size",
522 ));
523 }
524 // Only fallible allocation; protected stays live across it for panic-safety
525 let mut boxed = Box::<alloc::vec::Vec<u8>>::default();
526 core::mem::swap(&mut *boxed, &mut *buf);
527 Ok(Self::from(boxed))
528 }
529}
530
531#[cfg(feature = "serde-deserialize")]
532impl Dynamic<String> {
533 /// Deserializes into `Dynamic<String>`, rejecting payloads larger than `limit` bytes.
534 ///
535 /// The standard [`serde::Deserialize`] impl calls this with [`MAX_DESERIALIZE_BYTES`].
536 /// Use this method directly when you need a tighter or looser ceiling.
537 ///
538 /// The intermediate buffer is kept inside a `Zeroizing` wrapper until after the `Box`
539 /// allocation completes, guaranteeing zeroization even on OOM panic. Oversized buffers
540 /// are also zeroized before the error is returned.
541 ///
542 /// **Important:** this limit is enforced *after* the upstream deserializer has fully
543 /// materialized the payload. It is a **result-length acceptance bound**, not a
544 /// pre-allocation DoS guard. For untrusted input, enforce size limits at the
545 /// transport or parser layer upstream.
546 pub fn deserialize_with_limit<'de, D>(deserializer: D, limit: usize) -> Result<Self, D::Error>
547 where
548 D: serde::Deserializer<'de>,
549 {
550 let mut buf: zeroize::Zeroizing<alloc::string::String> =
551 zeroize::Zeroizing::new(serde::Deserialize::deserialize(deserializer)?);
552 if buf.len() > limit {
553 // buf drops here → Zeroizing zeros the oversized buffer before deallocation
554 return Err(serde::de::Error::custom(
555 "deserialized secret exceeds maximum size",
556 ));
557 }
558 // Only fallible allocation; protected stays live across it for panic-safety
559 let mut boxed = Box::<alloc::string::String>::default();
560 core::mem::swap(&mut *boxed, &mut *buf);
561 Ok(Self::from(boxed))
562 }
563}
564
565#[cfg(feature = "serde-deserialize")]
566impl<'de> serde::Deserialize<'de> for Dynamic<alloc::vec::Vec<u8>> {
567 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
568 where
569 D: serde::Deserializer<'de>,
570 {
571 Self::deserialize_with_limit(deserializer, MAX_DESERIALIZE_BYTES)
572 }
573}
574
575#[cfg(feature = "serde-deserialize")]
576impl<'de> serde::Deserialize<'de> for Dynamic<String> {
577 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
578 where
579 D: serde::Deserializer<'de>,
580 {
581 Self::deserialize_with_limit(deserializer, MAX_DESERIALIZE_BYTES)
582 }
583}
584
585// Zeroize + Drop (now always present with bound)
586impl<T: ?Sized + zeroize::Zeroize> zeroize::Zeroize for Dynamic<T> {
587 fn zeroize(&mut self) {
588 self.inner.zeroize();
589 }
590}
591
592impl<T: ?Sized + zeroize::Zeroize> Drop for Dynamic<T> {
593 fn drop(&mut self) {
594 self.zeroize();
595 }
596}
597
598impl<T: ?Sized + zeroize::Zeroize> zeroize::ZeroizeOnDrop for Dynamic<T> {}