1#![warn(missing_docs)]
4#![deny(rustdoc::missing_crate_level_docs)]
5#![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 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 Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => unnamed
29 .iter()
30 .enumerate()
31 .map(|(i, _)| i.to_string())
32 .collect(),
33 Fields::Unit => Vec::new(),
35 },
36 _ => panic!("this macro does not support enums or unions for constant-time operations"),
37 }
38}
39
40#[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 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 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#[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#[proc_macro_derive(ConstantTimeGreater)]
182pub fn derive_gt(input: TokenStream) -> TokenStream {
183 let DeriveInput { ident, data, .. } = parse_macro_input!(input);
184
185 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 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#[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}