Skip to main content

secure_gate/traits/revealed_secrets/
encoded_secret.rs

1//! Owned zeroizing wrapper for encoded secret strings.
2//!
3//! > **Import path:** `use secure_gate::EncodedSecret;`
4//!
5//! [`EncodedSecret`] wraps `Zeroizing<String>` with `Debug` → `[REDACTED]`. It is
6//! returned by all `*_zeroizing` encoding methods (`to_hex_zeroizing`,
7//! `to_base64url_zeroizing`, `try_to_bech32_zeroizing`, etc.).
8//!
9//! Prefer zeroizing variants when the encoded form is sensitive (private keys, tokens).
10//! Use plain `String` variants for public encodings (addresses, transaction IDs).
11//!
12//! This is **not** a secret wrapper like [`Fixed`](crate::Fixed) / [`Dynamic`](crate::Dynamic)
13//! — it is a zeroizing `String` wrapper for encoded output. It implements
14//! `Deref<Target = str>` and `Display`.
15
16#[cfg(feature = "alloc")]
17/// Owned wrapper for encoded secret strings. Guarantees zeroization on drop
18/// while redacting `Debug` output. Use this when the encoded form remains sensitive
19/// (e.g. full PEM keys, long-lived Bech32 private keys, tokens).
20///
21/// See the zeroizing encoding methods on [`Fixed`] and [`Dynamic`] (e.g.
22/// [`to_hex_zeroizing`](crate::Fixed::to_hex_zeroizing)).
23#[must_use = "dropping EncodedSecret may immediately zeroize encoded output"]
24pub struct EncodedSecret(zeroize::Zeroizing<alloc::string::String>);
25
26#[cfg(feature = "alloc")]
27impl EncodedSecret {
28    #[cfg(any(
29        feature = "encoding-hex",
30        feature = "encoding-base64",
31        feature = "encoding-bech32",
32        feature = "encoding-bech32m",
33    ))]
34    #[inline(always)]
35    pub(crate) fn new(s: alloc::string::String) -> Self {
36        Self(zeroize::Zeroizing::new(s))
37    }
38
39    /// Consumes self and returns the inner `String`.
40    ///
41    /// This ends zeroization protection for the encoded output.
42    #[inline(always)]
43    pub fn into_inner(mut self) -> alloc::string::String {
44        core::mem::take(&mut self.0)
45    }
46
47    /// Consumes self and returns the underlying `Zeroizing<String>`.
48    ///
49    /// This is an explicit escape hatch consistent with `InnerSecret`.
50    #[inline(always)]
51    pub fn into_zeroizing(self) -> zeroize::Zeroizing<alloc::string::String> {
52        self.0
53    }
54}
55
56#[cfg(feature = "alloc")]
57impl core::ops::Deref for EncodedSecret {
58    type Target = str;
59
60    #[inline(always)]
61    fn deref(&self) -> &str {
62        &self.0
63    }
64}
65
66#[cfg(feature = "alloc")]
67impl core::fmt::Debug for EncodedSecret {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        f.write_str("[REDACTED]")
70    }
71}
72
73#[cfg(feature = "alloc")]
74impl core::convert::AsRef<str> for EncodedSecret {
75    fn as_ref(&self) -> &str {
76        &self.0
77    }
78}
79
80#[cfg(feature = "alloc")]
81impl core::convert::AsRef<[u8]> for EncodedSecret {
82    fn as_ref(&self) -> &[u8] {
83        self.0.as_ref()
84    }
85}
86
87#[cfg(feature = "alloc")]
88impl core::fmt::Display for EncodedSecret {
89    /// Outputs the encoded secret content.
90    ///
91    /// Unlike `Debug` (which prints `[REDACTED]`), `Display` is intentionally transparent
92    /// so the value can be written to a sink or used in format strings.
93    /// **Do not log with `{}` in production** — prefer `Debug` for diagnostic output
94    /// to avoid accidental secret exposure in log files.
95    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        core::fmt::Display::fmt(&**self, f)
97    }
98}