Skip to main content

secure_gate/macros/
dynamic_newtype.rs

1//! `dynamic_newtype!` — nominal newtype over `Dynamic<T>`.
2//!
3//! Ships in 0.9.0. Design record:
4//! [`docs/nominal_newtypes.md`](https://github.com/Slurp9187/secure-gate/blob/main/docs/design/nominal_newtypes.md) — a repository file, not part of the
5//! published crate, so this is a link and not a path.
6
7/// Creates a distinct nominal type wrapping [`Dynamic<T>`](crate::Dynamic).
8///
9/// Generates a `struct` rather than a `type` alias: two `dynamic_newtype!`
10/// types over the same inner type are distinct, where two plain `type`
11/// aliases over [`Dynamic<T>`](crate::Dynamic) are one type.
12///
13/// # Syntax
14///
15/// ```text
16/// dynamic_newtype!(pub Name, String);                    // shaped: full String API
17/// dynamic_newtype!(pub Name, Vec<u8>);                   // shaped: full byte API
18/// dynamic_newtype!(pub Name, generic T);                 // reduced API, opted into
19/// dynamic_newtype!(pub(crate) Name, String);             // crate-visible
20/// dynamic_newtype!(Name, String);                        // private
21/// dynamic_newtype!(pub Name, String, "doc string");      // with custom doc
22/// dynamic_newtype!(pub Name, String, derive: [FromWrapper]);
23/// dynamic_newtype!(pub Name, String, "doc", derive: [FromWrapper]);
24/// ```
25///
26/// Supported `derive:` options are `Deserialize`, `FromWrapper`, `IntoWrapper`, and `WrapperAccess` (= both directions), plus `ConstantTimeEq` **on the `generic` arm only**;
27/// `Clone` and `Serialize` are deliberately absent — see
28/// [`fixed_newtype!`](crate::fixed_newtype) for the reasoning and the
29/// hand-written pattern, which applies identically here.
30///
31/// **`ConstantTimeEq` is not an option on the `String` and `Vec<u8>` arms, and
32/// naming it is an error.** Both wrappers implement it whenever the `ct-eq` feature
33/// is on, so a plain `type Name = Dynamic<String>;` always had
34/// [`ct_eq`](crate::ConstantTimeEq::ct_eq) and the newtype — the spelling this crate
35/// recommends as the safer of the two — did not. Since these arms now emit the impl
36/// themselves, a second one from the `derive:` list is `E0119: conflicting
37/// implementations` — delete the token. It stays a real opt-in on the `generic` arm,
38/// where the inner type may or may not implement the trait.
39///
40/// Note that `ct_eq` on a variable-length secret short-circuits when the two
41/// lengths differ, so only the equal-length comparison is constant-time. Where the
42/// length itself is sensitive, reach for [`fixed_newtype!`](crate::fixed_newtype).
43///
44/// # The `"doc string"` slot takes exactly one string literal
45///
46/// That argument is matched as `$doc:literal`, so it accepts a single string
47/// literal and nothing else. `concat!("a ", "b")` is a macro call, not a
48/// literal, and will not match — nor will a `const`, or pieces joined together
49/// at compile time.
50///
51/// **The error you get is misleading.** When the doc argument fails to match,
52/// the input falls through to the catch-all arm, which reports that the *inner
53/// type* is not one of the shaped types and suggests writing `String` or
54/// `Vec<u8>` literally. The inner type is usually fine; the doc slot is what
55/// failed. If you see that message on a call whose inner type is plainly
56/// `String` or `Vec<u8>`, look at the doc argument first.
57///
58/// For anything longer than one literal — multiple paragraphs, generated text,
59/// `#[doc = ...]` — put ordinary attributes before the name instead. The macro
60/// accepts `$(#[$attr:meta])*` there, so normal `///` comments work and are the
61/// better style for real prose:
62///
63/// ```rust
64/// # #[cfg(feature = "alloc")] {
65/// use secure_gate::dynamic_newtype;
66///
67/// dynamic_newtype!(
68///     /// First line of prose.
69///     ///
70///     /// A second paragraph, as many lines as you like.
71///     pub SessionToken, String
72/// );
73/// # let _ = SessionToken::new(String::from("x"));
74/// # }
75/// ```
76///
77/// The `"doc string"` form remains for short one-liners and for callers
78/// generating the whole invocation from another macro.
79///
80/// # Inner types must be written literally
81///
82/// Macros match **tokens**, not resolved types. `String` and `Vec<u8>` are
83/// matched as literal tokens, so they must be spelled exactly that way to get
84/// the full API for their shape:
85///
86/// ```rust
87/// # #[cfg(feature = "alloc")] {
88/// use secure_gate::{dynamic_newtype, SecretLen};
89///
90/// dynamic_newtype!(pub ApiKey, String);       // full String API
91/// dynamic_newtype!(pub Token, Vec<u8>);       // full byte API: hex, io::Write, …
92///
93/// let k: ApiKey = "sk_live".into();
94/// assert_eq!(k.len(), 7);
95/// # }
96/// ```
97///
98/// A type alias (`type MyStr = String`) or a fully-qualified path
99/// (`std::string::String`) is a *different token sequence*, so it cannot reach
100/// those arms — and no macro can see through it, because macro expansion runs
101/// before type resolution. Rather than silently hand back a newtype missing
102/// half its API, such input is a **compile error** naming the fix.
103///
104/// `generic` is **not** a way to get past that error. `generic Vec<u8>` and
105/// `generic String` are themselves a compile error, because each is the same
106/// growable payload as the shaped arm with strictly less API — for `Vec<u8>`,
107/// no `io::Write`, which is the only growth path that wipes the buffer it
108/// abandons. Delete the word and the safe surface comes with it.
109///
110/// That reject matches literal tokens too, so it is a guard on the *spelling*,
111/// not on the type: `generic MyBytes` and `generic alloc::vec::Vec<u8>` are
112/// different token sequences and still land on the reduced arm, carrying a
113/// growable payload with no safe way to grow it. The macro cannot see through
114/// an alias or a path — that is the same limitation as above, not a separate
115/// one — and a token deny-list does not become type-level by getting longer.
116/// If the inner type is a `String` or a `Vec<u8>` under any spelling, write the
117/// literal token.
118///
119/// When the inner type genuinely is something else, opt in with `generic`:
120///
121/// ```rust
122/// # #[cfg(feature = "alloc")] {
123/// use secure_gate::{dynamic_newtype, RevealSecret};
124///
125/// dynamic_newtype!(pub Counters, generic Vec<u32>);
126///
127/// let c = Counters::new(vec![1u32, 2, 3]);
128/// assert_eq!(c.with_secret(|v| v.len()), 3);
129/// # }
130/// ```
131///
132/// The `generic` form deliberately provides less: [`RevealSecret`](crate::RevealSecret),
133/// [`RevealSecretMut`](crate::RevealSecretMut), redacted `Debug`, `Zeroize`,
134/// `ZeroizeOnDrop`, and `new` — no [`SecretLen`](crate::SecretLen) and no
135/// encoders. The reason is what the macro can see rather than what each method
136/// means: the inner type arrives as one opaque token, so the expansion cannot
137/// tell a `Vec` from a struct and withholds the shape-dependent surface
138/// uniformly. An encoder genuinely has no meaning for an arbitrary inner type;
139/// `SecretLen` does, and the base wrapper implements it for every
140/// `Dynamic<Vec<T>>` with a correct `byte_len`, so `derive: [IntoWrapper]`
141/// reaches it through `as_wrapper()`. What the arm withholds is the newtype's
142/// own `len()`, not the answer. Writing the marker is how you say you know
143/// that.
144///
145/// It also has no `new_with`, which the `Vec<u8>` arm does get, and the reason is
146/// sharper than it used to be. [`fixed_newtype!`](crate::fixed_newtype)'s generic arm
147/// *does* forward [`Fixed::new_with`](crate::Fixed::new_with), because that constructor
148/// is generic over any `T: SentinelValue` — it hands the closure the inner value itself.
149/// `Dynamic::new_with` hands a sized **byte slot**, `FnOnce(&mut [u8])`, which is what
150/// makes growth inexpressible and the reallocation hazard impossible. A byte slot is
151/// meaningless for an arbitrary inner type, so there is nothing here to forward. `new`
152/// takes its value by value — and *moves* the allocation rather than copying it, so
153/// build it pre-sized and as close to the call as you can.
154///
155/// The `String` arm has no `new_with` either, as of 0.9.0-rc.12, and cannot: a `String`
156/// must hold valid UTF-8 and characters have variable byte widths, so a fixed byte
157/// window is not somewhere arbitrary text can be written.
158///
159/// The two macros are also asymmetric about `SecretLen`, deliberately.
160/// `fixed_newtype!` has a third arm for a *literal* array — `generic [i16; 256]` —
161/// which forwards `SecretLen` because the element type and the length are both
162/// visible as tokens. There is no equivalent here and none is wanted: a `Dynamic`
163/// payload carries its length at runtime rather than in the type, so there is no
164/// token for an arm to read, and `derive: [IntoWrapper]` remains the way to reach
165/// the base wrapper's own `len()`.
166///
167/// # Implementation Notes
168///
169/// The generated type is `#[repr(transparent)]` over the wrapper and delegates
170/// through `#[inline]` methods, so it costs nothing at runtime.
171///
172/// Unlike [`Fixed`](crate::Fixed), there is **no compile-time zero-size check**
173/// here, and the reason is about the payload rather than the wrapper. For
174/// `String` and `Vec<u8>` emptiness is a runtime property — an empty one is a
175/// legitimate value to hold before validation — so there is nothing for a
176/// compile-time check to decide. That leaves one real gap: a statically
177/// zero-sized inner type. `Dynamic<Zst>` for a zero-sized `Zst` constructs
178/// happily, where `Fixed<Zst>` is now rejected. Validate expected lengths in
179/// your own tests, and do not reach for a zero-sized inner type expecting to be
180/// stopped.
181///
182/// **Do not add your own `Drop` impl.** None is needed: the wrapped
183/// [`Dynamic`](crate::Dynamic) still runs its own, so zeroization is
184/// unaffected. Adding one makes the inner field unmovable (**E0509**) and
185/// silently costs you [`into_inner`](crate::RevealSecret::into_inner).
186///
187/// # Security
188///
189/// Generated types inherit every [`Dynamic`](crate::Dynamic) guarantee:
190/// zeroize on drop including spare capacity, `Debug` that always prints
191/// `[REDACTED]`, and access only through
192/// [`RevealSecret`](crate::RevealSecret) /
193/// [`RevealSecretMut`](crate::RevealSecretMut). There is no `Deref`, so a
194/// generated newtype is not coercible to its wrapper — which is what keeps the
195/// nominal separation total rather than by-value-only.
196///
197/// **Nominal separation guards against mistakes, not intent** — but nothing is
198/// generated that would undo it by accident. There is no `From<Wrapper>` and
199/// no `Deref`, so a base-typed value cannot flow into a newtype through
200/// `.into()`, and `&Newtype` never coerces to `&Wrapper` at a call site. By
201/// default the only way material enters or leaves is the 3-tier access API —
202/// a `with_secret` round trip — which is explicit and shows up in the audit
203/// sweep. Base-wrapper access is opt-in, per newtype, and **split by
204/// direction**: `derive: [FromWrapper]` adds `from_wrapper` (a base value
205/// *enters* this role), `derive: [IntoWrapper]` adds `as_wrapper`,
206/// `as_wrapper_mut`, and `into_wrapper` (material *leaves* this role toward
207/// the base type), and `derive: [WrapperAccess]` is shorthand for both.
208///
209/// These cross a different wall from `into_inner`. A newtype has two:
210///
211/// | Wall | Going in | Going out |
212/// |---|---|---|
213/// | Contents (the wrapper's protection) | `new`, `From<&str>` / `From<&[u8]>` | `with_secret`, `expose_secret`, `into_inner` |
214/// | Role (the nominal label) | `from_wrapper` | `as_wrapper`, `as_wrapper_mut`, `into_wrapper` |
215///
216/// `into_inner` leaves the protection: it hands back the plain value, so the
217/// contents are in the caller's hands (tier 3 of the access model, audited).
218/// `into_wrapper` is a **label** drop; `into_inner` is a **protection** drop.
219/// Confusing those two names is the main way this model gets misread. `into_wrapper` only removes the label: the
220/// result is still a `Dynamic`, still unreadable without `with_secret`. The
221/// role row exists so that dropping a label never forces opening the
222/// contents — without it, reaching base-typed code costs an `into_inner` plus
223/// a rebuild, a reveal the job never needed.
224///
225/// **Which direction is safe depends on the pool.** In a mixed tree the base
226/// type is not raw material: every plain `type` alias sharing it *is* that
227/// type, so a directional token connects this role to all of them at once.
228/// `FromWrapper` lets anything in the pool become this role — the source never
229/// opts in, because the source is just the base type — so a type that guards
230/// a boundary must never take it. `IntoWrapper` lets this role become anything
231/// in the pool, which is safe only when the role is no more sensitive than
232/// the least-sensitive plain alias sharing its base; on a secret role it is an
233/// explicit, greppable downgrade, not a neutral operation. The default,
234/// neither token, is sufficient more often than it looks. The exposure is
235/// largest during a partial migration — the regime real consumers live in —
236/// because while most of the pool stays a plain `type` alias, the base type
237/// is a universal donor. Audit these methods the way you audit
238/// `expose_secret()`.
239///
240/// The heap caveats of [`Dynamic`](crate::Dynamic) carry over unchanged: see
241/// `SECURITY.md` on realloc residue for `Vec`/`String` growth after wrapping.
242///
243/// # See also
244///
245/// - [`fixed_newtype!`](crate::fixed_newtype) — stack-allocated counterpart,
246///   and the reference for the `derive:` rules
247/// - a plain `type` alias — `pub type Password = Dynamic<String>;` — when a
248///   readable name is all that is wanted and interchangeability with the base
249///   type is a feature rather than a risk; the crate's README argues when that
250///   is the right reach
251#[cfg(feature = "alloc")]
252#[macro_export]
253macro_rules! dynamic_newtype {
254    // ---- `generic` applied to a shape that has its own arm: rejected ----
255    //
256    // `generic Vec<u8>` and `generic String` are strictly dominated spellings,
257    // but not for the same reason — the two shaped arms no longer offer the
258    // same growth story, so what each `generic` spelling gives up differs too.
259    //
260    // `generic Vec<u8>` takes a payload this crate already knows how to grow
261    // safely and then withholds the method that does it: the `generic` arm
262    // emits only `__sg_newtype_base!` plus a constructor, so the shaped arm's
263    // `SecretLen`, `From`, `new_with`, `try_new_with`, encoders, and — under
264    // `std` — the `io::Write` impl that wipes the buffer it abandons, are all
265    // absent. Same payload, strictly less API, and no reason to write it.
266    //
267    // `generic String` is dominated for a narrower reason, because there is
268    // less for it to withhold: the shaped `String` arm has no `new_with` of
269    // its own, as of 0.9.0-rc.12, and cannot — a `String` must hold valid
270    // UTF-8 and characters have variable byte widths, so there is no sized
271    // byte slot that arbitrary text can be written into. What the `generic`
272    // arm withholds is `SecretLen` and `From<&str>` only. The crate has no
273    // safe growth path for `Dynamic<String>` at all; pre-size the value and
274    // move it in with `Dynamic::new`.
275    //
276    // These must precede every `generic $inner:ty` arm, for the reason the
277    // comment below gives: once a `:ty` fragment is parsed there is no
278    // backtracking. They match literal tokens only, exactly as the shaped arms
279    // do, so a type alias or a path-qualified spelling
280    // (`alloc::vec::Vec<u8>`) is a different token sequence and is NOT caught.
281    // That remainder is documented rather than chased; a token deny-list does
282    // not become type-level by getting longer.
283    ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
284        $crate::__sg_dynamic_generic_is_shaped!(@bytes);
285    };
286    ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>, $doc:literal) => {
287        $crate::__sg_dynamic_generic_is_shaped!(@bytes);
288    };
289    ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>, derive: [$($opt:ident),* $(,)?]) => {
290        $crate::__sg_dynamic_generic_is_shaped!(@bytes);
291    };
292    ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>) => {
293        $crate::__sg_dynamic_generic_is_shaped!(@bytes);
294    };
295    ($(#[$attr:meta])* $vis:vis $name:ident, generic String, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
296        $crate::__sg_dynamic_generic_is_shaped!(@string);
297    };
298    ($(#[$attr:meta])* $vis:vis $name:ident, generic String, $doc:literal) => {
299        $crate::__sg_dynamic_generic_is_shaped!(@string);
300    };
301    ($(#[$attr:meta])* $vis:vis $name:ident, generic String, derive: [$($opt:ident),* $(,)?]) => {
302        $crate::__sg_dynamic_generic_is_shaped!(@string);
303    };
304    ($(#[$attr:meta])* $vis:vis $name:ident, generic String) => {
305        $crate::__sg_dynamic_generic_is_shaped!(@string);
306    };
307
308    // ---- doc-string forms, one per shape ----
309    //
310    // These must be matched by literal tokens *before* any arm that opens with
311    // a `$inner:ty` fragment. `macro_rules!` does not backtrack once a `:ty`
312    // fragment has been parsed: a single `$inner:ty, $doc:literal` arm would
313    // consume the type, fail to find the comma, and hard-error with "unexpected
314    // end of macro invocation" instead of falling through to the arms below.
315    ($(#[$attr:meta])* $vis:vis $name:ident, String, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
316        $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, String, derive: [$($opt),*]);
317    };
318    ($(#[$attr:meta])* $vis:vis $name:ident, String, $doc:literal) => {
319        $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, String, derive: []);
320    };
321    ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
322        $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, Vec<u8>, derive: [$($opt),*]);
323    };
324    ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>, $doc:literal) => {
325        $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, Vec<u8>, derive: []);
326    };
327    ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
328        $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, generic $inner, derive: [$($opt),*]);
329    };
330    ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty, $doc:literal) => {
331        $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, generic $inner, derive: []);
332    };
333
334    // ---- String arm: matched by literal tokens, before the generic arm ----
335    ($(#[$attr:meta])* $vis:vis $name:ident, String) => {
336        $crate::dynamic_newtype!($(#[$attr])* $vis $name, String, derive: []);
337    };
338    ($(#[$attr:meta])* $vis:vis $name:ident, String, derive: [$($opt:ident),* $(,)?]) => {
339        $crate::__sg_newtype_base!(
340            $(#[$attr])* $vis $name($crate::Dynamic<$crate::__private::String>), derive: [$($opt),*]
341        );
342        $crate::__sg_newtype_ct_eq!($name);
343        $crate::__sg_newtype_len!($name);
344        $crate::__sg_dynamic_ctor!(@into $name, $crate::__private::String);
345
346        impl ::core::convert::From<&str> for $name {
347            #[inline]
348            fn from(s: &str) -> Self { Self(<$crate::Dynamic<$crate::__private::String>>::from(s)) }
349        }
350    };
351
352    // ---- Vec<u8> arm ----
353    ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>) => {
354        $crate::dynamic_newtype!($(#[$attr])* $vis $name, Vec<u8>, derive: []);
355    };
356    ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>, derive: [$($opt:ident),* $(,)?]) => {
357        $crate::__sg_newtype_base!(
358            $(#[$attr])* $vis $name($crate::Dynamic<$crate::__private::Vec<u8>>), derive: [$($opt),*]
359        );
360        $crate::__sg_newtype_ct_eq!($name);
361        $crate::__sg_newtype_len!($name);
362        $crate::__sg_dynamic_ctor!(@into $name, $crate::__private::Vec<u8>);
363
364        impl ::core::convert::From<&[u8]> for $name {
365            #[inline]
366            fn from(s: &[u8]) -> Self { Self(<$crate::Dynamic<$crate::__private::Vec<u8>>>::from(s)) }
367        }
368        impl $name {
369            /// Writes into a sized, pre-zeroed slot inside the wrapper's own storage.
370            /// Growth is not expressible, so no reallocation can abandon the secret.
371            #[inline]
372            pub fn new_with<F>(len: usize, f: F) -> Self
373            where F: ::core::ops::FnOnce(&mut [u8]) {
374                Self(<$crate::Dynamic<$crate::__private::Vec<u8>>>::new_with(len, f))
375            }
376
377            /// The same, for a fill that can fail. On `Err` the partial write is
378            /// zeroized before the error is returned.
379            #[inline]
380            pub fn try_new_with<F, E>(len: usize, f: F) -> ::core::result::Result<Self, E>
381            where F: ::core::ops::FnOnce(&mut [u8]) -> ::core::result::Result<(), E> {
382                ::core::result::Result::Ok(Self(
383                    <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_new_with(len, f)?
384                ))
385            }
386        }
387
388        $crate::__sg_if_rand! {
389            impl $name {
390                /// Generates `len` random bytes from the system RNG.
391                #[inline]
392                pub fn from_random(len: usize) -> Self { Self(<$crate::Dynamic<$crate::__private::Vec<u8>>>::from_random(len)) }
393
394                /// Generates `len` bytes from a caller-supplied CSPRNG.
395                #[inline]
396                pub fn from_rng<R>(len: usize, rng: &mut R) -> ::core::result::Result<Self, R::Error>
397                where
398                    R: $crate::__private::TryRng + $crate::__private::TryCryptoRng,
399                {
400                    ::core::result::Result::Ok(Self(
401                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::from_rng(len, rng)?,
402                    ))
403                }
404            }
405        }
406
407        $crate::__sg_if_hex! {
408            impl $name {
409                /// Constant-time hex decode into this secret type.
410                #[inline]
411                pub fn try_from_hex(s: &str) -> ::core::result::Result<Self, $crate::HexError> {
412                    ::core::result::Result::Ok(Self(
413                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_hex(s)?,
414                    ))
415                }
416            }
417            impl $crate::ToHex for $name {
418                #[inline]
419                fn to_hex(&self) -> $crate::EncodedSecret {
420                    $crate::ToHex::to_hex(&self.0)
421                }
422                #[inline]
423                fn to_hex_upper(&self) -> $crate::EncodedSecret {
424                    $crate::ToHex::to_hex_upper(&self.0)
425                }
426            }
427        }
428
429        $crate::__sg_if_base32! {
430            impl $name {
431                /// Constant-time Base32 decode into this secret type.
432                #[inline]
433                pub fn try_from_base32(s: &str) -> ::core::result::Result<Self, $crate::Base32Error> {
434                    ::core::result::Result::Ok(Self(
435                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_base32(s)?,
436                    ))
437                }
438            }
439            impl $crate::ToBase32 for $name {
440                #[inline]
441                fn to_base32(&self) -> $crate::EncodedSecret {
442                    $crate::ToBase32::to_base32(&self.0)
443                }
444            }
445        }
446
447        $crate::__sg_if_base64! {
448            impl $name {
449                /// Constant-time Base64url decode into this secret type.
450                #[inline]
451                pub fn try_from_base64url(s: &str) -> ::core::result::Result<Self, $crate::Base64Error> {
452                    ::core::result::Result::Ok(Self(
453                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_base64url(s)?,
454                    ))
455                }
456            }
457            impl $crate::ToBase64Url for $name {
458                #[inline]
459                fn to_base64url(&self) -> $crate::EncodedSecret {
460                    $crate::ToBase64Url::to_base64url(&self.0)
461                }
462            }
463        }
464
465        $crate::__sg_if_bech32! {
466            impl $name {
467                /// HRP-validated Bech32 decode into this secret type.
468                #[inline]
469                pub fn try_from_bech32(s: &str, expected_hrp: &str)
470                    -> ::core::result::Result<Self, $crate::Bech32Error> {
471                    ::core::result::Result::Ok(Self(
472                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32(s, expected_hrp)?,
473                    ))
474                }
475                /// Bech32 decode without HRP validation.
476                #[inline]
477                pub fn try_from_bech32_unchecked(s: &str)
478                    -> ::core::result::Result<Self, $crate::Bech32Error> {
479                    ::core::result::Result::Ok(Self(
480                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32_unchecked(s)?,
481                    ))
482                }
483                /// HRP-validated Bech32 decode accepting strings up to `C` characters.
484                #[inline]
485                pub fn try_from_bech32_sized<const C: usize>(s: &str, expected_hrp: &str)
486                    -> ::core::result::Result<Self, $crate::Bech32Error> {
487                    ::core::result::Result::Ok(Self(
488                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32_sized::<C>(s, expected_hrp)?,
489                    ))
490                }
491                /// Bech32 decode without HRP validation, accepting strings up to `C` characters.
492                #[inline]
493                pub fn try_from_bech32_unchecked_sized<const C: usize>(s: &str)
494                    -> ::core::result::Result<Self, $crate::Bech32Error> {
495                    ::core::result::Result::Ok(Self(
496                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32_unchecked_sized::<C>(s)?,
497                    ))
498                }
499            }
500            impl $crate::ToBech32 for $name {
501                #[inline]
502                fn try_to_bech32(
503                    &self,
504                    hrp: &str,
505                    case: $crate::Case,
506                ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
507                    $crate::ToBech32::try_to_bech32(&self.0, hrp, case)
508                }
509                #[inline]
510                fn try_to_bech32_sized<const C: usize>(
511                    &self,
512                    hrp: &str,
513                    case: $crate::Case,
514                ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
515                    $crate::ToBech32::try_to_bech32_sized::<C>(&self.0, hrp, case)
516                }
517            }
518        }
519
520        $crate::__sg_if_bech32m! {
521            impl $name {
522                /// HRP-validated Bech32m decode into this secret type.
523                #[inline]
524                pub fn try_from_bech32m(s: &str, expected_hrp: &str)
525                    -> ::core::result::Result<Self, $crate::Bech32Error> {
526                    ::core::result::Result::Ok(Self(
527                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m(s, expected_hrp)?,
528                    ))
529                }
530                /// Bech32m decode without HRP validation.
531                #[inline]
532                pub fn try_from_bech32m_unchecked(s: &str)
533                    -> ::core::result::Result<Self, $crate::Bech32Error> {
534                    ::core::result::Result::Ok(Self(
535                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m_unchecked(s)?,
536                    ))
537                }
538                /// HRP-validated Bech32m decode accepting strings up to `C` characters.
539                #[inline]
540                pub fn try_from_bech32m_sized<const C: usize>(s: &str, expected_hrp: &str)
541                    -> ::core::result::Result<Self, $crate::Bech32Error> {
542                    ::core::result::Result::Ok(Self(
543                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m_sized::<C>(s, expected_hrp)?,
544                    ))
545                }
546                /// Bech32m decode without HRP validation, accepting strings up to `C` characters.
547                #[inline]
548                pub fn try_from_bech32m_unchecked_sized<const C: usize>(s: &str)
549                    -> ::core::result::Result<Self, $crate::Bech32Error> {
550                    ::core::result::Result::Ok(Self(
551                        <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m_unchecked_sized::<C>(s)?,
552                    ))
553                }
554            }
555            impl $crate::ToBech32m for $name {
556                #[inline]
557                fn try_to_bech32m(
558                    &self,
559                    hrp: &str,
560                    case: $crate::Case,
561                ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
562                    $crate::ToBech32m::try_to_bech32m(&self.0, hrp, case)
563                }
564                #[inline]
565                fn try_to_bech32m_sized<const C: usize>(
566                    &self,
567                    hrp: &str,
568                    case: $crate::Case,
569                ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
570                    $crate::ToBech32m::try_to_bech32m_sized::<C>(&self.0, hrp, case)
571                }
572            }
573        }
574
575        $crate::__sg_if_std! {
576            impl ::std::io::Write for $name {
577                #[inline]
578                fn write(&mut self, buf: &[u8]) -> ::std::io::Result<usize> {
579                    ::std::io::Write::write(&mut self.0, buf)
580                }
581                #[inline]
582                fn flush(&mut self) -> ::std::io::Result<()> { ::std::io::Write::flush(&mut self.0) }
583            }
584            impl $name {
585                /// Zeroizing reader over the secret bytes.
586                #[inline]
587                pub fn as_reader(&self) -> $crate::DynamicReader<'_> { self.0.as_reader() }
588            }
589        }
590    };
591
592    // ---- explicit generic arm: caller opts in to the reduced API ----
593    //
594    // `String` and `Vec<u8>` above are matched as literal tokens, so a type
595    // alias (`type MyStr = String`) does not reach them. Rather than let such
596    // an input fall through and silently lose the shaped API, the fallback
597    // requires the `generic` marker; anything else hits the catch-all below.
598    ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty) => {
599        $crate::dynamic_newtype!($(#[$attr])* $vis $name, generic $inner, derive: []);
600    };
601    ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty, derive: [$($opt:ident),* $(,)?]) => {
602        $crate::__sg_newtype_base!(
603            $(#[$attr])* $vis $name($crate::Dynamic<$inner>), derive: [$($opt),*]
604        );
605        $crate::__sg_dynamic_ctor!($name, $inner);
606    };
607
608    // ---- catch-all: unrecognised inner type ----
609    //
610    // Uses `$($rest:tt)+` rather than `$inner:ty` so it can never itself
611    // hard-error on a malformed tail; the message names what was written.
612    ($(#[$attr:meta])* $vis:vis $name:ident, $($rest:tt)+) => {
613        ::core::compile_error!(::core::concat!(
614            "dynamic_newtype!: `",
615            ::core::stringify!($($rest)+),
616            "` is not one of the shaped inner types. `String` and `Vec<u8>` are \
617             matched as literal tokens, so a type alias (e.g. `type MyStr = String`) \
618             or a fully-qualified path (e.g. `alloc::vec::Vec<u8>`) does NOT match \
619             them. If what you wrote IS a `String` or a `Vec<u8>` under another \
620             spelling, write the literal token to get the full API for that shape — \
621             do NOT reach for `generic` to silence this, because the macro cannot \
622             see through a path or an alias either, and you would land on the \
623             reduced arm with the growable payload and none of the methods that \
624             handle it safely. `generic <type>` is for an inner type that is \
625             genuinely neither `String` nor `Vec<u8>`; it gives RevealSecret, \
626             RevealSecretMut, Debug, Zeroize, and `new`."
627        ));
628    };
629}
630
631/// Rejects `generic` applied to a shape that has its own arm.
632///
633/// One helper rather than the message inline in eight arms, so the wording
634/// cannot drift between the doc-string and `derive:` tails.
635#[doc(hidden)]
636#[macro_export]
637macro_rules! __sg_dynamic_generic_is_shaped {
638    (@bytes) => {
639        ::core::compile_error!(
640            "dynamic_newtype!: write `Vec<u8>`, not `generic Vec<u8>`. The `generic` \
641             arm emits only the shape-independent surface, so this spelling withholds \
642             `SecretLen`, `From<&[u8]>`, `new_with`, `try_new_with`, the encoders, \
643             `from_random`, and — under `std` — the `std::io::Write` impl, which is \
644             the one growth path that wipes the buffer it abandons. It is the same \
645             growable payload with strictly less API and no safe way to grow it. \
646             `generic` is for inner types that are neither `String` nor `Vec<u8>`."
647        );
648    };
649    (@string) => {
650        ::core::compile_error!(
651            "dynamic_newtype!: write `String`, not `generic String`. The `generic` arm \
652             emits only the shape-independent surface, so this spelling withholds \
653             `SecretLen` and `From<&str>`. It is the same payload with strictly less \
654             API. `generic` is for inner types that are neither `String` nor \
655             `Vec<u8>`."
656        );
657    };
658}
659
660#[doc(hidden)]
661#[macro_export]
662macro_rules! __sg_dynamic_ctor {
663    // Shaped arms: accept anything convertible to the inner value, so
664    // `Name::new("literal")` and `Name::new(string)` both work — the shape a
665    // hand-written `new(impl Into<String>)` has, so call sites need not move.
666    (@into $name:ident, $inner:ty) => {
667        impl $name {
668            /// Creates the secret from any value convertible into the inner type.
669            #[inline(always)]
670            pub fn new(value: impl ::core::convert::Into<$inner>) -> Self {
671                Self(<$crate::Dynamic<$inner>>::new(value.into()))
672            }
673        }
674    };
675    ($name:ident, $inner:ty) => {
676        impl $name {
677            /// Moves a value onto the heap as this secret type.
678            #[inline(always)]
679            pub fn new<U>(value: U) -> Self
680            where
681                U: ::core::convert::Into<$crate::__private::Box<$inner>>,
682            {
683                Self(<$crate::Dynamic<$inner>>::new(value))
684            }
685        }
686    };
687}