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
//! Macros for nominal newtype secret types.
//!
//! [`fixed_newtype!`] and [`dynamic_newtype!`] create domain-specific secret
//! types (e.g., `Aes256Key`, `Password`) that inherit all security guarantees
//! from [`Fixed`](crate::Fixed) or [`Dynamic`](crate::Dynamic): zeroize on
//! drop, redacted `Debug`, explicit access only.
//!
//! # Newtype or plain `type`?
//!
//! A newtype emits a `struct`, so two newtypes of the same shape are
//! **distinct** types and passing an encryption key where a MAC key belongs is
//! a compile error rather than a silent bug. Use these when distinct roles
//! share a shape — the common cases being `Fixed<[u8; 32]>` for two different
//! keys, or `Dynamic<String>` for two different credentials — and where you
//! are unsure, prefer one anyway: it is the safer of the two spellings, and
//! the declaration asks for a visibility, a name, a size (or an inner type)
//! and a doc string, with no `Fixed` / `Dynamic` and no `[u8; N]` to spell.
//!
//! Naming a secret does not need a macro at all. A plain Rust `type` alias
//! gives a readable name whose every guarantee is the wrapper's own, because
//! the named type *is* the wrapper, and it stays interchangeable with its
//! base, so it crosses into APIs you do not own without ceremony. Two such
//! aliases over the same shape (two names for `Fixed<[u8; 32]>`, say) resolve
//! to the **same** nominal type and are freely assignable to each other: an
//! alias is a readability and audit-grep device, not compile-time separation
//! between cryptographic roles.
//!
//! Write a newtype when the compiler should enforce the role, and a `type`
//! when a name is all you want. Earlier releases shipped `fixed_alias!`,
//! `dynamic_alias!`, `fixed_generic_alias!` and `dynamic_generic_alias!`
//! macros that expanded to exactly that one `type` line; they were removed in
//! this release, and
//! [CHANGELOG.md](https://github.com/Slurp9187/secure-gate/blob/main/CHANGELOG.md)
//! carries the reasoning and a migration table.
//!
//! | Written as | Generates | Nominal? | Feature |
//! |--------------------------------|-------------------------------|----------|---------|
//! | [`fixed_newtype!`] | `struct` over `Fixed<[u8; N]>`, or over `Fixed<T>` with `generic T` | **Yes** | Always |
//! | [`dynamic_newtype!`] | `struct` over `Dynamic<T>`, or the same with `generic T` | **Yes** | `alloc` |
//! | `type Name = Fixed<[u8; N]>;` | a second name for the wrapper | No | Always |
//! | `type Name = Dynamic<T>;` | a second name for the wrapper | No | `alloc` |
//!
//! The `generic T` form of either macro is for an inner type that is neither a
//! byte array nor a `String` — an `[i16; 256]` polynomial, a `Vec<u32>` of
//! counters — and emits the surface that is meaningful for an arbitrary `T`
//! (`RevealSecret`, redacted `Debug`, zeroize, `new`, and on `fixed_newtype!`
//! also `new_with`). `fixed_newtype!(.., generic [u8; N])` is a compile error:
//! write the size literal, which is the full API for that payload.
//!
//! On `fixed_newtype!` a *literal* array — `generic [i16; 256]` — gets two things
//! more, because the element type and the length are both still tokens the expansion
//! can read: `SecretLen` (`len` in elements, `byte_len` in bytes) and the
//! declaration-site `N = 0` guard. A name for that array, or a non-literal length,
//! hides the shape again and lands on the reduced arm.
//!
//! # Security note
//!
//! [`fixed_newtype!`] rejects `N = 0` at the declaration — for a size literal and for
//! a literal array alike — and [`Fixed`](crate::Fixed) itself rejects a zero-sized
//! inner value at construction: `Fixed::new` and `Fixed::new_with` carry a `const`
//! assertion, so a plain `type` alias and the opaque `generic T` arm are covered too.
//! The two are complementary, not redundant: the macro counts a length, the wrapper
//! measures a size, and `generic [(); 4]` is caught only by the second.
//! [`dynamic_newtype!`] has no compile-time equivalent, because for `String` and
//! `Vec<u8>` emptiness is a runtime property and an empty one is a legitimate
//! value to hold before validation. The gap that leaves is a statically
//! zero-sized inner type: `Dynamic<Zst>` constructs where `Fixed<Zst>` is now
//! rejected. Validate lengths that come from configuration in your own tests.
//!
//! Newtypes generate no `From<Wrapper>` and no `Deref`, so a base-typed value
//! (any plain `type` alias included) cannot become a newtype through `.into()`
//! and a newtype never coerces back to its base. Base-wrapper access is opt-in
//! per newtype and split by direction: `derive: [FromWrapper]` lets a base
//! value enter the role, `derive: [IntoWrapper]` lets material leave toward
//! the base; `WrapperAccess` is both. In a mixed tree the base type is the
//! pool every plain alias lives in, so `FromWrapper` on a boundary type
//! accepts all of them, and `IntoWrapper` on a secret role downgrades it to
//! the least-sensitive alias sharing its base. Neither token is the sufficient
//! default more often than it looks. Audit these as you would
//! `expose_secret()`.
//!
//! # Example
//!
//! ```rust
//! use secure_gate::{fixed_newtype, Fixed, RevealSecret, SecretLen};
//!
//! // Newtypes — two roles the compiler keeps apart.
//! fixed_newtype!(pub EncKey, 32);
//! fixed_newtype!(pub MacKey, 32);
//!
//! fn seal(_enc: &EncKey, _mac: &MacKey) {}
//! seal(&EncKey::new([1u8; 32]), &MacKey::new([2u8; 32]));
//! // seal(&MacKey::new(..), &EncKey::new(..)) would not compile.
//! assert_eq!(EncKey::new([1u8; 32]).len(), 32);
//!
//! // A plain `type` alias — a readable name for one shape.
//! type Aes256Key = Fixed<[u8; 32]>;
//! let key: Aes256Key = [0u8; 32].into();
//! key.with_secret(|b| assert_eq!(b.len(), 32));
//! ```