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}