const_num_traits/personality.rs
1//! Personality typestate: a zero-size marker that selects which implementation
2//! of an operation is picked at monomorphization.
3//!
4//! Two personalities ship here:
5//!
6//! - [`Nct`] (default): standard "non-constant-time" implementation.
7//! - [`Ct`]: constant-time implementation. Slower bodies whose timing is
8//! independent of operand values, defensible against an adversarial
9//! optimizer. Used by signing paths and any code handling secret values.
10//!
11//! This is a pure-`core` marker (it depends only on [`PhantomData`]); it is
12//! deliberately **not** gated behind the `ct` feature, which governs only the
13//! `subtle`-backed constant-time *implementations*. It is intended to apply to
14//! this crate's numeric operations, but nothing about the marker itself is
15//! numeric — if a non-numeric consumer ever needs it, lifting it into its own
16//! crate (re-exported here) is mechanical.
17//!
18//! ## Const-eval dispatch via `match P::TAG`
19//!
20//! Composite `const fn` methods that need to dispatch on personality can
21//! `match` on the [`PersonalityTag`] associated constant:
22//!
23//! ```ignore
24//! const fn dispatched<P: Personality>(x: u32) -> u32 {
25//! match P::TAG {
26//! PersonalityTag::Nct => fast_body(x),
27//! PersonalityTag::Ct => ct_body(x),
28//! }
29//! }
30//! ```
31//!
32//! Trait *method* calls (e.g. `P::widening_mul(x, y)`) require unstable
33//! `const_trait_impl` in const contexts and are not used by this pattern. Tag
34//! dispatch works on stable today and composes through multi-level generic
35//! `const fn` chains without loss of const-eval.
36
37use core::marker::PhantomData;
38
39mod sealed {
40 pub trait Sealed {}
41}
42
43/// Marker trait for personalities. Sealed — implementations live in this
44/// crate. Downstream code uses the [`Nct`] and [`Ct`] types directly or
45/// writes `match P::TAG` dispatch using [`PersonalityTag`].
46pub trait Personality: sealed::Sealed + Copy + 'static {
47 /// Compile-time tag used by `const fn` dispatch:
48 /// `match P::TAG { PersonalityTag::Nct => …, PersonalityTag::Ct => … }`.
49 const TAG: PersonalityTag;
50}
51
52/// Compile-time tag identifying the active personality. Returned by
53/// [`Personality::TAG`] and used as the discriminant for `match P::TAG`
54/// dispatch in `const fn` bodies.
55#[derive(Copy, Clone, PartialEq, Eq, Debug)]
56pub enum PersonalityTag {
57 /// Non-constant-time.
58 Nct,
59 /// Constant-time. Defensible against optimizer-introduced timing leaks.
60 Ct,
61}
62
63/// Non-constant-time marker. The default personality.
64#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
65pub struct Nct;
66impl sealed::Sealed for Nct {}
67impl Personality for Nct {
68 const TAG: PersonalityTag = PersonalityTag::Nct;
69}
70
71/// Constant-time marker.
72///
73/// Selects constant-time implementations of operation primitives — bodies
74/// whose execution time and memory access pattern do not depend on operand
75/// values. Appropriate for code that handles secret values, where
76/// operand-dependent timing or memory access would leak them.
77///
78/// Constant-time-only APIs (e.g. `subtle::ConditionallySelectable`,
79/// `ConstantTimeEq`) should be exposed only for the `Ct` variant — wrong-
80/// variant calls then become compile errors, not silent non-constant-time
81/// execution.
82#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
83pub struct Ct;
84impl sealed::Sealed for Ct {}
85impl Personality for Ct {
86 const TAG: PersonalityTag = PersonalityTag::Ct;
87}
88
89/// `PhantomData` helper for storing a personality marker as a zero-size field.
90/// Use as `_p: PersonalityMarker<P>` in struct definitions.
91pub type PersonalityMarker<P> = PhantomData<fn() -> P>;
92
93/// Projects a carrier type's [`Personality`] at the type level.
94///
95/// Implemented by carrier types whose implementation shape is selected
96/// by a personality parameter (a bigint parameterized over its
97/// personality projects that parameter), and by the primitive integers,
98/// which project [`Nct`]: they expose a single hardware-backed
99/// implementation, select no constant-time variant, and make no
100/// constant-time guarantee — integer division in particular has
101/// operand-dependent latency on common CPUs.
102///
103/// Consumers bound on the projection to gate algorithm choice by
104/// personality: `T: HasPersonality<P = Nct>` admits a type into
105/// variable-time code paths, `P = Ct` into constant-time-only ones.
106/// The declaration is the carrier author's contract; there is no way
107/// to verify it structurally.
108pub trait HasPersonality {
109 /// The carrier's personality.
110 type P: Personality;
111}
112
113macro_rules! impl_has_personality_nct {
114 ($($t:ty),*) => {
115 $(
116 impl HasPersonality for $t {
117 type P = Nct;
118 }
119 )*
120 };
121}
122
123impl_has_personality_nct!(
124 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
125);
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn tags_and_defaults() {
133 assert_eq!(Nct::TAG, PersonalityTag::Nct);
134 assert_eq!(Ct::TAG, PersonalityTag::Ct);
135 assert_eq!(Nct, Nct);
136 assert_eq!(Ct, Ct);
137 }
138
139 #[test]
140 fn has_personality_projects_nct_for_primitives() {
141 fn assert_nct<T: HasPersonality<P = Nct>>() {}
142 assert_nct::<u8>();
143 assert_nct::<u32>();
144 assert_nct::<u128>();
145 assert_nct::<usize>();
146 assert_nct::<i64>();
147 }
148
149 #[test]
150 fn const_tag_dispatch() {
151 const fn dispatched<P: Personality>() -> u32 {
152 match P::TAG {
153 PersonalityTag::Nct => 1,
154 PersonalityTag::Ct => 2,
155 }
156 }
157 assert_eq!(dispatched::<Nct>(), 1);
158 assert_eq!(dispatched::<Ct>(), 2);
159 }
160}