Skip to content

Commit 1baa58e

Browse files
committed
proc derive macro for ConstantTimeEq
1 parent 6b6a81a commit 1baa58e

File tree

8 files changed

+669
-2
lines changed

8 files changed

+669
-2
lines changed

Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
[workspace]
2+
members = [
3+
"subtle-derive"
4+
]
5+
16
[package]
27
name = "subtle"
38
# Before incrementing:
@@ -25,13 +30,17 @@ exclude = [
2530
[badges]
2631
travis-ci = { repository = "dalek-cryptography/subtle", branch = "master"}
2732

33+
[dependencies]
34+
subtle-derive = { version = "0.1.0", optional = true, path = "subtle-derive" }
35+
2836
[dev-dependencies]
2937
rand = { version = "0.8" }
3038

3139
[features]
3240
const-generics = []
3341
core_hint_black_box = []
3442
default = ["std", "i128"]
43+
derive = ["subtle-derive"]
3544
std = []
3645
i128 = []
3746
# DEPRECATED: As of 2.4.1, this feature does nothing.

README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,38 @@ barrier ([`core::hint::black_box`]). To use the new optimization barrier,
3131
enable the `core_hint_black_box` feature.
3232

3333
Rust versions from 1.51 or higher have const generics support. You may enable
34-
`const-generics` feautre to have `subtle` traits implemented for arrays `[T; N]`.
34+
`const-generics` feature to have `subtle` traits implemented for arrays `[T; N]`.
3535

3636
Versions prior to `2.2` recommended use of the `nightly` feature to enable an
3737
optimization barrier; this is not required in versions `2.2` and above.
3838

39+
Enable `derive` feature to generate implementations for traits using procedural
40+
macros in [`subtle-derive`].
41+
42+
```toml
43+
subtle = { version = "2.6", features = ["derive"] }
44+
```
45+
46+
## Example
47+
48+
```ignore
49+
use subtle::ConstantTimeEq;
50+
51+
#[derive(ConstantTimeEq)]
52+
struct MyStruct {
53+
data: [u8; 16]
54+
}
55+
56+
57+
fn main() {
58+
let first = MyStruct { data: [1u8;16]};
59+
let second = MyStruct { data: [1u8;16]};
60+
61+
assert!(bool::from(first.ct_eq(&second)));
62+
}
63+
```
64+
65+
3966
Note: the `subtle` crate contains `debug_assert`s to check invariants during
4067
debug builds. These invariant checks involve secret-dependent branches, and
4168
are not present when compiled in release mode. This crate is intended to be
@@ -80,3 +107,4 @@ effort is fundamentally limited.
80107
[docs]: https://docs.rs/subtle
81108
[`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html
82109
[rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security
110+
[`subtle-derive`]: https://crates.io/crates/subtle-derive

src/lib.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,37 @@
4646
//! enable the `core_hint_black_box` feature.
4747
//!
4848
//! Rust versions from 1.51 or higher have const generics support. You may enable
49-
//! `const-generics` feautre to have `subtle` traits implemented for arrays `[T; N]`.
49+
//! `const-generics` feature to have `subtle` traits implemented for arrays `[T; N]`.
5050
//!
5151
//! Versions prior to `2.2` recommended use of the `nightly` feature to enable an
5252
//! optimization barrier; this is not required in versions `2.2` and above.
5353
//!
54+
//! Enable `derive` feature to generate implementations for traits using procedural macros
55+
//! in [`subtle-derive`].
56+
//!
57+
//! ```toml
58+
//! subtle = { version = "2.6", features = ["derive"] }
59+
//! ```
60+
//!
61+
//! ## Example
62+
//!
63+
//! ```ignore
64+
//! use subtle::ConstantTimeEq;
65+
//!
66+
//! #[derive(ConstantTimeEq)]
67+
//! struct MyStruct {
68+
//! data: [u8; 16]
69+
//! }
70+
//!
71+
//! fn main() {
72+
//! use subtle::ConstantTimeEq;
73+
//! let first = MyStruct { data: [1u8;16]};
74+
//! let second = MyStruct { data: [1u8;16]};
75+
//!
76+
//! assert!(bool::from(first.ct_eq(&second)));
77+
//! }
78+
//! ```
79+
//!
5480
//! Note: the `subtle` crate contains `debug_assert`s to check invariants during
5581
//! debug builds. These invariant checks involve secret-dependent branches, and
5682
//! are not present when compiled in release mode. This crate is intended to be
@@ -95,15 +121,23 @@
95121
//! [docs]: https://docs.rs/subtle
96122
//! [`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html
97123
//! [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security
124+
//! [`subtle-derive`]: https://crates.io/crates/subtle-derive
98125
99126
#[cfg(feature = "std")]
100127
#[macro_use]
101128
extern crate std;
102129

130+
#[cfg(feature = "subtle-derive")]
131+
#[macro_use]
132+
extern crate subtle_derive;
133+
103134
use core::cmp;
104135
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
105136
use core::option::Option;
106137

138+
#[cfg(feature = "subtle-derive")]
139+
pub use subtle_derive::ConstantTimeEq;
140+
107141
/// The `Choice` struct represents a choice for use in conditional assignment.
108142
///
109143
/// It is a wrapper around a `u8`, which should have the value either `1` (true)

subtle-derive/Cargo.toml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "subtle-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["Varsha Jayadev <varsha@mobilecoin.com>"]
6+
readme = "README.md"
7+
license = "BSD-3-Clause"
8+
repository = "https://github.com/dalek-cryptography/subtle"
9+
homepage = "https://dalek.rs/"
10+
documentation = "https://docs.rs/crate/subtle-derive"
11+
categories = ["cryptography", "no-std"]
12+
keywords = ["cryptography", "crypto", "constant-time", "utilities"]
13+
description = "Macros implementation of #[derive(ConstantTimeEq)]"
14+
15+
[features]
16+
default = []
17+
18+
[lib]
19+
proc-macro = true
20+
21+
[dependencies]
22+
proc-macro2 = "1.0.8"
23+
quote = "1.0"
24+
syn = "2.0.15"

subtle-derive/LICENSE

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions are
5+
met:
6+
7+
1. Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
14+
3. Neither the name of the copyright holder nor the names of its
15+
contributors may be used to endorse or promote products derived from
16+
this software without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20+
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24+
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

subtle-derive/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# subtle
2+
3+
[![Crates.io][crate-image]][crate-link]<!--
4+
-->[![Docs Status][docs-image]][docs-link]
5+
6+
**Procedural macros for deriving [subtle] trait implementations.**
7+
8+
Derive macro implemented for traits:
9+
- [x] ConstantTimeEq
10+
- [ ] ConstantTimeGreater
11+
- [ ] ConstantTimeLesser
12+
13+
## Documentation
14+
15+
Documentation is available [here][subtle-docs].
16+
17+
# Installation
18+
To install, add the following to the dependencies section of your project's `Cargo.toml`:
19+
20+
```toml
21+
subtle = { version = "2.6", features = ["derive"] }
22+
```
23+
24+
[crate-image]: https://img.shields.io/crates/v/subtle-derive?style=flat-square
25+
[crate-link]: https://crates.io/crates/subtle-derive
26+
[docs-image]: https://img.shields.io/docsrs/subtle-derive?style=flat-square
27+
[docs-link]: https://docs.rs/crate/subtle-derive
28+
[subtle]: https://crates.io/crates/subtle
29+
[subtle-docs]: https://docs.rs/subtle

subtle-derive/src/lib.rs

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) 2023 The MobileCoin Foundation
2+
3+
//! # subtle
4+
//!
5+
//! [![Crates.io][crate-image]][crate-link]<!--
6+
//! -->[![Docs Status][docs-image]][docs-link]
7+
//!
8+
//! **Procedural macros for deriving [subtle] trait implementations.**
9+
//!
10+
//! Derive macro implemented for traits:
11+
//! - [x] ConstantTimeEq
12+
//! - [ ] ConstantTimeGreater
13+
//! - [ ] ConstantTimeLesser
14+
//!
15+
//! ## Documentation
16+
//!
17+
//! Documentation is available [here][subtle-docs].
18+
//!
19+
//! # Installation
20+
//! To install, add the following to the dependencies section of your project's `Cargo.toml`:
21+
//!
22+
//! ```toml
23+
//! subtle = { version = "2.6", features = ["derive"] }
24+
//! ```
25+
//!
26+
//! [crate-image]: https://img.shields.io/crates/v/subtle-derive?style=flat-square
27+
//! [crate-link]: https://crates.io/crates/subtle-derive
28+
//! [docs-image]: https://img.shields.io/docsrs/subtle-derive?style=flat-square
29+
//! [docs-link]: https://docs.rs/crate/subtle-derive
30+
//! [subtle]: https://crates.io/crates/subtle
31+
//! [subtle-docs]: https://docs.rs/subtle
32+
33+
use proc_macro::TokenStream;
34+
use quote::quote;
35+
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, GenericParam, Generics};
36+
37+
#[proc_macro_derive(ConstantTimeEq)]
38+
pub fn constant_time_eq(input: TokenStream) -> TokenStream {
39+
let input = parse_macro_input!(input as DeriveInput);
40+
derive_ct_eq(&input)
41+
}
42+
43+
44+
fn parse_fields(fields: &Fields) -> Result<proc_macro2::TokenStream, &'static str> {
45+
match &fields {
46+
Fields::Named(fields_named) => {
47+
let mut token_stream = quote!();
48+
let mut iter = fields_named.named.iter().peekable();
49+
50+
while let Some(field) = iter.next() {
51+
let ident = &field.ident;
52+
match iter.peek() {
53+
None => token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) }),
54+
Some(_) => {
55+
token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) & })
56+
}
57+
}
58+
}
59+
Ok(token_stream)
60+
}
61+
Fields::Unnamed(unnamed_fields) => {
62+
let mut token_stream = quote!();
63+
let mut iter = unnamed_fields.unnamed.iter().peekable();
64+
let mut idx = 0;
65+
while let Some(_) = iter.next() {
66+
let i = syn::Index::from(idx);
67+
match iter.peek() {
68+
None => token_stream.extend(quote! { {self.#i}.ct_eq(&{other.#i}) }),
69+
Some(_) => {
70+
token_stream.extend(quote! { {self.#i}.ct_eq(&{other.#i}) & });
71+
idx += 1;
72+
}
73+
}
74+
}
75+
76+
Ok(token_stream)
77+
}
78+
Fields::Unit => Err("Constant time cannot be derived for unit fields"),
79+
}
80+
}
81+
82+
fn parse_enum(data_enum: &DataEnum) -> Result<proc_macro2::TokenStream, &'static str> {
83+
for variant in data_enum.variants.iter() {
84+
if let Fields::Unnamed(_) = variant.fields {
85+
panic!("Cannot derive ct_eq for fields in enums")
86+
}
87+
}
88+
let token_stream = quote! {
89+
::subtle::Choice::from((self == other) as u8)
90+
};
91+
92+
Ok(token_stream)
93+
}
94+
95+
fn parse_data(data: &Data) -> Result<proc_macro2::TokenStream, &'static str> {
96+
match data {
97+
Data::Struct(variant_data) => parse_fields(&variant_data.fields),
98+
Data::Enum(data_enum) => parse_enum(data_enum),
99+
Data::Union(..) => Err("Constant time cannot be derived for a union"),
100+
}
101+
}
102+
103+
fn parse_lifetime(generics: &Generics) -> u32 {
104+
let mut count = 0;
105+
for i in generics.params.iter() {
106+
if let GenericParam::Lifetime(_) = i {
107+
count += 1;
108+
}
109+
}
110+
count
111+
}
112+
113+
fn derive_ct_eq(input: &DeriveInput) -> TokenStream {
114+
let ident = &input.ident;
115+
let data = &input.data;
116+
let generics = &input.generics;
117+
let is_lifetime = parse_lifetime(generics);
118+
let ct_eq_stream: proc_macro2::TokenStream =
119+
parse_data(data).expect("Failed to parse DeriveInput data");
120+
let data_ident = if is_lifetime != 0 {
121+
let mut s = format!("{}<'_", ident);
122+
123+
for _ in 1..is_lifetime {
124+
s.push_str(", '_");
125+
}
126+
s.push('>');
127+
128+
s
129+
} else {
130+
ident.to_string()
131+
};
132+
let ident_stream: proc_macro2::TokenStream =
133+
data_ident.parse().expect("Should be valid lifetime tokens");
134+
135+
let expanded: proc_macro2::TokenStream = quote! {
136+
impl ::subtle::ConstantTimeEq for #ident_stream {
137+
fn ct_eq(&self, other: &Self) -> ::subtle::Choice {
138+
use ::subtle::ConstantTimeEq;
139+
return #ct_eq_stream
140+
}
141+
}
142+
};
143+
144+
expanded.into()
145+
}

0 commit comments

Comments
 (0)