secure_gate/lib.rs
1
2// no_std by default; the `std` feature opts back into the standard library.
3// Verified in CI by cross-building for a bare-metal target.
4#![cfg_attr(not(feature = "std"), no_std)]
5// Forbid unsafe code unconditionally
6#![forbid(unsafe_code)]
7#![warn(missing_docs)]
8
9//! Secure wrappers for secrets with **explicit access** and **mandatory zeroization** — a
10//! `no_std`-compatible, zero-overhead library with audit-friendly access patterns.
11//!
12//! Secrets are **automatically zeroized on drop** (the inner type must implement
13//! [`Zeroize`](zeroize::Zeroize)). While a secret is held in [`Fixed`] or [`Dynamic`],
14//! there is no `Deref` and no `AsRef`: callers reach the inner secret only via
15//! [`RevealSecret`] / [`RevealSecretMut`], and `Debug` always prints `[REDACTED]`.
16//! All access follows a **3-tier model**: scoped closures (preferred), direct
17//! references (escape hatch), and owned extraction (consumption).
18//!
19//! Extraction is a hand-off, not a third layer of protection. The
20//! [output wrappers](#type-taxonomy) it returns ([`InnerSecret<T>`], [`EncodedSecret`])
21//! **do** implement `Deref` — that is their purpose. They keep zeroize-on-drop for the
22//! buffer they own and keep `Debug` redacted; ordinary copies you make of the derefed
23//! value (`*inner`, `.to_string()`) are not tracked. See
24//! [Where accident-prevention ends](#where-accident-prevention-ends).
25//!
26//! # Which type should I use?
27//!
28//! | Type | Allocation | Use case | Feature |
29//! |------|-----------|----------|----------|
30//! | [`Fixed<T>`] | Stack | Keys, nonces, tokens — compile-time-known size | Always available |
31//! | [`Dynamic<T>`] | Heap | Passwords, API keys, ciphertexts — variable length | `alloc` (default) |
32//!
33//! Both types share the same [`RevealSecret`] / [`RevealSecretMut`] access API.
34//!
35//! # Quick start
36//!
37//! ```rust
38//! use secure_gate::{Fixed, RevealSecret};
39//!
40//! // Wrap a 32-byte key
41//! let key = Fixed::new([0x42u8; 32]);
42//!
43//! // Tier 1 — scoped access (preferred): secret ref cannot escape the closure
44//! let first = key.with_secret(|bytes| bytes[0]);
45//! assert_eq!(first, 0x42);
46//!
47//! // Tier 2 — direct reference (escape hatch for FFI / third-party APIs)
48//! assert_eq!(key.expose_secret().len(), 32);
49//!
50//! // Debug is always redacted
51//! assert_eq!(format!("{:?}", key), "[REDACTED]");
52//! // key is zeroized when dropped
53//! ```
54//!
55//! ```rust
56//! # #[cfg(feature = "alloc")]
57//! # {
58//! use secure_gate::{Dynamic, RevealSecret};
59//!
60//! let password: Dynamic<String> = Dynamic::new(String::from("hunter2"));
61//! let len = password.with_secret(|s: &String| s.len());
62//! assert_eq!(len, 7);
63//! # }
64//! ```
65//!
66//! ```rust
67//! use secure_gate::{fixed_alias, RevealSecret};
68//!
69//! fixed_alias!(pub Aes256Key, 32);
70//!
71//! let key: Aes256Key = [0xABu8; 32].into();
72//! key.with_secret(|b| assert_eq!(b.len(), 32));
73//! ```
74//!
75//! # Module structure
76//!
77//! ```text
78//! secure_gate (lib.rs)
79//! ├── Fixed<T> ← always available, stack-allocated
80//! ├── Dynamic<T> ← requires `alloc`, heap-allocated
81//! ├── traits/
82//! │ ├── RevealSecret ← immutable access (always available)
83//! │ ├── RevealSecretMut ← mutable access (always available)
84//! │ ├── revealed_secrets/
85//! │ │ ├── InnerSecret<T> ← owned extraction wrapper
86//! │ │ └── EncodedSecret ← zeroizing encoded string wrapper (alloc)
87//! │ ├── ConstantTimeEq ← ct-eq feature
88//! │ ├── CloneableSecret ← cloneable feature
89//! │ ├── SerializableSecret← serde-serialize feature
90//! │ ├── encoding/ ← ToHex, ToBase32, ToBase64Url, ToBech32, ToBech32m
91//! │ └── decoding/ ← FromHexStr, FromBase32Str, FromBase64UrlStr, FromBech32Str, FromBech32mStr
92//! ├── macros/ ← fixed_alias!, fixed_newtype!, dynamic_alias!, dynamic_newtype!, etc.
93//! └── error ← FromSliceError, HexError, Base32Error, Base64Error, Bech32Error, DecodingError
94//! ```
95//!
96//! All public items are re-exported at the crate root. Use `secure_gate::Fixed`,
97//! not `secure_gate::fixed::Fixed`.
98//!
99//! # Type taxonomy
100//!
101//! | Category | Types | `Deref` to secret? | Purpose |
102//! |----------|-------|-------------------|----------|
103//! | **Secret wrappers** | [`Fixed<T>`], [`Dynamic<T>`] | No — use [`RevealSecret`] | Hold live secrets; `Debug` → `[REDACTED]` |
104//! | **Output wrappers** | [`InnerSecret<T>`], [`EncodedSecret`] | Yes — caller owns the data | Hold extracted or encoded results |
105//! | **Opt-in markers** | [`CloneableSecret`], [`SerializableSecret`] | — (no methods) | Implement on inner type `T` to unlock gated impls |
106//!
107//! `CloneableSecret` and `SerializableSecret` are implemented on the **inner type `T`**,
108//! not on `Fixed<T>` or `Dynamic<T>` directly. Output wrappers ([`InnerSecret`],
109//! [`EncodedSecret`]) are not secret wrappers and do not interact with these markers.
110//!
111//! # Where accident-prevention ends
112//!
113//! This crate carries two separate obligations, and they end in different places.
114//!
115//! **Accidents must not compile.** This applies while the secret is held in
116//! [`Fixed`]/[`Dynamic`], and it ends at the named extraction. `into_inner`,
117//! `expose_secret`, and `to_*_zeroizing` are the exits: you typed a name, the call site
118//! is grep-able, and ownership moves to you.
119//!
120//! **Documented behavior must be accurate.** This never ends, and it is why the
121//! taxonomy table above says `Deref: Yes` for output wrappers.
122//!
123//! What an output wrapper still guarantees: the buffer *it* owns is zeroized on drop,
124//! and its `Debug` prints `[REDACTED]`. What it does not guarantee: copies you make
125//! through `Deref` are ordinary values. `*inner` on a `Copy` type, `str::to_string()`,
126//! and `.to_owned()` all produce untracked plaintext. That is what extraction is for —
127//! the crate is not trying to follow the bytes into your TLS stack.
128//!
129//! Two consequences worth knowing:
130//!
131//! - `format!("{:?}", &*inner)` prints the secret. Redaction lives on the wrapper, not
132//! on `T`; dereferencing first opts out of it.
133//! - `into_zeroizing()` returns [`zeroize::Zeroizing<T>`], whose `Debug` is **not**
134//! redacted. It is a deliberate hand-off to a foreign type, not an equivalent wrapper.
135//!
136//! # Import paths
137//!
138//! ```rust
139//! // ✅ Correct — always import from the crate root
140//! use secure_gate::{Fixed, RevealSecret};
141//!
142//! // ❌ Wrong — these internal paths compile but are not the public API
143//! // use secure_gate::traits::reveal_secret::RevealSecret;
144//! // use secure_gate::traits::encoding::hex::ToHex;
145//! ```
146//!
147//! # Method resolution: wrapper methods vs trait methods
148//!
149//! Encoding methods exist at **two levels** — both produce identical results:
150//!
151//! | Call style | Example | Appears in audit sweep? |
152//! |-----------|---------|------------------------|
153//! | **Wrapper trait impl** (ergonomic) | `key.to_hex()` (needs `use secure_gate::ToHex`) | No — grep for `to_hex` directly |
154//! | **Trait via scoped access** (audit-friendly) | `key.with_secret(\|b\| b.to_hex())` | Yes — `with_secret` is grep-able |
155//!
156//! Both levels are impls of the **same** trait ([`ToHex`], [`ToBase32`], [`ToBase64Url`],
157//! [`ToBech32`], [`ToBech32m`]): a blanket impl covers the raw bytes inside
158//! `with_secret`, and per-wrapper impls on `Fixed<[u8; N]>` / `Dynamic<Vec<u8>>`
159//! delegate through `with_secret` internally. One trait also means one bound:
160//! `fn fingerprint<S: ToHex>(s: &S)` accepts wrappers and forwarding newtypes
161//! alike.
162//!
163//! # Feature flags
164//!
165//! | Feature | Default | Description |
166//! |---------|---------|-------------|
167//! | `alloc` | **yes** | Heap types ([`Dynamic<T>`]), `Vec`/`String` zeroization |
168//! | `std` | no | Full `std` support (implies `alloc`) |
169//! | | | **Cryptographic** |
170//! | `ct-eq` | no | [`ConstantTimeEq`] via `subtle` — timing-safe comparison |
171//! | `rand` | no | `from_random()` / `from_rng()` — `no_std` for [`Fixed`] |
172//! | | | **Serialization** |
173//! | `serde-serialize` | no | Serde `Serialize` (requires [`SerializableSecret`] marker) |
174//! | `serde-deserialize` | no | Serde `Deserialize` with 1 MiB default limit |
175//! | `serde` | no | Both directions |
176//! | | | **Encoding** |
177//! | `encoding-hex` | no | [`ToHex`] / [`FromHexStr`] via `base16ct` (constant-time) |
178//! | `encoding-base32` | no | [`ToBase32`] / [`FromBase32Str`] via `base32ct` (constant-time) |
179//! | `encoding-base64` | no | [`ToBase64Url`] / [`FromBase64UrlStr`] via `base64ct` (constant-time) |
180//! | `encoding-bech32` | no | [`ToBech32`] / [`FromBech32Str`] — BIP-173, extended ~5 KB limit |
181//! | `encoding-bech32m` | no | [`ToBech32m`] / [`FromBech32mStr`] — BIP-350, standard 90-byte limit |
182//! | `encoding` | no | All encoding features |
183//! | | | **Meta** |
184//! | `cloneable` | no | [`CloneableSecret`] opt-in cloning |
185//! | `full` | no | Everything |
186//!
187//! # What's available without `alloc`?
188//!
189//! With `default-features = false`:
190//! - [`Fixed<T>`], [`RevealSecret`], [`RevealSecretMut`], [`InnerSecret`]
191//! - [`Fixed::try_from_hex`](Fixed::try_from_hex), [`Fixed::try_from_base32`](Fixed::try_from_base32),
192//! [`Fixed::try_from_base64url`](Fixed::try_from_base64url),
193//! [`Fixed::try_from_bech32`](Fixed::try_from_bech32), [`Fixed::try_from_bech32m`](Fixed::try_from_bech32m)
194//! (no-alloc stack-based decoding)
195//! - [`fixed_alias!`], [`fixed_generic_alias!`] (type aliases), and
196//! [`fixed_newtype!`] (distinct nominal types — two keys of the same size that
197//! the compiler keeps apart)
198//! - [`FromSliceError`]
199//!
200//! **Not** available without `alloc`: [`Dynamic<T>`], [`EncodedSecret`],
201//! encoding traits ([`ToHex`], etc.), decoding traits ([`FromHexStr`], etc.),
202//! [`dynamic_alias!`], [`dynamic_generic_alias!`], [`dynamic_newtype!`], serde support.
203//!
204//! # `no_std`
205//!
206//! `no_std` compatible (`#![no_std]` unless the `std` feature is enabled — verified in CI
207//! by cross-building for `thumbv7em-none-eabihf`). [`Fixed<T>`] works without `alloc`.
208//! Enable `alloc` (default) for [`Dynamic<T>`]. For pure stack / embedded builds, use
209//! `default-features = false`. MSRV: **1.70** (Rust edition 2021, LTS line).
210//!
211//! One caveat: the `rand` feature compiles without `std` or `alloc`, but
212//! [`Fixed::from_random`] relies on `getrandom`, which needs a platform entropy backend.
213//! On bare-metal targets you must configure one (see the `getrandom` crate's
214//! "custom backend" documentation); without it, builds enabling `rand` fail at link
215//! time on such targets. [`Fixed::from_rng`] with a caller-supplied RNG has no such
216//! requirement.
217//!
218//! # Security
219//!
220//! This crate has **not** undergone an independent security audit. No unsafe code —
221//! enforced with `#![forbid(unsafe_code)]`. Prefer scoped access ([`RevealSecret::with_secret`])
222//! over direct references. Prefer zeroizing encoding variants (`to_hex_zeroizing`, etc.)
223//! when the encoded form is sensitive. See
224//! [SECURITY.md](https://github.com/Slurp9187/secure-gate/blob/main/secure-gate-core/SECURITY.md)
225//! for the full threat model.
226//!
227//! # Inherent Rust Limitations
228//!
229//! Three universal limits apply to any in-memory secret library in Rust (and in
230//! C, C++, and Go) — `secure-gate` documents them honestly rather than
231//! overclaiming:
232//!
233//! - **Stack-move residue**: moving a [`Fixed<T>`] by value leaves the prior stack
234//! slot uncleared until a later frame overwrites it. Prefer
235//! [`Fixed::new_with`](Fixed::new_with), pass by reference, or use
236//! [`Dynamic<T>`] for long-lived secrets.
237//! - **Heap-reallocation residue**: `Vec` / `String` realloc frees the old buffer
238//! unzeroed. Pre-size, use `Dynamic<[u8; N]>` (boxed array — no realloc surface),
239//! or install a zero-on-dealloc global allocator such as `zeroizing-alloc` at
240//! the binary level.
241//! - **Swap / core dumps**: process memory paged to disk or written on crash is
242//! outside any in-process library's reach. Use OS facilities (`mlock`,
243//! encrypted swap, disabled core dumps).
244//!
245//! Full discussion in
246//! [SECURITY.md § Inherent Rust Limitations](https://github.com/Slurp9187/secure-gate/blob/main/secure-gate-core/SECURITY.md#inherent-rust-limitations).
247//!
248//! See the [README](https://github.com/Slurp9187/secure-gate/blob/main/README.md) and
249//! [SECURITY.md](https://github.com/Slurp9187/secure-gate/blob/release/0.8/secure-gate-core/SECURITY.md) for full details.
250
251#[cfg(feature = "alloc")]
252extern crate alloc;
253
254#[cfg(feature = "alloc")]
255mod dynamic;
256
257/// Fixed-size secret wrapper types - always available with zero dependencies.
258/// These provide fundamental secure storage abstractions for fixed-size data.
259mod fixed;
260
261/// Centralized error types - always available.
262mod error;
263
264/// Core traits for wrapper polymorphism - always available.
265pub mod traits;
266
267/// Implementation detail of the `*_newtype!` macros — not a public API.
268///
269/// Generated code needs to name `Zeroize`, `serde` traits, and `alloc` types
270/// without requiring the caller to depend on those crates directly, and
271/// `::alloc::…` paths do not resolve in an ordinary `std` crate. Re-exporting
272/// them here keeps expansions self-contained via `$crate::__private::…`.
273///
274/// Semver-exempt: items here may change or disappear in any release.
275#[doc(hidden)]
276pub mod __private {
277 #[cfg(feature = "alloc")]
278 pub use alloc::{boxed::Box, string::String, vec::Vec};
279 #[cfg(feature = "rand")]
280 pub use rand::{TryCryptoRng, TryRngCore};
281 #[cfg(feature = "serde-deserialize")]
282 pub use serde::{Deserialize, Deserializer};
283 #[cfg(feature = "serde-serialize")]
284 pub use serde::{Serialize, Serializer};
285 pub use zeroize::{Zeroize, ZeroizeOnDrop};
286}
287
288/// Heap-allocated secret wrapper with explicit access and automatic zeroization on drop.
289///
290/// Variable-length secrets (passwords, API keys, ciphertexts). Inner type must implement
291/// `Zeroize`. Secret bytes live on the heap only — never on the stack. Requires `alloc`.
292///
293/// See [`Fixed<T>`] for the stack-allocated alternative.
294///
295/// ```rust
296/// # #[cfg(feature = "alloc")]
297/// # {
298/// use secure_gate::{Dynamic, RevealSecret};
299///
300/// let pw: Dynamic<String> = Dynamic::new(String::from("hunter2"));
301/// assert_eq!(pw.with_secret(|s: &String| s.len()), 7);
302/// assert_eq!(format!("{:?}", pw), "[REDACTED]");
303/// # }
304/// ```
305#[cfg(feature = "alloc")]
306pub use dynamic::Dynamic;
307
308/// Cursor-like reader over [`Dynamic<Vec<u8>>`] — see [`Dynamic::as_reader`].
309#[cfg(feature = "std")]
310pub use dynamic::DynamicReader;
311
312#[cfg(all(feature = "alloc", feature = "serde-deserialize"))]
313/// Default maximum byte length for `Dynamic<Vec<u8>>` / `Dynamic<String>` deserialization (1 MiB).
314///
315/// The standard `serde::Deserialize` impl for both types rejects payloads exceeding this value.
316/// Pass a custom ceiling to [`Dynamic::deserialize_with_limit`] when a different limit is needed.
317///
318/// **Important:** this limit is enforced *after* the upstream deserializer has fully
319/// materialized the payload. It is a **result-length acceptance bound**, not a
320/// pre-allocation DoS guard. For untrusted input, enforce size limits at the
321/// transport or parser layer upstream.
322pub use dynamic::MAX_DESERIALIZE_BYTES;
323
324/// Stack-allocated secret wrapper with explicit access and automatic zeroization on drop.
325///
326/// Fixed-size secrets (keys, nonces, tokens). Inner type must implement `Zeroize`.
327/// Always available — works without `alloc`. Prefer [`new_with`](Fixed::new_with) over
328/// [`new`](Fixed::new) when minimizing stack residue matters.
329///
330/// See [`Dynamic<T>`] for the heap-allocated alternative.
331///
332/// ```rust
333/// use secure_gate::{Fixed, RevealSecret};
334///
335/// let key = Fixed::new([0xABu8; 32]);
336/// key.with_secret(|b| assert_eq!(b[0], 0xAB));
337/// assert_eq!(format!("{:?}", key), "[REDACTED]");
338/// ```
339pub use fixed::Fixed;
340
341/// Marker trait that opts a secret type into cloning. No methods — gates the `Clone`
342/// impl on [`Fixed`] and [`Dynamic`]. Each clone is independently zeroized on drop,
343/// but increases the in-memory exposure surface. Requires `cloneable` feature.
344///
345/// Implement this on your inner type `T`; `Fixed<T>` and `Dynamic<T>` then gain the
346/// gated `Clone` impl automatically. **This marker is deliberately not implemented by
347/// default** on `Fixed<T>` or `Dynamic<T>` — cloning is an opt-in risk that must be
348/// explicitly enabled. Without the `cloneable` feature this type does not exist at all.
349///
350///
351/// **Coherence note:** Rust's orphan rule prevents downstream crates from implementing
352/// this marker for foreign types such as `String`, `Vec<u8>`, or `[u8; N]` — so
353/// `Dynamic<String>` and `Fixed<[u8; N]>` themselves can never become `Clone`. To opt
354/// a secret into cloning, define a newtype inner type in your crate (deriving
355/// `Zeroize` and `Clone`) and implement the marker on it. This is intentional: it keeps
356/// every cloneable secret type visible in *your* code. See the trait-level docs for a
357/// worked example.
358/// See also [`SerializableSecret`] (the other opt-in marker trait).
359#[cfg(feature = "cloneable")]
360pub use traits::CloneableSecret;
361
362/// Constant-time equality for secrets — prevents timing side-channel attacks.
363///
364/// Provides [`ct_eq()`](ConstantTimeEq::ct_eq) via the `subtle` crate. `==` is
365/// **deliberately not implemented** on [`Fixed`] / [`Dynamic`] — always use `ct_eq`.
366/// Requires `ct-eq` feature.
367///
368/// ```rust
369/// # #[cfg(feature = "ct-eq")]
370/// # {
371/// use secure_gate::{Fixed, ConstantTimeEq};
372///
373/// let a = Fixed::new([1u8; 32]);
374/// let b = Fixed::new([1u8; 32]);
375/// assert!(a.ct_eq(&b));
376/// # }
377/// ```
378#[cfg(feature = "ct-eq")]
379pub use traits::ConstantTimeEq;
380
381/// Explicit immutable access to secret contents (3-tier access model).
382///
383/// - **Tier 1** (preferred): [`with_secret()`](RevealSecret::with_secret) — scoped closure,
384/// borrow cannot escape.
385/// - **Tier 2** (escape hatch): [`expose_secret()`](RevealSecret::expose_secret) — direct
386/// `&T` reference for FFI / third-party APIs.
387/// - **Tier 3** (consumption): [`into_inner()`](RevealSecret::into_inner) — returns
388/// [`InnerSecret<T>`] with zeroization transferred to caller.
389///
390/// Length metadata lives in the separate [`SecretLen`] trait. See
391/// [`RevealSecretMut`] for the mutable counterpart.
392// Kept as a separate `use` from `SecretLen` on purpose: rustdoc 1.70 (the 0.8
393// line's MSRV toolchain) ICEs resolving intra-doc links on a grouped
394// `pub use a::{B, C};` re-export, and the two lines share this file.
395pub use traits::RevealSecret;
396
397/// Length metadata for secrets whose inner type has a meaningful length.
398///
399/// [`len()`](SecretLen::len) / [`byte_len()`](SecretLen::byte_len) /
400/// [`is_empty()`](SecretLen::is_empty) do not expose contents, but length itself
401/// can be sensitive for variable-length secrets — see the trait's Security
402/// section. Implemented for `Fixed<[T; N]>`, `Dynamic<String>`, and
403/// `Dynamic<Vec<T>>`; deliberately not for custom inner types.
404pub use traits::SecretLen;
405
406/// Explicit mutable access to secret contents.
407///
408/// Extends [`RevealSecret`]. Prefer [`with_secret_mut()`](RevealSecretMut::with_secret_mut)
409/// (Tier 1) over [`expose_secret_mut()`](RevealSecretMut::expose_secret_mut) (Tier 2).
410/// Only [`Fixed`] and [`Dynamic`] implement this — read-only wrappers deliberately do not.
411pub use traits::RevealSecretMut;
412
413/// Owned extraction **output wrapper** returned by [`RevealSecret::into_inner`] (Tier 3 access).
414///
415/// Wraps [`Zeroizing<T>`](zeroize::Zeroizing) with `Debug` → `[REDACTED]`. Implements
416/// `Deref<Target = T>` for ergonomic access. Both output wrappers deref
417/// ([`EncodedSecret`] derefs to `str`); the secret wrappers [`Fixed`] and [`Dynamic`]
418/// deliberately do not.
419///
420/// This is an **output wrapper**, not a secret wrapper like [`Fixed`]/[`Dynamic`] — it
421/// holds the owned result of Tier 3 extraction, with zeroization transferred to the
422/// caller. See also [`EncodedSecret`] (the other output wrapper, for encoded strings).
423/// Use [`into_zeroizing()`](InnerSecret::into_zeroizing) when an API requires
424/// `Zeroizing<T>` directly.
425pub use traits::InnerSecret;
426
427/// Placeholder values left behind by [`RevealSecret::into_inner`] (Tier 3 access).
428///
429/// When `into_inner` moves the real secret out of a wrapper, it must leave *something*
430/// behind for the wrapper's `Drop` impl to zeroize. `SentinelValue::sentinel_value()`
431/// produces that inert placeholder (an all-default array, empty `String`, or empty
432/// `Vec`). Implemented for `[T; N]` (any `N`, `T: Default`), `String`, and `Vec<T>`.
433///
434/// Implement this for your own inner types to make `into_inner` available on wrappers
435/// around them. A sentinel must never contain secret material.
436pub use traits::SentinelValue;
437
438/// Encoded string **output wrapper** for zeroizing encoded output.
439///
440/// This is an **output wrapper** — it exists *only* to keep encoded data zeroized until
441/// it drops. It is **not** a secret wrapper like [`Fixed`]/[`Dynamic`] and does not
442/// accept [`CloneableSecret`] or [`SerializableSecret`] markers. See also
443/// [`InnerSecret`] (the other output wrapper, for owned secret extraction).
444///
445/// Returned by all `*_zeroizing` encoding methods (`to_hex_zeroizing`,
446/// `to_base32_zeroizing`, `to_base64url_zeroizing`, `try_to_bech32_zeroizing`, etc.).
447/// Wraps `Zeroizing<String>` with `Debug` → `[REDACTED]`. Implements `Deref<Target = str>`,
448/// `AsRef<str>`, and `AsRef<[u8]>`. Deliberately **no** `Display`: `{}` on this type is
449/// a compile error, so `Debug` redaction cannot mislead a caller into logging the
450/// encoded secret. Write it out with `&*encoded`.
451///
452/// Use [`into_inner()`](EncodedSecret::into_inner) to extract a plain `String`
453/// (ends zeroization) or [`into_zeroizing()`](EncodedSecret::into_zeroizing) to
454/// preserve it.
455///
456/// Requires `alloc` feature.
457#[cfg(feature = "alloc")]
458pub use traits::EncodedSecret;
459
460/// Marker trait that opts a secret type into Serde serialization. No methods — gates the
461/// `Serialize` impl on [`Fixed`] and [`Dynamic`]. Serialization exposes the full secret;
462/// audit every impl. Requires `serde-serialize` feature.
463///
464/// **`Deserialize` does NOT require this marker** — it is gated separately by the
465/// `serde-deserialize` feature with its own impls on the wrapper types directly. This
466/// marker controls `Serialize` only.
467///
468/// Implement this on your inner type `T`; `Fixed<T>` and `Dynamic<T>` then gain the
469/// gated `Serialize` impl automatically. **This marker is deliberately not implemented
470/// by default** on `Fixed<T>` or `Dynamic<T>` — serialization is an opt-in risk that
471/// must be explicitly enabled. Without the `serde-serialize` feature this type does not
472/// exist at all.
473///
474///
475/// **Coherence note:** Rust's orphan rule prevents downstream crates from implementing
476/// this marker for foreign types such as `String`, `Vec<u8>`, or `[u8; N]` — so
477/// `Fixed<[u8; N]>` and `Dynamic<String>` themselves can never become `Serialize`. To
478/// opt a secret into serialization, define a newtype inner type in your crate (deriving
479/// `Zeroize` and `Serialize`) and implement the marker on it. This is intentional: it
480/// keeps every serializable secret type visible in *your* code. See the trait-level
481/// docs for a worked example.
482/// See also [`CloneableSecret`] (the other opt-in marker trait).
483#[cfg(feature = "serde-serialize")]
484pub use traits::SerializableSecret;
485
486// Type alias macros (always available)
487mod macros;
488
489/// Decodes Base32 strings (`&str`) to `Vec<u8>`. Blanket impl for `AsRef<str>`.
490/// Requires `encoding-base32` + `alloc`. See [`ToBase32`] for the encoding counterpart.
491#[cfg(all(feature = "encoding-base32", feature = "alloc"))]
492pub use traits::FromBase32Str;
493
494/// Decodes Base64url strings (`&str`) to `Vec<u8>`. Blanket impl for `AsRef<str>`.
495/// Requires `encoding-base64` + `alloc`. See [`ToBase64Url`] for the encoding counterpart.
496#[cfg(all(feature = "encoding-base64", feature = "alloc"))]
497pub use traits::FromBase64UrlStr;
498
499/// Decodes Bech32 (BIP-173) strings to `Vec<u8>` with HRP validation.
500/// Requires `encoding-bech32` + `alloc`. See [`ToBech32`] for the encoding counterpart.
501#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
502pub use traits::FromBech32Str;
503
504/// Decodes Bech32m (BIP-350) strings to `Vec<u8>` with HRP validation.
505/// Requires `encoding-bech32m` + `alloc`. See [`ToBech32m`] for the encoding counterpart.
506#[cfg(all(feature = "encoding-bech32m", feature = "alloc"))]
507pub use traits::FromBech32mStr;
508
509/// Decodes hex strings (`&str`) to `Vec<u8>`. Blanket impl for `AsRef<str>`.
510/// Requires `encoding-hex` + `alloc`. See [`ToHex`] for the encoding counterpart.
511#[cfg(all(feature = "encoding-hex", feature = "alloc"))]
512pub use traits::FromHexStr;
513
514/// Encodes byte data as Base32 strings (RFC 4648 §6, uppercase, no padding).
515/// Blanket impl for `AsRef<[u8]>`. Requires `encoding-base32` + `alloc`.
516/// See [`FromBase32Str`] for the decoding counterpart.
517#[cfg(all(feature = "encoding-base32", feature = "alloc"))]
518pub use traits::ToBase32;
519
520/// Encodes byte data as Base64url strings (RFC 4648, URL-safe, no padding).
521/// Blanket impl for `AsRef<[u8]>`. Requires `encoding-base64` + `alloc`.
522/// See [`FromBase64UrlStr`] for the decoding counterpart.
523#[cfg(all(feature = "encoding-base64", feature = "alloc"))]
524pub use traits::ToBase64Url;
525
526/// Encodes byte data as Bech32 (BIP-173) strings with extended ~5 KB payload limit.
527/// Blanket impl for `AsRef<[u8]>`. Requires `encoding-bech32` + `alloc`.
528/// See [`FromBech32Str`] for the decoding counterpart.
529#[cfg(all(feature = "encoding-bech32", feature = "alloc"))]
530pub use traits::ToBech32;
531
532/// Encodes byte data as Bech32m (BIP-350) strings with standard 90-byte payload limit.
533/// Blanket impl for `AsRef<[u8]>`. Requires `encoding-bech32m` + `alloc`.
534/// See [`FromBech32mStr`] for the decoding counterpart.
535#[cfg(all(feature = "encoding-bech32m", feature = "alloc"))]
536pub use traits::ToBech32m;
537
538/// Encodes byte data as hexadecimal strings (constant-time via `base16ct`).
539/// Blanket impl for `AsRef<[u8]>`. Provides `to_hex()`, `to_hex_upper()`, and
540/// zeroizing variants. Requires `encoding-hex` + `alloc`.
541/// See [`FromHexStr`] for the decoding counterpart.
542#[cfg(all(feature = "encoding-hex", feature = "alloc"))]
543pub use traits::ToHex;
544
545/// Marker trait for types that support secure decoding (`AsRef<str>`). No methods —
546/// enables blanket impls of [`FromHexStr`], [`FromBase32Str`], [`FromBase64UrlStr`], etc.
547#[cfg(any(
548 feature = "encoding-hex",
549 feature = "encoding-base32",
550 feature = "encoding-base64",
551 feature = "encoding-bech32",
552 feature = "encoding-bech32m",
553))]
554pub use traits::SecureDecoding;
555
556/// Marker trait for types that support secure encoding (`AsRef<[u8]>`). No methods —
557/// enables blanket impls of [`ToHex`], [`ToBase32`], [`ToBase64Url`], etc.
558#[cfg(any(
559 feature = "encoding-hex",
560 feature = "encoding-base32",
561 feature = "encoding-base64",
562 feature = "encoding-bech32",
563 feature = "encoding-bech32m",
564))]
565pub use traits::SecureEncoding;
566
567/// Errors from Bech32 (BIP-173) and Bech32m (BIP-350) decoding.
568/// Variant shapes are identical in debug and release builds; no secret material
569/// (payload bytes, HRP strings) is ever carried in an error.
570#[cfg(any(feature = "encoding-bech32", feature = "encoding-bech32m"))]
571pub use error::Bech32Error;
572
573/// Errors from Base32 (RFC 4648 §6, uppercase, unpadded) decoding. Variant shapes are
574/// identical in debug and release builds; only numeric length metadata is carried.
575#[cfg(feature = "encoding-base32")]
576pub use error::Base32Error;
577
578/// Errors from Base64url decoding. Variant shapes are identical in debug and
579/// release builds; only numeric length metadata is carried.
580#[cfg(feature = "encoding-base64")]
581pub use error::Base64Error;
582
583/// Errors from hex decoding. Variant shapes are identical in debug and
584/// release builds; only numeric length metadata is carried.
585#[cfg(feature = "encoding-hex")]
586pub use error::HexError;
587
588/// Unified error type wrapping format-specific decoding errors ([`HexError`],
589/// [`Base32Error`], [`Base64Error`], [`Bech32Error`]). Always available; variants depend
590/// on enabled features.
591pub use error::DecodingError;
592
593/// Error returned when a byte slice cannot be converted to `Fixed<[u8; N]>` due to
594/// length mismatch. Produced by `Fixed::try_from(&[u8])`.
595pub use error::FromSliceError;
596
597// The README's examples are compiled and run by `cargo test --doc`, so they cannot
598// silently rot. `cfg(doctest)` is set only while rustdoc *collects doctests* — never
599// while it *builds documentation* — so this item never reaches docs.rs or `cargo doc`
600// output, and the README is not duplicated onto the crate page.
601//
602// Gated on `full` as well: the README documents the fully-featured crate, so its
603// examples use `alloc` and the encoding traits unconditionally. Without this gate the
604// `--no-default-features` doctest job in CI would fail on imports the README does not
605// feature-gate. CI's `--features full` and `--all-features` entries do run them.
606//
607// It must live at the end of the file: an item here would otherwise sit ahead of the
608// `//!` crate docs above, which is not allowed.
609#[cfg(all(doctest, feature = "full"))]
610#[doc = include_str!("../README.md")]
611pub struct ReadmeDoctests;