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