Skip to main content

secure_gate/traits/encoding/
mod.rs

1//! Encoding traits for explicit secret-to-string conversion.
2//!
3//! > **Import paths:** `use secure_gate::ToHex;` etc. (not `secure_gate::traits::encoding::hex::ToHex`)
4//!
5//! All encoding traits require `alloc`. Every encoder returns
6//! [`EncodedSecret`](crate::EncodedSecret): the encoded form is a second full copy of
7//! the secret, and it stays wiped until it drops.
8//! See the [`decoding`](super::decoding) module for the reverse direction.
9//!
10//! Each encoding trait has its own feature gate:
11//!
12//! | Trait            | Feature             |
13//! |------------------|---------------------|
14//! | [`ToHex`]        | `encoding-hex`      |
15//! | [`ToBase32`]     | `encoding-base32`   |
16//! | [`ToBase64Url`]  | `encoding-base64`   |
17//! | [`ToBech32`]     | `encoding-bech32`   |
18//! | [`ToBech32m`]    | `encoding-bech32`   |
19pub mod base32;
20pub mod base64_url;
21pub mod bech32;
22#[cfg(feature = "encoding-bech32")]
23pub mod bech32m;
24pub mod hex;
25
26/// Which case an encoder should emit.
27///
28/// This parameter appears only on encoders where both cases are canonical and decoding
29/// accepts either — currently bech32 and bech32m, where BIP-173 forbids mixed case and
30/// accepts either pure case, with the checksum defined over the lowercase form.
31///
32/// It is deliberately absent elsewhere, and the absence is the safety property:
33///
34/// - `to_base64url` takes no case, because base64url's alphabet gives `a`–`z` and
35///   `A`–`Z` distinct meanings. Converting the case of a base64url string destroys it.
36/// - `to_base32` takes no case, because RFC 4648 §6 output is uppercase by definition
37///   and the decoder rejects anything else.
38/// - `to_hex` / `to_hex_upper` stay two methods, because hex decoding is case-insensitive
39///   either way and the pair predates this type.
40///
41/// A caller therefore cannot ask for a case conversion that would corrupt the value —
42/// there is no parameter to pass.
43#[cfg(feature = "encoding-bech32")]
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45#[non_exhaustive]
46pub enum Case {
47    /// Lowercase output. The canonical form for bech32 and bech32m.
48    Lower,
49    /// Uppercase output. Valid per BIP-173, and the form used by key formats such as
50    /// age's `AGE-SECRET-KEY-…`.
51    Upper,
52}
53
54/// Marker for types that may be **encoded**: byte-shaped sources.
55///
56/// *Available whenever any encoding feature is on (`encoding-hex`, `encoding-base32`,
57/// `encoding-base64`, or `encoding-bech32`).*
58///
59/// Every encoding trait in this module is blanket-implemented for
60/// `T: AsRef<[u8]> + EncodableBytes`. The second bound is what keeps string-shaped
61/// types out, and it is load-bearing rather than decorative — removing it changes
62/// which calls compile.
63///
64/// # Why the extra bound exists
65///
66/// `str` and `String` implement `AsRef<[u8]>`, so an `AsRef<[u8]>`-only blanket made
67/// every string an encoding *input* — even though strings are this crate's decoding
68/// input. Two silent wrong answers followed from that:
69///
70/// - `encoded.to_hex()` compiled for an [`EncodedSecret`](crate::EncodedSecret), which
71///   derefs to `str`. It hex-encoded the *encoded text*: a 32-byte key came back as 124
72///   hex characters, with no signature or name to suggest anything was wrong.
73/// - `"text".to_hex()` encoded a string's UTF-8 bytes, which is occasionally what
74///   someone means and usually not.
75///
76/// Both are now compile errors. Write `.as_bytes()` when you did mean the UTF-8.
77///
78/// # Implementing it
79///
80/// Implemented here for `[u8]`, `[u8; N]` and `Vec<u8>`. It is an **opt-in marker** in
81/// the same family as [`CloneableSecret`](crate::CloneableSecret) and
82/// [`SerializableSecret`](crate::SerializableSecret): implement it for your own
83/// byte-shaped newtype to make it encodable.
84///
85/// ```rust
86/// # #[cfg(all(feature = "encoding-hex", feature = "alloc"))] {
87/// use secure_gate::{EncodableBytes, ToHex};
88///
89/// struct Nonce([u8; 12]);
90/// impl AsRef<[u8]> for Nonce {
91///     fn as_ref(&self) -> &[u8] { &self.0 }
92/// }
93/// impl EncodableBytes for Nonce {}
94///
95/// assert_eq!(Nonce([0xAB; 12]).to_hex().len(), 24);
96/// # }
97/// ```
98///
99/// The orphan rule means no downstream crate can implement it for `str` or `String`,
100/// so the hole cannot be reopened from outside.
101#[cfg(any(
102    feature = "encoding-hex",
103    feature = "encoding-base32",
104    feature = "encoding-base64",
105    feature = "encoding-bech32",
106))]
107pub trait EncodableBytes {}
108
109#[cfg(any(
110    feature = "encoding-hex",
111    feature = "encoding-base32",
112    feature = "encoding-base64",
113    feature = "encoding-bech32",
114))]
115impl EncodableBytes for [u8] {}
116
117#[cfg(any(
118    feature = "encoding-hex",
119    feature = "encoding-base32",
120    feature = "encoding-base64",
121    feature = "encoding-bech32",
122))]
123impl<const N: usize> EncodableBytes for [u8; N] {}
124
125#[cfg(all(
126    feature = "alloc",
127    any(
128        feature = "encoding-hex",
129        feature = "encoding-base32",
130        feature = "encoding-base64",
131        feature = "encoding-bech32",
132    )
133))]
134impl EncodableBytes for alloc::vec::Vec<u8> {}
135
136// Every encoding trait produces EncodedSecret — all require alloc
137#[cfg(all(feature = "encoding-base32", feature = "alloc"))]
138pub use base32::ToBase32;
139#[cfg(all(feature = "encoding-base64", feature = "alloc"))]
140pub use base64_url::ToBase64Url;
141#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
142pub use bech32::ToBech32;
143#[cfg(feature = "encoding-bech32")]
144pub use bech32::{BECH32_CODE_LENGTH, Bech32Sized, Bech32Standard, bech32_code_length};
145#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
146pub use bech32m::ToBech32m;
147#[cfg(feature = "encoding-bech32")]
148pub use bech32m::{Bech32mSized, Bech32mStandard};
149#[cfg(all(feature = "encoding-hex", feature = "alloc"))]
150pub use hex::ToHex;