secure_gate/macros/fixed_newtype.rs
1//! `fixed_newtype!` — nominal newtype over `Fixed<[u8; N]>`.
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 [`Fixed<[u8; N]>`](crate::Fixed).
8///
9/// Generates a `struct` rather than a `type` alias: two `fixed_newtype!` types
10/// of the same `N` are **not** interchangeable, where two `type` aliases of
11/// that `N` are one and the same type. Use it when distinct cryptographic
12/// roles share a shape — an encryption key and a MAC key are both
13/// `Fixed<[u8; 32]>`, and under a plain alias the compiler cannot tell them
14/// apart.
15///
16/// # Syntax
17///
18/// ```text
19/// fixed_newtype!(pub Name, N); // public, auto-generated doc
20/// fixed_newtype!(pub(crate) Name, N); // crate-visible
21/// fixed_newtype!(Name, N); // private
22/// fixed_newtype!(pub Name, N, "doc string"); // with custom doc
23/// fixed_newtype!(pub Name, N, derive: [FromWrapper]); // with opt-in impls
24/// fixed_newtype!(pub Name, N, "doc", derive: [FromWrapper]);
25/// fixed_newtype!(pub Name, generic [T; N]); // array: adds SecretLen
26/// fixed_newtype!(pub Name, generic T); // reduced API, opted into
27/// fixed_newtype!(pub Name, generic T, "doc string");
28/// fixed_newtype!(pub Name, generic T, derive: [WrapperAccess]);
29/// fixed_newtype!(pub Name, generic T, "doc", derive: [WrapperAccess]);
30/// ```
31///
32/// Supported `derive:` options are `Deserialize`, `FromWrapper`, `IntoWrapper`, and `WrapperAccess` (= both directions), plus `ConstantTimeEq` **on the `generic` arms only**. `Deserialize` and `ConstantTimeEq` constrain the inner type — it must implement them, which rules them out for most `generic` inner types (`[i16; 256]` has neither) — while the three wrapper-access tokens apply to any. See
33/// *Cloning and serialization* below for the two that are deliberately absent.
34///
35/// **`ConstantTimeEq` is not an option on the size-literal arm, and naming it is an
36/// error.** A `[u8; N]` payload gets [`ct_eq`](crate::ConstantTimeEq::ct_eq)
37/// automatically whenever the `ct-eq` feature is on, because the wrapper it forwards
38/// to always has it and a plain `type Name = Fixed<[u8; N]>;` therefore always did.
39/// Requiring the token here put the extra word on the spelling this crate recommends
40/// as the safer of the two, for the one comparison whose alternative is a timing
41/// leak. Since the arm now emits the impl itself, a second one from the `derive:`
42/// list is `E0119: conflicting implementations` — delete the token. It remains a
43/// real opt-in on the `generic` arms, where the inner type may or may not implement
44/// the trait.
45///
46/// # Examples
47///
48/// All three visibility forms:
49///
50/// ```rust
51/// use secure_gate::{fixed_newtype, SecretLen};
52///
53/// fixed_newtype!(pub EncKey, 32); // public
54/// fixed_newtype!(pub(crate) HmacKey, 32); // crate-visible
55/// fixed_newtype!(local_nonce, 12); // private
56///
57/// let key = EncKey::new([42u8; 32]);
58/// assert_eq!(key.len(), 32);
59/// ```
60///
61/// Distinct roles do not mix — this is the point:
62///
63/// ```rust,compile_fail
64/// use secure_gate::fixed_newtype;
65///
66/// fixed_newtype!(pub EncKey, 32);
67/// fixed_newtype!(pub MacKey, 32);
68///
69/// fn seal(_enc: &EncKey, _mac: &MacKey) {}
70///
71/// let enc = EncKey::new([1u8; 32]);
72/// let mac = MacKey::new([2u8; 32]);
73/// seal(&mac, &enc); // E0308: roles swapped
74/// ```
75///
76/// Zero-size is a **compile error**, raised where the type is declared rather
77/// than where one is first built:
78///
79/// ```rust,compile_fail
80/// use secure_gate::fixed_newtype;
81/// fixed_newtype!(pub Bad, 0); // compile-time error: index out of bounds
82/// ```
83///
84/// # Inner types other than bytes
85///
86/// The size-literal forms cover `Fixed<[u8; N]>`, the shape nearly every
87/// secret takes. [`Fixed<T>`](crate::Fixed) holds more than that, and the
88/// `generic` marker opts a newtype into it. The requirement is
89/// `Zeroize + `[`SentinelValue`](crate::SentinelValue)` + `[`FixedStorage`](crate::FixedStorage), checked at the
90/// declaration — slightly narrower than `Fixed<T>` itself, which needs only
91/// `Zeroize`, because the generated [`into_inner`](crate::RevealSecret::into_inner)
92/// has to leave an inert value behind. Arrays of a `Default` element, `String`
93/// and `Vec<T>` already satisfy both; a custom inner type needs its own
94/// `impl SentinelValue`, which is why that trait is public:
95///
96/// ```rust
97/// use secure_gate::{fixed_newtype, RevealSecret};
98///
99/// fixed_newtype!(pub Poly, generic [i16; 256]);
100///
101/// let poly = Poly::new([0i16; 256]);
102/// assert_eq!(poly.with_secret(|coeffs| coeffs.len()), 256);
103/// ```
104///
105/// The case this exists for is `no_std`. With no allocator there is no
106/// [`Dynamic`](crate::Dynamic), so `Fixed` is the only wrapper, and a secret on
107/// such a target is frequently not bytes: an ML-KEM secret polynomial
108/// `[i16; 256]`, Ed25519 scalar limbs `[u64; 4]`, AES-256 expanded round keys
109/// `[u32; 60]`. An inner type missing either bound is a compile error reported
110/// from the expansion: `Zeroize` fails against `Fixed<T>`'s own bound, and
111/// `SentinelValue` against the one the generated `RevealSecret` restates.
112/// [`dynamic_newtype!`](crate::dynamic_newtype)'s `generic` arm carries the
113/// same pair, for the same reason.
114///
115/// Every `generic` form emits [`RevealSecret`](crate::RevealSecret),
116/// [`RevealSecretMut`](crate::RevealSecretMut), redacted `Debug`, `Zeroize`,
117/// `ZeroizeOnDrop`, `new`, and [`new_with`](crate::Fixed::new_with). What sits on top
118/// of that depends on how much shape the macro can read off the tokens you wrote, and
119/// there are two answers.
120///
121/// **A literal array — `generic [i16; 256]` — takes the array arm.** The element type
122/// and the length are both still tokens there, so the expansion can see the shape and
123/// emits two more things: [`SecretLen`](crate::SecretLen), and the declaration-site
124/// `N = 0` guard the size-literal arm has always carried. `len()` counts elements and
125/// `byte_len()` multiplies by `size_of::<T>()`, because that is what
126/// `Fixed<[T; N]>`'s own impl computes — 256 and 512 for a polynomial. The arm needs a
127/// *literal* length: `generic [i16; KEY_LEN]` is a different token sequence and falls
128/// through to the opaque arm, the same remainder a path-qualified spelling has.
129///
130/// **A true opaque inner type — `generic Poly`, a struct, an alias — takes the reduced
131/// arm.** `SecretLen` is absent there, and so is the declaration-site guard; a
132/// zero-sized inner value is refused by [`Fixed`](crate::Fixed) at construction
133/// instead. That is a limit on the macro's field of view rather than a verdict on the
134/// type: the base wrapper still implements `SecretLen` for every `Fixed<[T; N]>`, so
135/// the capability is one layer down and `derive: [IntoWrapper]` reaches it in one call.
136/// What the arm withholds is the newtype's *own* `len()`, not the answer.
137///
138/// **`From`, `TryFrom`, the encoders and the RNG constructors are absent from both.**
139/// The encoders have no defined byte order over `[i16]`, and byte order is not the
140/// whole of it: the canonical encoding of these secrets is domain-specific — ML-KEM
141/// serialises as bit-packed 12-bit coefficients, not a 16-bit dump — so an encoder here
142/// would round-trip inside this crate and disagree with every implementation outside
143/// it. RNG is a separate question with a separate answer: the byte-level fill is well
144/// defined, but uniform bits are not a valid ML-KEM coefficient, a reduced scalar, or a
145/// derived round key. It is tracked in #219 rather than settled here.
146///
147/// **`generic [u8; N]` is a compile error**, not a reduced byte array. It is the
148/// same payload as `fixed_newtype!(pub K, N)` with strictly less API, and
149/// [`dynamic_newtype!`](crate::dynamic_newtype) already refuses the analogous
150/// spellings (`generic Vec<u8>`, `generic String`). Write the size literal.
151///
152/// **The inner type must not be able to reallocate.** This arm used to accept
153/// `generic Vec<u8>` and `generic String`, which put a growable buffer inside the
154/// wrapper the documentation calls free of any realloc surface; measured, each
155/// abandoned an unwiped buffer holding the whole secret on any capacity change, and
156/// with no `io::Write` escape of the sort [`Dynamic<Vec<u8>>`](crate::Dynamic) has.
157/// [`Fixed::new`](crate::Fixed::new) now requires
158/// [`FixedStorage`](crate::FixedStorage), so those forms are a compile error reported
159/// at this declaration. Arrays, the primitives, tuples and `Option` are covered for you,
160/// which is every shape this arm exists for; a custom inner type adds
161/// `impl FixedStorage for MyType {}`, which asserts that it owns no heap
162/// allocation. For a growable payload reach for
163/// [`dynamic_newtype!`](crate::dynamic_newtype) instead, where the residue is
164/// documented and one safe growth path exists.
165///
166/// **`new_with` is forwarded.** [`Fixed::new_with`](crate::Fixed::new_with) is an
167/// inherent on every `T: `[`FixedStorage`](crate::FixedStorage)` + `[`SentinelValue`](crate::SentinelValue),
168/// not only `[u8; N]`: the slot starts as `T::sentinel_value()` and the closure
169/// writes into the wrapper's own storage. The generic arm emits it, so a
170/// polynomial does not have to take the secret by value the way `new` does.
171/// Prefer a size-literal newtype when the secret *is* a byte array — that arm
172/// is the full API, and `generic [u8; N]` is refused rather than quietly
173/// reduced.
174///
175/// # Cloning and serialization are not generated
176///
177/// There is no `derive: [Clone]` or `derive: [Serialize]`, by design. Neither
178/// can be forwarded from the wrapper — `Fixed<[u8; N]>: Clone` requires
179/// `[u8; N]: `[`CloneableSecret`](crate::CloneableSecret), which the orphan
180/// rule makes permanently unimplementable downstream (deliberately so). A
181/// generated impl would therefore have to route through `with_secret` and
182/// rebuild, opting the secret into cloning with no marker impl anywhere — a
183/// second door around the opt-in system, spelled in one word inside a macro
184/// expansion. Asking for either is a compile error that says so.
185///
186/// The generated type is local to **your** crate, so write the impl by hand
187/// when you mean it. The decision then lives in your code, where it is
188/// visible, greppable, and reviewable:
189///
190/// ```rust
191/// use secure_gate::{fixed_newtype, Fixed, RevealSecret};
192///
193/// fixed_newtype!(pub SessionKey, 32);
194///
195/// // Deliberate: each clone is an independent copy, zeroized on its own drop.
196/// // Cloning widens the window for memory-extraction attacks — audit each use.
197/// impl Clone for SessionKey {
198/// fn clone(&self) -> Self {
199/// Self::new(self.with_secret(|bytes| *bytes))
200/// }
201/// }
202///
203/// let key = SessionKey::new([7u8; 32]);
204/// let copy = key.clone();
205/// assert_eq!(copy.expose_secret()[0], 7);
206/// ```
207///
208/// The same applies to `Serialize`, with a higher bar: serialization exposes
209/// the full secret, so treat a hand-written impl as a security decision and
210/// see [`SerializableSecret`](crate::SerializableSecret) for the risk notes.
211///
212/// # Implementation Notes
213///
214/// The generated type is `#[repr(transparent)]` over the wrapper, so
215/// `size_of::<EncKey>() == N` and delegation is `#[inline]` throughout — the
216/// newtype costs nothing at runtime.
217///
218/// The size-literal and array expansions both emit
219/// `const _: () = { let _ = [(); N][0]; };`, a declaration-site courtesy: `N = 0`
220/// fails on the line that declares the type, which is the line worth pointing at.
221/// Whether the length arrives as the size argument or inside a literal array, it is
222/// a literal the macro can count at expansion.
223///
224/// It is not the only guard, and the two are not redundant.
225/// [`Fixed::new`](crate::Fixed::new) and
226/// [`Fixed::new_with`](crate::Fixed::new_with) carry a `const` assertion rejecting a
227/// zero-sized inner value at construction, which is what covers a plain `type` alias
228/// and the opaque `generic` arm; there the error falls at the first concrete
229/// construction instead. The macro guard checks a *length*, the wrapper's checks a
230/// *size* — so `generic [(); 4]` passes the first and is caught by the second, which
231/// is exactly the case `tests/compile-fail/fixed_zero_size.rs` pins.
232///
233/// **Do not add your own `Drop` impl.** None is needed: the wrapped
234/// [`Fixed`](crate::Fixed) still runs its own, so zeroization is unaffected.
235/// Adding one makes the inner field unmovable (**E0509**) and silently costs
236/// you [`into_inner`](crate::RevealSecret::into_inner).
237///
238/// # Security
239///
240/// Generated types inherit every [`Fixed`](crate::Fixed) guarantee: zeroize on
241/// drop, `Debug` that always prints `[REDACTED]`, and access only through
242/// [`RevealSecret`](crate::RevealSecret) /
243/// [`RevealSecretMut`](crate::RevealSecretMut). There is no `Deref` — a
244/// generated newtype is not coercible to its wrapper, which is what keeps the
245/// nominal separation total rather than by-value-only.
246///
247/// **Nominal separation guards against mistakes, not intent** — but nothing is
248/// generated that would undo it by accident. There is no `From<Wrapper>` and
249/// no `Deref`, so a base-typed value cannot flow into a newtype through
250/// `.into()`, and `&Newtype` never coerces to `&Wrapper` at a call site. By
251/// default the only way material enters or leaves is the 3-tier access API —
252/// a `with_secret` round trip — which is explicit and shows up in the audit
253/// sweep. Base-wrapper access is opt-in, per newtype, and **split by
254/// direction**: `derive: [FromWrapper]` adds `from_wrapper` (a base value
255/// *enters* this role), `derive: [IntoWrapper]` adds `as_wrapper`,
256/// `as_wrapper_mut`, and `into_wrapper` (material *leaves* this role toward
257/// the base type), and `derive: [WrapperAccess]` is shorthand for both.
258///
259/// These cross a different wall from `into_inner`. A newtype has two:
260///
261/// | Wall | Going in | Going out |
262/// |---|---|---|
263/// | Contents (the wrapper's protection) | `new`, `From<[u8; N]>` | `with_secret`, `expose_secret`, `into_inner` |
264/// | Role (the nominal label) | `from_wrapper` | `as_wrapper`, `as_wrapper_mut`, `into_wrapper` |
265///
266/// `into_inner` leaves the protection: it hands back the plain value, so the
267/// contents are in the caller's hands (tier 3 of the access model, audited).
268/// `into_wrapper` is a **label** drop; `into_inner` is a **protection** drop.
269/// Confusing those two names is the main way this model gets misread. `into_wrapper` only removes the label: the
270/// result is still a `Fixed`, still unreadable without `with_secret`. The
271/// role row exists so that dropping a label never forces opening the
272/// contents — without it, reaching base-typed code costs an `into_inner` plus
273/// a rebuild, a reveal the job never needed.
274///
275/// **Which direction is safe depends on the pool.** In a mixed tree the base
276/// type is not raw material: every plain `type` alias sharing it *is* that
277/// type, so a directional token connects this role to all of them at once.
278/// `FromWrapper` lets anything in the pool become this role — the source never
279/// opts in, because the source is just the base type — so a type that guards
280/// a boundary must never take it. `IntoWrapper` lets this role become anything
281/// in the pool, which is safe only when the role is no more sensitive than
282/// the least-sensitive alias sharing its base; on a secret role it is an
283/// explicit, greppable downgrade, not a neutral operation. The default,
284/// neither token, is sufficient more often than it looks. `IntoWrapper` also
285/// reopens whatever the base wrapper implements for that inner type, which on a
286/// `generic` newtype includes the surface the arm withheld: `as_wrapper().len()`
287/// and `.byte_len()` answer for any `Fixed<[T; N]>`. The reduced surface is a
288/// property of the label, not a barrier. This is now worth granting less often than
289/// it was — a literal array gets its own `len()` from the array arm, so reaching for
290/// the outbound token merely to measure a secret is no longer the trade it used to
291/// be. Where it is still the only route is the opaque arm. The exposure is
292/// largest during a partial migration — the regime real consumers live in —
293/// because while most aliases stay plain the base type is a universal donor.
294/// Audit these methods the way you audit `expose_secret()`.
295///
296/// # See also
297///
298/// - [`dynamic_newtype!`](crate::dynamic_newtype) — heap-allocated counterpart
299/// - a plain `type` alias — `pub type Aes256Key = Fixed<[u8; 32]>;` — when a
300/// readable name rather than role separation is the goal, and the name should
301/// stay interchangeable with its base. Two aliases over one shape are the
302/// same type, so the compiler will not keep two roles apart; that is the
303/// whole of the choice, and the crate's README argues it at length
304#[macro_export]
305macro_rules! fixed_newtype {
306 // ---- `generic` applied to a byte array: rejected ----
307 //
308 // `generic [u8; N]` is strictly dominated by the size-literal arm. It takes
309 // the payload this crate already specialises and then withholds `SecretLen`,
310 // `From`, `TryFrom`, the encoders and `from_random`. Same bytes, strictly
311 // less API, and no reason to write it. These must precede every
312 // `generic $inner:ty` arm: once a `:ty` fragment is parsed there is no
313 // backtracking. They match literal tokens only, so `generic [u8; KEY_LEN]`
314 // with an ident in the length slot is a different sequence and is NOT
315 // caught — the same remainder `dynamic_newtype!` documents for paths.
316 ($(#[$attr:meta])* $vis:vis $name:ident, generic [u8; $n:literal], $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
317 $crate::__sg_fixed_generic_is_bytes!($n);
318 };
319 ($(#[$attr:meta])* $vis:vis $name:ident, generic [u8; $n:literal], $doc:literal) => {
320 $crate::__sg_fixed_generic_is_bytes!($n);
321 };
322 ($(#[$attr:meta])* $vis:vis $name:ident, generic [u8; $n:literal], derive: [$($opt:ident),* $(,)?]) => {
323 $crate::__sg_fixed_generic_is_bytes!($n);
324 };
325 ($(#[$attr:meta])* $vis:vis $name:ident, generic [u8; $n:literal]) => {
326 $crate::__sg_fixed_generic_is_bytes!($n);
327 };
328
329 // ---- `generic` applied to a non-byte array: the shaped array arm ----
330 //
331 // `[i16; 256]` is an array and the macro can see that it is, so it should not
332 // have to pretend it is an opaque struct. The no_std case the `generic` marker
333 // exists for is exactly this shape. This arm emits what is meaningful for any
334 // `[T; N]` and nothing that is not: the declaration-site `N = 0` guard the
335 // size-literal arm already carries, and `SecretLen`, whose answers
336 // `Fixed<[T; N]>` already computes correctly — `len` in elements, `byte_len`
337 // multiplied by `size_of::<T>()`.
338 //
339 // Still withheld: `From`/`TryFrom`, the encoders and the RNG constructors. Hex
340 // over `[i16]` has no defined byte order, and for the structured secrets this
341 // arm exists for the canonical encoding is bit-packed rather than a raw element
342 // dump, so an encoder here would round-trip inside this crate and disagree with
343 // every implementation outside it. RNG is a different question — the byte-level
344 // fill is well defined, but uniform bits are not a valid ML-KEM coefficient, a
345 // reduced scalar, or a derived round key — and is tracked separately in #219.
346 //
347 // One arm, not four. The pattern consumes a `:ty` fragment for the element
348 // type, so two arms both opening `generic [$t:ty; ...` cannot coexist: the
349 // same no-backtracking rule that forces the opaque arm below into the
350 // optional-group form. The `[u8; N]` rejects above still win, because they
351 // precede this arm and match `u8` as a literal token.
352 ($(#[$attr:meta])* $vis:vis $name:ident, generic [$t:ty; $n:literal]
353 $(, $doc:literal)? $(, derive: [$($opt:ident),* $(,)?])?) => {
354 const _: () = { let _ = [(); $n][0]; };
355
356 $crate::__sg_newtype_base!(
357 $(#[$attr])* $(#[doc = $doc])* $vis $name($crate::Fixed<[$t; $n]>),
358 derive: [$($($opt),*)?]
359 );
360 $crate::__sg_newtype_len!($name);
361
362 impl $name {
363 /// Wraps an array in place. `const fn`, like `Fixed::new`.
364 #[inline(always)]
365 pub const fn new(value: [$t; $n]) -> Self {
366 Self($crate::Fixed::new(value))
367 }
368 /// Scoped construction — writes directly into the wrapper's storage.
369 #[inline(always)]
370 pub fn new_with<F>(f: F) -> Self
371 where F: ::core::ops::FnOnce(&mut [$t; $n]) {
372 Self($crate::Fixed::new_with(f))
373 }
374
375 /// Scoped construction from a fill that can fail. On `Err` the partial
376 /// write is zeroized before the error is returned.
377 #[inline(always)]
378 pub fn try_new_with<F, E>(f: F) -> ::core::result::Result<Self, E>
379 where F: ::core::ops::FnOnce(&mut [$t; $n]) -> ::core::result::Result<(), E> {
380 ::core::result::Result::Ok(Self($crate::Fixed::try_new_with(f)?))
381 }
382 }
383 };
384
385 // ---- explicit generic arms: caller opts in to the reduced API ----
386 //
387 // Placed after the array arm. They match the literal token `generic` in the
388 // inner-type position, so they cannot shadow a size literal, and matching the
389 // marker before any `:literal` or `:ty` fragment is parsed keeps the ordering
390 // safe: `macro_rules!` does not backtrack once a fragment has been
391 // consumed. The doc-literal forms get their own arms, exactly as
392 // `dynamic_newtype!`'s generic form does, so that the third positional
393 // argument means the same thing on both macros and on every arm of each.
394 ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty
395 $(, $doc:literal)? $(, derive: [$($opt:ident),* $(,)?])?) => {
396 $crate::__sg_newtype_base!(
397 $(#[$attr])* $(#[doc = $doc])* $vis $name($crate::Fixed<$inner>),
398 derive: [$($($opt),*)?]
399 );
400
401 impl $name {
402 /// Wraps a value in place. `const fn`, like `Fixed::new`.
403 #[inline(always)]
404 pub const fn new(value: $inner) -> Self {
405 Self($crate::Fixed::new(value))
406 }
407 /// Scoped construction — writes directly into the wrapper's storage.
408 #[inline(always)]
409 pub fn new_with<F>(f: F) -> Self
410 where F: ::core::ops::FnOnce(&mut $inner) {
411 Self($crate::Fixed::new_with(f))
412 }
413
414 /// Scoped construction from a fill that can fail. On `Err` the partial
415 /// write is zeroized before the error is returned.
416 #[inline(always)]
417 pub fn try_new_with<F, E>(f: F) -> ::core::result::Result<Self, E>
418 where F: ::core::ops::FnOnce(&mut $inner) -> ::core::result::Result<(), E> {
419 ::core::result::Result::Ok(Self($crate::Fixed::try_new_with(f)?))
420 }
421 }
422 };
423
424 ($(#[$attr:meta])* $vis:vis $name:ident, $size:literal, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
425 $crate::fixed_newtype!($(#[$attr])* #[doc = $doc] $vis $name, $size, derive: [$($opt),*]);
426 };
427 ($(#[$attr:meta])* $vis:vis $name:ident, $size:literal, $doc:literal) => {
428 $crate::fixed_newtype!($(#[$attr])* #[doc = $doc] $vis $name, $size, derive: []);
429 };
430 ($(#[$attr:meta])* $vis:vis $name:ident, $size:literal) => {
431 $crate::fixed_newtype!($(#[$attr])* $vis $name, $size, derive: []);
432 };
433 ($(#[$attr:meta])* $vis:vis $name:ident, $size:literal, derive: [$($opt:ident),* $(,)?]) => {
434 const _: () = { let _ = [(); $size][0]; };
435
436 $crate::__sg_newtype_base!(
437 $(#[$attr])* $vis $name($crate::Fixed<[u8; $size]>), derive: [$($opt),*]
438 );
439 $crate::__sg_newtype_ct_eq!($name);
440 $crate::__sg_newtype_len!($name);
441
442 impl $name {
443 /// Wraps a fixed-size byte array. `const fn`, like `Fixed::new`.
444 #[inline(always)]
445 pub const fn new(value: [u8; $size]) -> Self {
446 Self($crate::Fixed::new(value))
447 }
448 /// Scoped construction — writes directly into the wrapper's storage.
449 #[inline(always)]
450 pub fn new_with<F>(f: F) -> Self
451 where F: ::core::ops::FnOnce(&mut [u8; $size]) {
452 Self($crate::Fixed::new_with(f))
453 }
454
455 /// Scoped construction from a fill that can fail. On `Err` the partial
456 /// write is zeroized before the error is returned.
457 #[inline(always)]
458 pub fn try_new_with<F, E>(f: F) -> ::core::result::Result<Self, E>
459 where F: ::core::ops::FnOnce(&mut [u8; $size]) -> ::core::result::Result<(), E> {
460 ::core::result::Result::Ok(Self($crate::Fixed::try_new_with(f)?))
461 }
462 }
463
464 impl ::core::convert::From<[u8; $size]> for $name {
465 #[inline(always)]
466 fn from(value: [u8; $size]) -> Self { Self($crate::Fixed::new(value)) }
467 }
468
469 impl ::core::convert::TryFrom<&[u8]> for $name {
470 type Error = $crate::FromSliceError;
471 #[inline]
472 fn try_from(slice: &[u8]) -> ::core::result::Result<Self, Self::Error> {
473 ::core::result::Result::Ok(Self(
474 <$crate::Fixed<[u8; $size]> as ::core::convert::TryFrom<&[u8]>>::try_from(slice)?
475 ))
476 }
477 }
478
479 // Lets `sealed_fixed_newtype!` seal this role, and unseal back to it, by moving the
480 // `Fixed` across instead of copying the bytes out by hand.
481 $crate::__sg_if_sealed! {
482 impl $crate::__private::FixedRole for $name {
483 type Fixed = $crate::Fixed<[u8; $size]>;
484 #[inline(always)]
485 fn into_fixed(self) -> Self::Fixed { self.0 }
486 #[inline(always)]
487 fn from_fixed(fixed: Self::Fixed) -> Self { Self(fixed) }
488 }
489 }
490
491 $crate::__sg_if_rand! {
492 impl $name {
493 /// Generates the secret from the system RNG.
494 #[inline]
495 pub fn from_random() -> Self { Self($crate::Fixed::from_random()) }
496
497 /// Generates the secret from a caller-supplied CSPRNG.
498 #[inline]
499 pub fn from_rng<R>(rng: &mut R) -> ::core::result::Result<Self, R::Error>
500 where
501 R: $crate::__private::TryRng + $crate::__private::TryCryptoRng,
502 {
503 ::core::result::Result::Ok(Self($crate::Fixed::from_rng(rng)?))
504 }
505 }
506 }
507
508 $crate::__sg_if_hex! {
509 impl $name {
510 /// Constant-time hex decode into this secret type.
511 #[inline]
512 pub fn try_from_hex(hex: &str) -> ::core::result::Result<Self, $crate::HexError> {
513 ::core::result::Result::Ok(Self($crate::Fixed::try_from_hex(hex)?))
514 }
515 }
516 $crate::__sg_if_alloc! {
517 impl $crate::ToHex for $name {
518 #[inline]
519 fn to_hex(&self) -> $crate::EncodedSecret {
520 $crate::ToHex::to_hex(&self.0)
521 }
522 #[inline]
523 fn to_hex_upper(&self) -> $crate::EncodedSecret {
524 $crate::ToHex::to_hex_upper(&self.0)
525 }
526 }
527 }
528 }
529
530 $crate::__sg_if_base32! {
531 impl $name {
532 /// Constant-time Base32 decode into this secret type.
533 #[inline]
534 pub fn try_from_base32(s: &str) -> ::core::result::Result<Self, $crate::Base32Error> {
535 ::core::result::Result::Ok(Self($crate::Fixed::try_from_base32(s)?))
536 }
537 }
538 $crate::__sg_if_alloc! {
539 impl $crate::ToBase32 for $name {
540 #[inline]
541 fn to_base32(&self) -> $crate::EncodedSecret {
542 $crate::ToBase32::to_base32(&self.0)
543 }
544 }
545 }
546 }
547
548 $crate::__sg_if_base64! {
549 impl $name {
550 /// Constant-time Base64url decode into this secret type.
551 #[inline]
552 pub fn try_from_base64url(s: &str) -> ::core::result::Result<Self, $crate::Base64Error> {
553 ::core::result::Result::Ok(Self($crate::Fixed::try_from_base64url(s)?))
554 }
555 }
556 $crate::__sg_if_alloc! {
557 impl $crate::ToBase64Url for $name {
558 #[inline]
559 fn to_base64url(&self) -> $crate::EncodedSecret {
560 $crate::ToBase64Url::to_base64url(&self.0)
561 }
562 }
563 }
564 }
565
566 $crate::__sg_if_bech32m! {
567 impl $name {
568 /// HRP-validated Bech32m decode into this secret type.
569 #[inline]
570 pub fn try_from_bech32m(s: &str, expected_hrp: &str)
571 -> ::core::result::Result<Self, $crate::Bech32Error> {
572 ::core::result::Result::Ok(Self($crate::Fixed::try_from_bech32m(s, expected_hrp)?))
573 }
574 /// Bech32m decode without HRP validation.
575 #[inline]
576 pub fn try_from_bech32m_unchecked(s: &str)
577 -> ::core::result::Result<Self, $crate::Bech32Error> {
578 ::core::result::Result::Ok(Self($crate::Fixed::try_from_bech32m_unchecked(s)?))
579 }
580 /// HRP-validated Bech32m decode accepting strings up to `C` characters.
581 #[inline]
582 pub fn try_from_bech32m_sized<const C: usize>(s: &str, expected_hrp: &str)
583 -> ::core::result::Result<Self, $crate::Bech32Error> {
584 ::core::result::Result::Ok(Self(
585 $crate::Fixed::try_from_bech32m_sized::<C>(s, expected_hrp)?,
586 ))
587 }
588 /// Bech32m decode without HRP validation, accepting strings up to `C` characters.
589 #[inline]
590 pub fn try_from_bech32m_unchecked_sized<const C: usize>(s: &str)
591 -> ::core::result::Result<Self, $crate::Bech32Error> {
592 ::core::result::Result::Ok(Self(
593 $crate::Fixed::try_from_bech32m_unchecked_sized::<C>(s)?,
594 ))
595 }
596 }
597 $crate::__sg_if_alloc! {
598 impl $crate::ToBech32m for $name {
599 #[inline]
600 fn try_to_bech32m(
601 &self,
602 hrp: &str,
603 case: $crate::Case,
604 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
605 $crate::ToBech32m::try_to_bech32m(&self.0, hrp, case)
606 }
607 #[inline]
608 fn try_to_bech32m_sized<const C: usize>(
609 &self,
610 hrp: &str,
611 case: $crate::Case,
612 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
613 $crate::ToBech32m::try_to_bech32m_sized::<C>(&self.0, hrp, case)
614 }
615 }
616 }
617 }
618
619 $crate::__sg_if_bech32! {
620 $crate::__sg_if_alloc! {
621 impl $crate::ToBech32 for $name {
622 #[inline]
623 fn try_to_bech32(
624 &self,
625 hrp: &str,
626 case: $crate::Case,
627 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
628 $crate::ToBech32::try_to_bech32(&self.0, hrp, case)
629 }
630 #[inline]
631 fn try_to_bech32_sized<const C: usize>(
632 &self,
633 hrp: &str,
634 case: $crate::Case,
635 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
636 $crate::ToBech32::try_to_bech32_sized::<C>(&self.0, hrp, case)
637 }
638 }
639 }
640 impl $name {
641 /// HRP-validated Bech32 decode into this secret type.
642 #[inline]
643 pub fn try_from_bech32(s: &str, expected_hrp: &str)
644 -> ::core::result::Result<Self, $crate::Bech32Error> {
645 ::core::result::Result::Ok(Self($crate::Fixed::try_from_bech32(s, expected_hrp)?))
646 }
647 /// Bech32 decode without HRP validation.
648 #[inline]
649 pub fn try_from_bech32_unchecked(s: &str)
650 -> ::core::result::Result<Self, $crate::Bech32Error> {
651 ::core::result::Result::Ok(Self($crate::Fixed::try_from_bech32_unchecked(s)?))
652 }
653 /// HRP-validated Bech32 decode accepting strings up to `C` characters.
654 #[inline]
655 pub fn try_from_bech32_sized<const C: usize>(s: &str, expected_hrp: &str)
656 -> ::core::result::Result<Self, $crate::Bech32Error> {
657 ::core::result::Result::Ok(Self(
658 $crate::Fixed::try_from_bech32_sized::<C>(s, expected_hrp)?,
659 ))
660 }
661 /// Bech32 decode without HRP validation, accepting strings up to `C` characters.
662 #[inline]
663 pub fn try_from_bech32_unchecked_sized<const C: usize>(s: &str)
664 -> ::core::result::Result<Self, $crate::Bech32Error> {
665 ::core::result::Result::Ok(Self(
666 $crate::Fixed::try_from_bech32_unchecked_sized::<C>(s)?,
667 ))
668 }
669 }
670 }
671 };
672
673 // ---- catch-all: neither a byte size nor the `generic` marker ----
674 //
675 // Uses `$($rest:tt)+` rather than `$inner:ty` so it can never itself
676 // hard-error on a malformed tail; the message names what was written.
677 ($(#[$attr:meta])* $vis:vis $name:ident, $($rest:tt)+) => {
678 ::core::compile_error!(::core::concat!(
679 "fixed_newtype!: no form matches `",
680 ::core::stringify!($($rest)+),
681 "`. The accepted forms are `N`, `N, \"doc\"`, `N, derive: [..]` and \
682 `N, \"doc\", derive: [..]`, where N is the length of the `[u8; N]` \
683 array being wrapped; and the same four with `generic <type>` in place \
684 of N, for an inner type that is not a byte array \
685 (`fixed_newtype!(pub K, generic [i16; 256])`). A literal array there \
686 also gets `SecretLen` and the declaration-site `N = 0` guard; any other \
687 inner type takes the reduced API (RevealSecret, RevealSecretMut, Debug, \
688 Zeroize, `new`, `new_with`) on purpose. `generic [u8; N]` is not one of \
689 those: write the size literal. \
690 If the length or the marker looks right, the problem is in what follows \
691 it: a `derive:` list needs brackets, and accepts only Deserialize, \
692 FromWrapper, IntoWrapper and WrapperAccess — plus ConstantTimeEq on the \
693 `generic` arms, where it is not automatic."
694 ));
695 };
696}
697
698/// Rejects `generic [u8; N]`, which is strictly dominated by the size-literal arm.
699#[doc(hidden)]
700#[macro_export]
701macro_rules! __sg_fixed_generic_is_bytes {
702 ($n:literal) => {
703 ::core::compile_error!(::core::concat!(
704 "fixed_newtype!: write `",
705 ::core::stringify!($n),
706 "`, not `generic [u8; ",
707 ::core::stringify!($n),
708 "]`. The size-literal arm is the full API for a byte array (`new_with`, \
709 `SecretLen`, `From`, `TryFrom`, the encoders, `from_random`). The \
710 `generic` arm emits only the shape-independent surface, so this \
711 spelling is the same payload with strictly less API. `generic` is \
712 for an inner type that is not `[u8; N]`."
713 ));
714 };
715}