secure_gate/traits/
secure_encoding.rs

1#[cfg(feature = "encoding-hex")]
2use ::hex as hex_crate;
3
4#[cfg(feature = "encoding-base64")]
5use ::base64 as base64_crate;
6#[cfg(feature = "encoding-base64")]
7use base64_crate::engine::general_purpose::URL_SAFE_NO_PAD;
8#[cfg(feature = "encoding-base64")]
9use base64_crate::Engine;
10
11#[cfg(feature = "encoding-bech32")]
12use ::bech32::{self};
13
14#[cfg(feature = "encoding-bech32")]
15use crate::Bech32EncodingError;
16
17/// Extension trait for safe, explicit encoding of secret byte data to strings.
18///
19/// All methods require the caller to first call `.expose_secret()` (or similar).
20/// This makes every secret access loud, grep-able, and auditable.
21///
22/// For Bech32 encoding, use the trait methods with an HRP.
23///
24/// # Example
25///
26/// ```
27/// # #[cfg(feature = "encoding-hex")]
28/// # {
29/// use secure_gate::{SecureEncoding, ExposeSecret};
30/// let bytes = [0x42u8; 32];
31/// let hex_string = bytes.to_hex();
32/// let hex = hex_string.expose_secret(); // → "424242..."
33/// # }
34/// ```
35#[cfg(any(
36    feature = "encoding-hex",
37    feature = "encoding-base64",
38    feature = "encoding-bech32"
39))]
40pub trait SecureEncoding {
41    /// Encode secret bytes as lowercase hexadecimal.
42    #[cfg(feature = "encoding-hex")]
43    fn to_hex(&self) -> crate::encoding::hex::HexString;
44
45    /// Encode secret bytes as uppercase hexadecimal.
46    #[cfg(feature = "encoding-hex")]
47    fn to_hex_upper(&self) -> alloc::string::String;
48
49    /// Encode secret bytes as URL-safe base64 (no padding).
50    #[cfg(feature = "encoding-base64")]
51    fn to_base64url(&self) -> crate::encoding::base64::Base64String;
52
53    /// Try to encode secret bytes as Bech32 with the specified HRP.
54    #[cfg(feature = "encoding-bech32")]
55    fn try_to_bech32(
56        &self,
57        hrp: &str,
58    ) -> Result<crate::encoding::bech32::Bech32String, Bech32EncodingError>;
59
60    /// Try to encode secret bytes as Bech32m with the specified HRP.
61    #[cfg(feature = "encoding-bech32")]
62    fn try_to_bech32m(
63        &self,
64        hrp: &str,
65    ) -> Result<crate::encoding::bech32::Bech32String, Bech32EncodingError>;
66}
67
68#[cfg(any(
69    feature = "encoding-hex",
70    feature = "encoding-base64",
71    feature = "encoding-bech32"
72))]
73impl SecureEncoding for [u8] {
74    #[cfg(feature = "encoding-hex")]
75    #[inline(always)]
76    fn to_hex(&self) -> crate::encoding::hex::HexString {
77        let encoded = hex_crate::encode(self);
78        crate::encoding::hex::HexString::new(encoded).expect("fresh encode is always valid")
79    }
80
81    #[cfg(feature = "encoding-hex")]
82    #[inline(always)]
83    fn to_hex_upper(&self) -> alloc::string::String {
84        hex_crate::encode_upper(self)
85    }
86
87    #[cfg(feature = "encoding-base64")]
88    #[inline(always)]
89    fn to_base64url(&self) -> crate::encoding::base64::Base64String {
90        let encoded = URL_SAFE_NO_PAD.encode(self);
91        crate::encoding::base64::Base64String::new(encoded).expect("fresh encode is always valid")
92    }
93
94    #[cfg(feature = "encoding-bech32")]
95    #[inline(always)]
96    fn try_to_bech32(
97        &self,
98        hrp: &str,
99    ) -> Result<crate::encoding::bech32::Bech32String, Bech32EncodingError> {
100        let hrp_parsed = bech32::Hrp::parse(hrp).map_err(|_| Bech32EncodingError::InvalidHrp)?;
101        let encoded = bech32::encode::<bech32::Bech32>(hrp_parsed, self)
102            .map_err(|_| Bech32EncodingError::EncodingFailed)?;
103        Ok(crate::encoding::bech32::Bech32String::new(encoded)
104            .expect("fresh encode is always valid"))
105    }
106
107    #[cfg(feature = "encoding-bech32")]
108    #[inline(always)]
109    fn try_to_bech32m(
110        &self,
111        hrp: &str,
112    ) -> Result<crate::encoding::bech32::Bech32String, Bech32EncodingError> {
113        let hrp_parsed = bech32::Hrp::parse(hrp).map_err(|_| Bech32EncodingError::InvalidHrp)?;
114        let encoded = bech32::encode::<bech32::Bech32m>(hrp_parsed, self)
115            .map_err(|_| Bech32EncodingError::EncodingFailed)?;
116        Ok(crate::encoding::bech32::Bech32String::new(encoded)
117            .expect("fresh encode is always valid"))
118    }
119}
120
121#[cfg(any(
122    feature = "encoding-hex",
123    feature = "encoding-base64",
124    feature = "encoding-bech32"
125))]
126impl<const N: usize> SecureEncoding for [u8; N] {
127    #[cfg(feature = "encoding-hex")]
128    #[inline(always)]
129    fn to_hex(&self) -> crate::encoding::hex::HexString {
130        let encoded = hex_crate::encode(self);
131        crate::encoding::hex::HexString::new(encoded).expect("fresh encode is always valid")
132    }
133
134    #[cfg(feature = "encoding-hex")]
135    #[inline(always)]
136    fn to_hex_upper(&self) -> alloc::string::String {
137        hex_crate::encode_upper(self)
138    }
139
140    #[cfg(feature = "encoding-base64")]
141    #[inline(always)]
142    fn to_base64url(&self) -> crate::encoding::base64::Base64String {
143        let encoded = URL_SAFE_NO_PAD.encode(self);
144        crate::encoding::base64::Base64String::new(encoded).expect("fresh encode is always valid")
145    }
146
147    #[cfg(feature = "encoding-bech32")]
148    #[inline(always)]
149    fn try_to_bech32(
150        &self,
151        hrp: &str,
152    ) -> Result<crate::encoding::bech32::Bech32String, Bech32EncodingError> {
153        let hrp_parsed = bech32::Hrp::parse(hrp).map_err(|_| Bech32EncodingError::InvalidHrp)?;
154        let encoded = bech32::encode::<bech32::Bech32>(hrp_parsed, self)
155            .map_err(|_| Bech32EncodingError::EncodingFailed)?;
156        Ok(crate::encoding::bech32::Bech32String::new(encoded)
157            .expect("fresh encode is always valid"))
158    }
159
160    #[cfg(feature = "encoding-bech32")]
161    #[inline(always)]
162    fn try_to_bech32m(
163        &self,
164        hrp: &str,
165    ) -> Result<crate::encoding::bech32::Bech32String, Bech32EncodingError> {
166        let hrp_parsed = bech32::Hrp::parse(hrp).map_err(|_| Bech32EncodingError::InvalidHrp)?;
167        let encoded = bech32::encode::<bech32::Bech32m>(hrp_parsed, self)
168            .map_err(|_| Bech32EncodingError::EncodingFailed)?;
169        Ok(crate::encoding::bech32::Bech32String::new(encoded)
170            .expect("fresh encode is always valid"))
171    }
172}