Skip to main content

subtle_ng_derive/
lib.rs

1//! Derive macros for [`subtle-ng`](https://docs.rs/subtle-ng/latest/subtle_ng/) traits.
2
3#![warn(missing_docs)]
4#![deny(rustdoc::missing_crate_level_docs)]
5/* Make all doctests fail if they produce any warnings. */
6#![doc(test(attr(deny(warnings))))]
7#![deny(clippy::all)]
8
9use proc_macro::TokenStream;
10use proc_macro2::Span;
11use quote::quote;
12use syn::*;
13
14fn field_names(data: Data) -> Vec<String> {
15  match data {
16    Data::Struct(DataStruct { fields, .. }) => match fields {
17      /* Get the field names as strings. */
18      Fields::Named(FieldsNamed { named, .. }) => named
19        .iter()
20        .map(|Field { ident, .. }| {
21          ident
22            .as_ref()
23            .expect("named fields have idents")
24            .to_string()
25        })
26        .collect(),
27      /* If unnamed, get the indices of the fields as strings (this becomes e.g. `self.0`). */
28      Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => unnamed
29        .iter()
30        .enumerate()
31        .map(|(i, _)| i.to_string())
32        .collect(),
33      /* There are no fields to compare, so every instance is trivially equal. */
34      Fields::Unit => Vec::new(),
35    },
36    _ => panic!("this macro does not support enums or unions for constant-time operations"),
37  }
38}
39
40/// Derive macro for [`subtle_ng::ConstantTimeEq`](https://docs.rs/subtle-ng/latest/subtle_ng/trait.ConstantTimeEq.html).
41///
42///```
43/// use subtle::ConstantTimeEq;
44/// use subtle_ng_derive::ConstantTimeEq;
45///
46/// #[derive(ConstantTimeEq)]
47/// struct S { x: u8, y: u8 }
48/// let s1 = S { x: 0, y: 1 };
49/// let s2 = S { x: 0, y: 2 };
50/// assert_eq!(1, s1.ct_eq(&s1).unwrap_u8());
51/// assert_eq!(1, s2.ct_eq(&s2).unwrap_u8());
52/// assert_eq!(0, s1.ct_eq(&s2).unwrap_u8());
53///
54/// #[derive(ConstantTimeEq)]
55/// struct T(u8, u8);
56/// let t1 = T(0, 1);
57/// let t2 = T(0, 2);
58/// assert_eq!(1, t1.ct_eq(&t1).unwrap_u8());
59/// assert_eq!(1, t2.ct_eq(&t2).unwrap_u8());
60/// assert_eq!(0, t1.ct_eq(&t2).unwrap_u8());
61///```
62#[proc_macro_derive(ConstantTimeEq)]
63pub fn derive_eq(input: TokenStream) -> TokenStream {
64  let DeriveInput {
65    ident,
66    data,
67    generics: Generics {
68      params,
69      where_clause,
70      ..
71    },
72    ..
73  } = parse_macro_input!(input);
74
75  /* Generate the function body of a ct_eq() implementation. */
76  let eq_block = {
77    let field_names = field_names(data);
78    let mut eq_stmts: Vec<Stmt> = vec![parse_str("let mut ret: u8 = 1;").unwrap()];
79    eq_stmts.extend(field_names.into_iter().map(|name| {
80      parse_str(&format!(
81        "ret &= self.{}.ct_eq(&other.{}).unwrap_u8();",
82        name, name
83      ))
84      .unwrap()
85    }));
86    eq_stmts.push(parse_str("return ret.into();").unwrap());
87    Block {
88      brace_token: token::Brace {
89        span: Span::mixed_site(),
90      },
91      stmts: eq_stmts,
92    }
93  };
94
95  /* Insert the ct_eq() block into the quoted trait method. */
96  let output = if cfg!(feature = "with-ng") {
97    quote! {
98      impl ::subtle_ng::ConstantTimeEq for #ident {
99        #[inline]
100        fn ct_eq(&self, other: &Self) -> ::subtle_ng::Choice {
101          #eq_block
102        }
103      }
104    }
105  } else {
106    quote! {
107      impl ::subtle::ConstantTimeEq for #ident {
108        #[inline]
109        fn ct_eq(&self, other: &Self) -> ::subtle::Choice {
110          #eq_block
111        }
112      }
113    }
114  };
115
116  output.into()
117}
118
119/// Implement [`PartialEq`] and [`Eq`] given a [`subtle_ng::ConstantTimeEq`](https://docs.rs/subtle-ng/latest/subtle_ng/trait.ConstantTimeEq.html) implementation.
120///
121///```
122/// use subtle_ng_derive::{ConstantTimeEq, ConstEq};
123///
124/// #[derive(Debug, ConstantTimeEq, ConstEq)]
125/// pub struct S(pub u8);
126///
127/// assert_eq!(S(0), S(0));
128/// assert_ne!(S(0), S(1));
129///```
130#[proc_macro_derive(ConstEq)]
131pub fn derive_eq_impls(input: TokenStream) -> TokenStream {
132  let DeriveInput { ident, .. } = parse_macro_input!(input);
133
134  let output = if cfg!(feature = "with-ng") {
135    quote! {
136      impl PartialEq for #ident {
137        fn eq(&self, other: &Self) -> bool {
138          use ::subtle_ng::ConstantTimeEq;
139          self.ct_eq(other).into()
140        }
141      }
142
143      impl Eq for #ident {}
144    }
145  } else {
146    quote! {
147      impl PartialEq for #ident {
148        fn eq(&self, other: &Self) -> bool {
149          use ::subtle::ConstantTimeEq;
150          self.ct_eq(other).into()
151        }
152      }
153
154      impl Eq for #ident {}
155    }
156  };
157
158  output.into()
159}
160
161/// Derive macro for [`subtle_ng::ConstantTimeGreater`](https://docs.rs/subtle-ng/latest/subtle_ng/trait.ConstantTimeGreater.html).
162///
163///```
164/// use subtle::ConstantTimeGreater;
165/// use subtle_ng_derive::ConstantTimeGreater;
166///
167/// #[derive(ConstantTimeGreater)]
168/// struct S { x: u8, y: u8 }
169/// let s1 = S { x: 0, y: 1 };
170/// let s2 = S { x: 0, y: 2 };
171/// assert_eq!(0, s1.ct_gt(&s1).unwrap_u8());
172/// assert_eq!(1, s2.ct_gt(&s1).unwrap_u8());
173///
174/// #[derive(ConstantTimeGreater)]
175/// struct T(u8, u8);
176/// let t1 = T(0, 1);
177/// let t2 = T(0, 2);
178/// assert_eq!(0, t1.ct_gt(&t1).unwrap_u8());
179/// assert_eq!(1, t2.ct_gt(&t1).unwrap_u8());
180///```
181#[proc_macro_derive(ConstantTimeGreater)]
182pub fn derive_gt(input: TokenStream) -> TokenStream {
183  let DeriveInput { ident, data, .. } = parse_macro_input!(input);
184
185  /* Generate the function body of a ct_gt() implementation. */
186  let gt_block = {
187    let field_names = field_names(data);
188    let mut gt_stmts: Vec<Stmt> = vec![
189      parse_str("let mut still_at_least_eq: u8 = 1;").unwrap(),
190      parse_str("let mut was_gt: u8 = 0;").unwrap(),
191    ];
192    for name in field_names.into_iter() {
193      gt_stmts.push(
194        parse_str(&format!(
195          "was_gt |= still_at_least_eq & self.{}.ct_gt(&other.{}).unwrap_u8();",
196          name, name,
197        ))
198        .unwrap(),
199      );
200      gt_stmts.push(
201        parse_str(&format!(
202          "still_at_least_eq &= self.{}.ct_eq(&other.{}).unwrap_u8();",
203          name, name,
204        ))
205        .unwrap(),
206      );
207    }
208    gt_stmts.push(parse_str("return was_gt.into();").unwrap());
209    Block {
210      brace_token: token::Brace {
211        span: Span::mixed_site(),
212      },
213      stmts: gt_stmts,
214    }
215  };
216
217  /* Insert the ct_gt() block into the quoted trait method. */
218  let output = if cfg!(feature = "with-ng") {
219    quote! {
220      impl ::subtle_ng::ConstantTimeGreater for #ident {
221        #[inline]
222        fn ct_gt(&self, other: &Self) -> ::subtle_ng::Choice {
223          use ::subtle_ng::{ConstantTimeEq, ConstantTimeGreater};
224          #gt_block
225        }
226      }
227
228      impl ::subtle_ng::ConstantTimeLess for #ident {}
229    }
230  } else {
231    quote! {
232      impl ::subtle::ConstantTimeGreater for #ident {
233        #[inline]
234        fn ct_gt(&self, other: &Self) -> ::subtle::Choice {
235          use ::subtle::{ConstantTimeEq, ConstantTimeGreater};
236          #gt_block
237        }
238      }
239
240      impl ::subtle::ConstantTimeLess for #ident {}
241    }
242  };
243
244  output.into()
245}
246
247/// Implement [`PartialOrd`] and [`Ord`] given a [`subtle_ng::ConstantTimeCmp`](https://docs.rs/subtle-ng/latest/subtle_ng/trait.ConstantTimeCmp.html) implementation.
248///
249///```
250/// use subtle_ng_derive::{ConstantTimeEq, ConstantTimeGreater, ConstEq, ConstOrd};
251///
252/// #[derive(Debug, ConstantTimeEq, ConstantTimeGreater, ConstEq, ConstOrd)]
253/// pub struct S(pub u8);
254///
255/// assert_eq!(S(0), S(0));
256/// assert!(S(0) < S(1));
257/// assert!(S(0) <= S(1));
258///```
259#[proc_macro_derive(ConstOrd)]
260pub fn derive_ord_impls(input: TokenStream) -> TokenStream {
261  let DeriveInput { ident, .. } = parse_macro_input!(input);
262
263  let output = if cfg!(feature = "with-ng") {
264    quote! {
265      impl PartialOrd for #ident {
266        fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
267          use ::subtle_ng::ConstantTimePartialOrd;
268          self.ct_partial_cmp(other).into()
269        }
270      }
271
272      impl Ord for #ident {
273        fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
274          use ::subtle_ng::ConstantTimeOrd;
275          self.ct_cmp(other)
276        }
277      }
278    }
279  } else {
280    quote! {
281      impl PartialOrd for #ident {
282        fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
283          use ::subtle::ConstantTimePartialOrd;
284          self.ct_partial_cmp(other).into()
285        }
286      }
287
288      impl Ord for #ident {
289        fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
290          use ::subtle::ConstantTimeOrd;
291          self.ct_cmp(other)
292        }
293      }
294    }
295  };
296
297  output.into()
298}