Skip to content

Commit 58af68e

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

File tree

8 files changed

+662
-2
lines changed

8 files changed

+662
-2
lines changed

Cargo.toml

Lines changed: 9 additions & 0 deletions
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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@ 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+
3942
Note: the `subtle` crate contains `debug_assert`s to check invariants during
4043
debug builds. These invariant checks involve secret-dependent branches, and
4144
are not present when compiled in release mode. This crate is intended to be
@@ -80,3 +83,4 @@ effort is fundamentally limited.
8083
[docs]: https://docs.rs/subtle
8184
[`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html
8285
[rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security
86+
[`subtle-derive`]: https://crates.io/crates/subtle-derive

src/lib.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@
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+
//!
5457
//! Note: the `subtle` crate contains `debug_assert`s to check invariants during
5558
//! debug builds. These invariant checks involve secret-dependent branches, and
5659
//! are not present when compiled in release mode. This crate is intended to be
@@ -95,15 +98,23 @@
9598
//! [docs]: https://docs.rs/subtle
9699
//! [`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html
97100
//! [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security
101+
//! [`subtle-derive`]: https://crates.io/crates/subtle-derive
98102
99103
#[cfg(feature = "std")]
100104
#[macro_use]
101105
extern crate std;
102106

107+
#[cfg(feature = "subtle-derive")]
108+
#[macro_use]
109+
extern crate subtle_derive;
110+
103111
use core::cmp;
104112
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
105113
use core::option::Option;
106114

115+
#[cfg(feature = "subtle-derive")]
116+
pub use subtle_derive::ConstantTimeEq;
117+
107118
/// The `Choice` struct represents a choice for use in conditional assignment.
108119
///
109120
/// It is a wrapper around a `u8`, which should have the value either `1` (true)

subtle-derive/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
subtle = { version = "2.4.0", default-features = false }
25+
syn = "2.0.15"

subtle-derive/LICENSE

Lines changed: 28 additions & 0 deletions
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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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][docs-link].
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+
## Example
25+
26+
```rust
27+
use subtle::ConstantTimeEq;
28+
29+
#[derive(ConstantTimeEq)]
30+
struct MyStruct {
31+
data: [u8; 16]
32+
}
33+
34+
35+
fn main() {
36+
let first = MyStruct { data: [1u8;16]};
37+
let second = MyStruct { data: [1u8;16]};
38+
39+
assert!(bool::from(first.ct_eq(&second)));
40+
}
41+
```
42+
43+
[crate-image]: https://img.shields.io/crates/v/subtle-derive?style=flat-square
44+
[crate-link]: https://crates.io/crates/subtle-derive
45+
[docs-image]: https://img.shields.io/docsrs/subtle-derive?style=flat-square
46+
[docs-link]: https://docs.rs/crate/subtle-derive
47+
[subtle]: https://crates.io/crates/subtle

