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