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