subtle-derive/src/lib.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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][docs-link].
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+
//! ## Example
27+
//!
28+
//! ```rust
29+
//! use subtle_derive::ConstantTimeEq;
30+
//! use subtle::ConstantTimeEq;
31+
//!
32+
//! #[derive(ConstantTimeEq)]
33+
//! struct MyStruct {
34+
//! data: [u8; 16]
35+
//! }
36+
//!
37+
//!
38+
//! fn main() {
39+
//! let first = MyStruct { data: [1u8;16]};
40+
//! let second = MyStruct { data: [1u8;16]};
41+
//!
42+
//! assert!(bool::from(first.ct_eq(&second)));
43+
//! }
44+
//! ```
45+
//!
46+
//! [crate-image]: https://img.shields.io/crates/v/subtle-derive?style=flat-square
47+
//! [crate-link]: https://crates.io/crates/subtle-derive
48+
//! [docs-image]: https://img.shields.io/docsrs/subtle-derive?style=flat-square
49+
//! [docs-link]: https://docs.rs/crate/subtle-derive
50+
//! [subtle]: https://crates.io/crates/subtle
51+
52+
use proc_macro::TokenStream;
53+
use quote::quote;
54+
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, GenericParam, Generics};
55+
56+
#[proc_macro_derive(ConstantTimeEq)]
57+
pub fn constant_time_eq(input: TokenStream) -> TokenStream {
58+
let input = parse_macro_input!(input as DeriveInput);
59+
derive_ct_eq(&input)
60+
}
61+
62+
63+
fn parse_fields(fields: &Fields) -> Result<proc_macro2::TokenStream, &'static str> {
64+
match &fields {
65+
Fields::Named(fields_named) => {
66+
let mut token_stream = quote!();
67+
let mut iter = fields_named.named.iter().peekable();
68+
69+
while let Some(field) = iter.next() {
70+
let ident = &field.ident;
71+
match iter.peek() {
72+
None => token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) }),
73+
Some(_) => {
74+
token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) & })
75+
}
76+
}
77+
}
78+
Ok(token_stream)
79+
}
80+
Fields::Unnamed(unnamed_fields) => {
81+
let mut token_stream = quote!();
82+
let mut iter = unnamed_fields.unnamed.iter().peekable();
83+
let mut idx = 0;
84+
while let Some(_) = iter.next() {
85+
let i = syn::Index::from(idx);
86+
match iter.peek() {
87+
None => token_stream.extend(quote! { {self.#i}.ct_eq(&{other.#i}) }),
88+
Some(_) => {
89+
token_stream.extend(quote! { {self.#i}.ct_eq(&{other.#i}) & });
90+
idx += 1;
91+
}
92+
}
93+
}
94+
95+
Ok(token_stream)
96+
}
97+
Fields::Unit => Err("Constant time cannot be derived for unit fields"),
98+
}
99+
}
100+
101+
fn parse_enum(data_enum: &DataEnum) -> Result<proc_macro2::TokenStream, &'static str> {
102+
for variant in data_enum.variants.iter() {
103+
if let Fields::Unnamed(_) = variant.fields {
104+
panic!("Cannot derive ct_eq for fields in enums")
105+
}
106+
}
107+
let token_stream = quote! {
108+
::subtle::Choice::from((self == other) as u8)
109+
};
110+
111+
Ok(token_stream)
112+
}
113+
114+
fn parse_data(data: &Data) -> Result<proc_macro2::TokenStream, &'static str> {
115+
match data {
116+
Data::Struct(variant_data) => parse_fields(&variant_data.fields),
117+
Data::Enum(data_enum) => parse_enum(data_enum),
118+
Data::Union(..) => Err("Constant time cannot be derived for a union"),
119+
}
120+
}
121+
122+
fn parse_lifetime(generics: &Generics) -> u32 {
123+
let mut count = 0;
124+
for i in generics.params.iter() {
125+
if let GenericParam::Lifetime(_) = i {
126+
count += 1;
127+
}
128+
}
129+
count
130+
}
131+
132+
fn derive_ct_eq(input: &DeriveInput) -> TokenStream {
133+
let ident = &input.ident;
134+
let data = &input.data;
135+
let generics = &input.generics;
136+
let is_lifetime = parse_lifetime(generics);
137+
let ct_eq_stream: proc_macro2::TokenStream =
138+
parse_data(data).expect("Failed to parse DeriveInput data");
139+
let data_ident = if is_lifetime != 0 {
140+
let mut s = format!("{}<'_", ident);
141+
142+
for _ in 1..is_lifetime {
143+
s.push_str(", '_");
144+
}
145+
s.push('>');
146+
147+
s
148+
} else {
149+
ident.to_string()
150+
};
151+
let ident_stream: proc_macro2::TokenStream =
152+
data_ident.parse().expect("Should be valid lifetime tokens");
153+
154+
let expanded: proc_macro2::TokenStream = quote! {
155+
impl ::subtle::ConstantTimeEq for #ident_stream {
156+
fn ct_eq(&self, other: &Self) -> ::subtle::Choice {
157+
use ::subtle::ConstantTimeEq;
158+
return #ct_eq_stream
159+
}
160+
}
161+
};
162+
163+
expanded.into()
164+
}

0 commit comments

Comments
 (0)