Skip to main content

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}