Skip to main content

secure_gate/traits/encoding/
hex.rs

1//! Hexadecimal encoding trait.
2//!
3//! > **Import path:** `use secure_gate::ToHex;`
4//!
5//! This trait provides secure, explicit encoding of byte data to lowercase
6//! (or uppercase) hexadecimal strings. It is intended for intentional export
7//! only (QR codes, audited logs, API responses).
8//!
9//! Requires the `encoding-hex` feature.
10//!
11//! # Security Notes
12//!
13//! - **Full secret exposure**: The resulting string contains the **entire** secret.
14//!   Always treat output as sensitive; do not log or persist without protection.
15//! - **Zeroizing variants**: `to_hex_zeroizing()` / `to_hex_upper_zeroizing()` return
16//!   [`EncodedSecret`] (wrapping `Zeroizing<String>` with redacted `Debug`). Prefer these
17//!   when the encoded form itself is sensitive.
18//! - **Audit visibility**: Direct calls (`key.to_hex()` / `key.to_hex_upper()`) do **not** appear in
19//!   `grep expose_secret` / `grep with_secret` audit sweeps. For audit-first teams or
20//!   multi-step operations, prefer `with_secret(|b| b.to_hex())` — the borrow checker
21//!   enforces the reference cannot escape the closure.
22//! - **Treat all input as untrusted**: validate hex strings upstream before wrapping
23//!   in secrets.
24//!
25//! # Example
26//!
27//! ```rust
28//! # #[cfg(feature = "encoding-hex")]
29//! use secure_gate::{Fixed, ToHex, RevealSecret};
30//! # #[cfg(feature = "encoding-hex")]
31//! {
32//! let secret = Fixed::new([0x0au8, 0x0bu8, 0x0cu8, 0x0du8]);
33//!
34//! // Blanket impl on the inner byte array (via with_secret):
35//! let hex = secret.with_secret(|s| s.to_hex());
36//! assert_eq!(hex, "0a0b0c0d");
37//!
38//! let hex_upper = secret.with_secret(|s| s.to_hex_upper());
39//! assert_eq!(hex_upper, "0A0B0C0D");
40//!
41//! // Wrapper method (Direct Fixed<[u8; N]> API — same result):
42//! assert_eq!(secret.to_hex(), "0a0b0c0d");
43//! }
44//! ```
45#[cfg(all(feature = "encoding-hex", feature = "alloc"))]
46use base16ct;
47
48/// Extension trait for encoding byte data as hexadecimal strings.
49///
50/// *Requires feature `encoding-hex`.*
51///
52/// Blanket-implemented for all `AsRef<[u8]>` types (byte slices, arrays, `Vec<u8>`).
53/// To encode a secret wrapper, call the inherent `to_hex()` method directly (ergonomically
54/// safest for single operations — no reference in the caller's hands), or use
55/// `with_secret(|b| b.to_hex())` for multi-step operations or when audit-greppability matters.
56#[cfg(all(feature = "encoding-hex", feature = "alloc"))]
57pub trait ToHex {
58    /// Encode bytes as lowercase hexadecimal.
59    fn to_hex(&self) -> alloc::string::String;
60
61    /// Encode bytes as uppercase hexadecimal.
62    fn to_hex_upper(&self) -> alloc::string::String;
63
64    /// Encode bytes as lowercase hexadecimal and wrap the result in [`crate::EncodedSecret`].
65    fn to_hex_zeroizing(&self) -> crate::EncodedSecret;
66
67    /// Encode bytes as uppercase hexadecimal and wrap the result in [`crate::EncodedSecret`].
68    fn to_hex_upper_zeroizing(&self) -> crate::EncodedSecret;
69}
70
71// Blanket impl to cover any AsRef<[u8]> (e.g., &[u8], Vec<u8>, [u8; N], etc.)
72// encode_string requires alloc — the trait itself is alloc-gated.
73#[cfg(all(feature = "encoding-hex", feature = "alloc"))]
74impl<T: AsRef<[u8]> + ?Sized> ToHex for T {
75    #[inline(always)]
76    fn to_hex(&self) -> alloc::string::String {
77        base16ct::lower::encode_string(self.as_ref())
78    }
79
80    #[inline(always)]
81    fn to_hex_upper(&self) -> alloc::string::String {
82        base16ct::upper::encode_string(self.as_ref())
83    }
84
85    #[inline(always)]
86    fn to_hex_zeroizing(&self) -> crate::EncodedSecret {
87        crate::EncodedSecret::new(self.to_hex())
88    }
89
90    #[inline(always)]
91    fn to_hex_upper_zeroizing(&self) -> crate::EncodedSecret {
92        crate::EncodedSecret::new(self.to_hex_upper())
93    }
94}