-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
1,830 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "constant_time_derive" | ||
version = "0.1.0" | ||
edition = "2021" | ||
license = "Apache-2.0" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
proc-macro2 = "1.0.8" | ||
quote = "1.0" | ||
subtle = { version = "2.4.0", default-features = false } | ||
syn = "1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright (c) 2023 The MobileCoin Foundation | ||
|
||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, GenericParam, Generics}; | ||
|
||
#[proc_macro_derive(ConstantTimeEq)] | ||
pub fn constant_time_eq(input: TokenStream) -> TokenStream { | ||
let input = parse_macro_input!(input as DeriveInput); | ||
derive_ct_eq(&input) | ||
} | ||
|
||
// TODO: Check or remove padding and align decorators on the struct | ||
fn parse_fields(fields: &Fields) -> Result<proc_macro2::TokenStream, &'static str> { | ||
match &fields { | ||
Fields::Named(fields_named) => { | ||
let mut token_stream = quote!(); | ||
let mut iter = fields_named.named.iter().peekable(); | ||
|
||
while let Some(field) = iter.next() { | ||
let ident = &field.ident; | ||
match iter.peek() { | ||
None => token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) }), | ||
Some(_) => { | ||
token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) & }) | ||
} | ||
} | ||
} | ||
Ok(token_stream) | ||
} | ||
Fields::Unnamed(unnamed_fields) => { | ||
let mut token_stream = quote!(); | ||
let mut iter = unnamed_fields.unnamed.iter().peekable(); | ||
let mut idx = 0; | ||
while let Some(_) = iter.next() { | ||
let i = syn::Index::from(idx); | ||
match iter.peek() { | ||
None => token_stream.extend(quote! { {self.#i}.ct_eq(&{other.#i}) }), | ||
Some(_) => { | ||
token_stream.extend(quote! { {self.#i}.ct_eq(&{other.#i}) & }); | ||
idx += 1; | ||
} | ||
} | ||
} | ||
|
||
Ok(token_stream) | ||
} | ||
Fields::Unit => Err("Constant time cannot be derived for unit fields"), | ||
} | ||
} | ||
|
||
fn parse_enum(data_enum: &DataEnum) -> Result<proc_macro2::TokenStream, &'static str> { | ||
for variant in data_enum.variants.iter() { | ||
if let Fields::Unnamed(_) = variant.fields { | ||
panic!("Cannot derive ct_eq for fields in enums") | ||
} | ||
} | ||
let token_stream = quote! { | ||
if self == other { | ||
::subtle::Choice::from(1) | ||
} | ||
else { | ||
::subtle::Choice::from(0) | ||
} | ||
}; | ||
|
||
Ok(token_stream) | ||
} | ||
|
||
fn parse_data(data: &Data) -> Result<proc_macro2::TokenStream, &'static str> { | ||
match data { | ||
Data::Struct(variant_data) => parse_fields(&variant_data.fields), | ||
Data::Enum(data_enum) => parse_enum(data_enum), | ||
Data::Union(..) => Err("Constant time cannot be derived for a union"), | ||
} | ||
} | ||
|
||
fn parse_lifetime(generics: &Generics) -> u32 { | ||
let mut count = 0; | ||
for i in generics.params.iter() { | ||
if let GenericParam::Lifetime(_) = i { | ||
count += 1; | ||
} | ||
} | ||
count | ||
} | ||
|
||
fn derive_ct_eq(input: &DeriveInput) -> TokenStream { | ||
let ident = &input.ident; | ||
let data = &input.data; | ||
let generics = &input.generics; | ||
let is_lifetime = parse_lifetime(generics); | ||
let ct_eq_stream: proc_macro2::TokenStream = | ||
parse_data(data).expect("Failed to parse DeriveInput data"); | ||
let data_ident = if is_lifetime != 0 { | ||
let mut s = format!("{}<'_", ident); | ||
|
||
for _ in 1..is_lifetime { | ||
s.push_str(", '_"); | ||
} | ||
s.push('>'); | ||
|
||
s | ||
} else { | ||
ident.to_string() | ||
}; | ||
let ident_stream: proc_macro2::TokenStream = data_ident.parse().unwrap(); | ||
|
||
let expanded: proc_macro2::TokenStream = quote! { | ||
impl ::subtle::ConstantTimeEq for #ident_stream { | ||
fn ct_eq(&self, other: &Self) -> ::subtle::Choice { | ||
use ::subtle::ConstantTimeEq; | ||
return #ct_eq_stream | ||
} | ||
} | ||
}; | ||
|
||
expanded.into() | ||
} |
Oops, something went wrong.