Skip to main content

secure_gate/
lib.rs

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