From 119afc4d6c2710483d51408e61e1157394d56d87 Mon Sep 17 00:00:00 2001 From: Varsha Date: Wed, 3 May 2023 10:48:18 -0700 Subject: [PATCH] proc derive macro for ConstantTimeEq --- Cargo.toml | 9 + README.md | 29 ++- src/lib.rs | 36 +++- subtle-derive/Cargo.toml | 24 +++ subtle-derive/LICENSE | 28 +++ subtle-derive/README.md | 29 +++ subtle-derive/src/lib.rs | 145 +++++++++++++++ tests/ct_eq_tests.rs | 372 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 670 insertions(+), 2 deletions(-) create mode 100644 subtle-derive/Cargo.toml create mode 100644 subtle-derive/LICENSE create mode 100644 subtle-derive/README.md create mode 100644 subtle-derive/src/lib.rs create mode 100644 tests/ct_eq_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 64532ed..949bece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,8 @@ +[workspace] +members = [ + "subtle-derive" +] + [package] name = "subtle" # Before incrementing: @@ -25,6 +30,9 @@ exclude = [ [badges] travis-ci = { repository = "dalek-cryptography/subtle", branch = "master"} +[dependencies] +subtle-derive = { version = "0.1.0", optional = true, path = "subtle-derive" } + [dev-dependencies] rand = { version = "0.8" } @@ -32,6 +40,7 @@ rand = { version = "0.8" } const-generics = [] core_hint_black_box = [] default = ["std", "i128"] +derive = ["subtle-derive"] std = [] i128 = [] # DEPRECATED: As of 2.4.1, this feature does nothing. diff --git a/README.md b/README.md index de77575..73c3a0a 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,37 @@ barrier ([`core::hint::black_box`]). To use the new optimization barrier, enable the `core_hint_black_box` feature. Rust versions from 1.51 or higher have const generics support. You may enable -`const-generics` feautre to have `subtle` traits implemented for arrays `[T; N]`. +`const-generics` feature to have `subtle` traits implemented for arrays `[T; N]`. Versions prior to `2.2` recommended use of the `nightly` feature to enable an optimization barrier; this is not required in versions `2.2` and above. +Enable `derive` feature to generate implementations for traits using procedural +macros in [`subtle-derive`]. + +```toml +subtle = { version = "2.6", features = ["derive"] } +``` + +## Example + +```ignore +use subtle::ConstantTimeEq; + +#[derive(ConstantTimeEq)] +struct MyStruct { + data: [u8; 16] +} + + +fn main() { + let first = MyStruct { data: [1u8;16]}; + let second = MyStruct { data: [1u8;16]}; + + assert!(bool::from(first.ct_eq(&second))); +} +``` + Note: the `subtle` crate contains `debug_assert`s to check invariants during debug builds. These invariant checks involve secret-dependent branches, and are not present when compiled in release mode. This crate is intended to be @@ -80,3 +106,4 @@ effort is fundamentally limited. [docs]: https://docs.rs/subtle [`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security +[`subtle-derive`]: https://crates.io/crates/subtle-derive \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 795eade..80b9c38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,11 +46,37 @@ //! enable the `core_hint_black_box` feature. //! //! Rust versions from 1.51 or higher have const generics support. You may enable -//! `const-generics` feautre to have `subtle` traits implemented for arrays `[T; N]`. +//! `const-generics` feature to have `subtle` traits implemented for arrays `[T; N]`. //! //! Versions prior to `2.2` recommended use of the `nightly` feature to enable an //! optimization barrier; this is not required in versions `2.2` and above. //! +//! Enable `derive` feature to generate implementations for traits using procedural macros +//! in [`subtle-derive`]. +//! +//! ```toml +//! subtle = { version = "2.6", features = ["derive"] } +//! ``` +//! +//! ## Example +//! +//! ```ignore +//! use subtle::ConstantTimeEq; +//! +//! #[derive(ConstantTimeEq)] +//! struct MyStruct { +//! data: [u8; 16] +//! } +//! +//! fn main() { +//! use subtle::ConstantTimeEq; +//! let first = MyStruct { data: [1u8;16]}; +//! let second = MyStruct { data: [1u8;16]}; +//! +//! assert!(bool::from(first.ct_eq(&second))); +//! } +//! ``` +//! //! Note: the `subtle` crate contains `debug_assert`s to check invariants during //! debug builds. These invariant checks involve secret-dependent branches, and //! are not present when compiled in release mode. This crate is intended to be @@ -95,15 +121,23 @@ //! [docs]: https://docs.rs/subtle //! [`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html //! [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security +//! [`subtle-derive`]: https://crates.io/crates/subtle-derive #[cfg(feature = "std")] #[macro_use] extern crate std; +#[cfg(feature = "subtle-derive")] +#[macro_use] +extern crate subtle_derive; + use core::cmp; use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not}; use core::option::Option; +#[cfg(feature = "subtle-derive")] +pub use subtle_derive::ConstantTimeEq; + /// The `Choice` struct represents a choice for use in conditional assignment. /// /// It is a wrapper around a `u8`, which should have the value either `1` (true) diff --git a/subtle-derive/Cargo.toml b/subtle-derive/Cargo.toml new file mode 100644 index 0000000..46ab751 --- /dev/null +++ b/subtle-derive/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "subtle-derive" +version = "0.1.0" +edition = "2021" +authors = ["Varsha Jayadev "] +readme = "README.md" +license = "BSD-3-Clause" +repository = "https://github.com/dalek-cryptography/subtle" +homepage = "https://dalek.rs/" +documentation = "https://docs.rs/crate/subtle-derive" +categories = ["cryptography", "no-std"] +keywords = ["cryptography", "crypto", "constant-time", "utilities"] +description = "Macros implementation of #[derive(ConstantTimeEq)]" + +[features] +default = [] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.8" +quote = "1.0" +syn = "2.0.15" diff --git a/subtle-derive/LICENSE b/subtle-derive/LICENSE new file mode 100644 index 0000000..927c02c --- /dev/null +++ b/subtle-derive/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/subtle-derive/README.md b/subtle-derive/README.md new file mode 100644 index 0000000..99feb52 --- /dev/null +++ b/subtle-derive/README.md @@ -0,0 +1,29 @@ +# subtle + +[![Crates.io][crate-image]][crate-link][![Docs Status][docs-image]][docs-link] + +**Procedural macros for deriving [subtle] trait implementations.** + +Derive macro implemented for traits: +- [x] ConstantTimeEq +- [ ] ConstantTimeGreater +- [ ] ConstantTimeLesser + +## Documentation + +Documentation is available [here][subtle-docs]. + +# Installation +To install, add the following to the dependencies section of your project's `Cargo.toml`: + +```toml +subtle = { version = "2.6", features = ["derive"] } +``` + +[crate-image]: https://img.shields.io/crates/v/subtle-derive?style=flat-square +[crate-link]: https://crates.io/crates/subtle-derive +[docs-image]: https://img.shields.io/docsrs/subtle-derive?style=flat-square +[docs-link]: https://docs.rs/crate/subtle-derive +[subtle]: https://crates.io/crates/subtle +[subtle-docs]: https://docs.rs/subtle \ No newline at end of file diff --git a/subtle-derive/src/lib.rs b/subtle-derive/src/lib.rs new file mode 100644 index 0000000..e61c35a --- /dev/null +++ b/subtle-derive/src/lib.rs @@ -0,0 +1,145 @@ +// Copyright (c) 2023 The MobileCoin Foundation + +//! # subtle +//! +//! [![Crates.io][crate-image]][crate-link][![Docs Status][docs-image]][docs-link] +//! +//! **Procedural macros for deriving [subtle] trait implementations.** +//! +//! Derive macro implemented for traits: +//! - [x] ConstantTimeEq +//! - [ ] ConstantTimeGreater +//! - [ ] ConstantTimeLesser +//! +//! ## Documentation +//! +//! Documentation is available [here][subtle-docs]. +//! +//! # Installation +//! To install, add the following to the dependencies section of your project's `Cargo.toml`: +//! +//! ```toml +//! subtle = { version = "2.6", features = ["derive"] } +//! ``` +//! +//! [crate-image]: https://img.shields.io/crates/v/subtle-derive?style=flat-square +//! [crate-link]: https://crates.io/crates/subtle-derive +//! [docs-image]: https://img.shields.io/docsrs/subtle-derive?style=flat-square +//! [docs-link]: https://docs.rs/crate/subtle-derive +//! [subtle]: https://crates.io/crates/subtle +//! [subtle-docs]: https://docs.rs/subtle + +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) +} + + +fn parse_fields(fields: &Fields) -> Result { + 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 { + 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! { + ::subtle::Choice::from((self == other) as u8) + }; + + Ok(token_stream) +} + +fn parse_data(data: &Data) -> Result { + 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().expect("Should be valid lifetime tokens"); + + 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() +} diff --git a/tests/ct_eq_tests.rs b/tests/ct_eq_tests.rs new file mode 100644 index 0000000..1035612 --- /dev/null +++ b/tests/ct_eq_tests.rs @@ -0,0 +1,372 @@ +#![cfg(feature = "subtle-derive")] + +use subtle_derive::ConstantTimeEq; + +#[derive(Clone, Copy, ConstantTimeEq)] +struct NamedField { + data: [u8; 16], +} + +#[derive(ConstantTimeEq)] +struct NamedFields { + data1: [u8; 16], + data2: u64, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldsLifetime<'a> { + data1: &'a [u8], + data2: u64, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldsMultipleLifetime<'a, 'b> { + data1: &'a [u8], + data2: &'b [u8], +} + +#[derive(ConstantTimeEq)] +struct NamedFieldUnnamedFieldData { + data: UnnamedField, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldUnnamedFieldsData { + data: UnnamedFields, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldUnnamedArrayField { + data: UnnamedArrayField, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldsUnnamedFieldsData { + data1: UnnamedFields, + data2: u64, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldsUnnamedFieldData { + data1: UnnamedField, + data2: u64, +} + +#[derive(ConstantTimeEq)] +struct NamedFieldsUnnamedArrayFieldData { + data1: UnnamedArrayField, + data2: u64, +} + +#[derive(Clone, Copy, ConstantTimeEq)] +struct UnnamedField(NamedField); + +#[derive(Clone, Copy, ConstantTimeEq)] +struct UnnamedArrayField([u8; 8]); + +#[derive(Clone, Copy, ConstantTimeEq)] +struct UnnamedFields(u32, u64, u32); + +#[derive(Eq, PartialEq, ConstantTimeEq)] +enum EnumFields { + Field1, + Field2, +} + +#[cfg(test)] +mod test { + use crate::*; + use subtle::ConstantTimeEq; + + #[test] + fn ct_eq_named_field() { + let first = NamedField { data: [3u8; 16] }; + let second = NamedField { data: [3u8; 16] }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_field() { + let first = NamedField { data: [45u8; 16] }; + let second = NamedField { data: [87u8; 16] }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_fields() { + let first = NamedFields { + data1: [45u8; 16], + data2: 3, + }; + let second = NamedFields { + data1: [45u8; 16], + data2: 3, + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_fields() { + let first = NamedFields { + data1: [45u8; 16], + data2: 2, + }; + let second = NamedFields { + data1: [87u8; 16], + data2: 3, + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_unnamed_array_field() { + let first = UnnamedArrayField([3u8; 8]); + let second = UnnamedArrayField([3u8; 8]); + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_unnamed_array_field() { + let first = UnnamedArrayField([75u8; 8]); + let second = UnnamedArrayField([22u8; 8]); + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_unnamed_field() { + let first = UnnamedField(NamedField { data: [23u8; 16] }); + let second = UnnamedField(NamedField { data: [23u8; 16] }); + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_unnamed_field() { + let first = UnnamedField(NamedField { data: [33u8; 16] }); + let second = UnnamedField(NamedField { data: [83u8; 16] }); + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_unnamed_fields() { + let first = UnnamedFields(109, 2, 35); + let second = UnnamedFields(109, 2, 35); + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_unnamed_fields() { + let first = UnnamedFields(12, 2, 34); + let second = UnnamedFields(37, 7, 95); + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_field_with_unnamed_data() { + let first = NamedFieldUnnamedFieldData { + data: UnnamedField(NamedField { data: [23u8; 16] }), + }; + let second = NamedFieldUnnamedFieldData { + data: UnnamedField(NamedField { data: [23u8; 16] }), + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_field_with_unnamed_data() { + let first = NamedFieldUnnamedFieldData { + data: UnnamedField(NamedField { data: [3u8; 16] }), + }; + let second = NamedFieldUnnamedFieldData { + data: UnnamedField(NamedField { data: [53u8; 16] }), + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_field_with_unnamed_fields_data() { + let first = NamedFieldUnnamedFieldsData { + data: UnnamedFields(109, 2, 35), + }; + let second = NamedFieldUnnamedFieldsData { + data: UnnamedFields(109, 2, 35), + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_field_with_unnamed_fields_data() { + let first = NamedFieldUnnamedFieldsData { + data: UnnamedFields(109, 4, 109), + }; + let second = NamedFieldUnnamedFieldsData { + data: UnnamedFields(109, 7, 23), + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_field_with_unnamed_array_fields_data() { + let first = NamedFieldUnnamedArrayField { + data: UnnamedArrayField([75u8; 8]), + }; + let second = NamedFieldUnnamedArrayField { + data: UnnamedArrayField([75u8; 8]), + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_field_with_unnamed_array_fields_data() { + let first = NamedFieldUnnamedArrayField { + data: UnnamedArrayField([86u8; 8]), + }; + let second = NamedFieldUnnamedArrayField { + data: UnnamedArrayField([32u8; 8]), + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_fields_with_unnamed_data() { + let first = NamedFieldsUnnamedFieldData { + data1: UnnamedField(NamedField { data: [23u8; 16] }), + data2: 3, + }; + let second = NamedFieldsUnnamedFieldData { + data1: UnnamedField(NamedField { data: [23u8; 16] }), + data2: 3, + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_fields_with_unnamed_data() { + let first = NamedFieldsUnnamedFieldData { + data1: UnnamedField(NamedField { data: [3u8; 16] }), + data2: 5, + }; + let second = NamedFieldsUnnamedFieldData { + data1: UnnamedField(NamedField { data: [53u8; 16] }), + data2: 6, + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_fields_with_unnamed_fields_data() { + let first = NamedFieldsUnnamedFieldsData { + data1: UnnamedFields(109, 2, 35), + data2: 8, + }; + let second = NamedFieldsUnnamedFieldsData { + data1: UnnamedFields(109, 2, 35), + data2: 8, + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_fields_with_unnamed_fields_data() { + let first = NamedFieldsUnnamedFieldsData { + data1: UnnamedFields(109, 4, 109), + data2: 3, + }; + let second = NamedFieldsUnnamedFieldsData { + data1: UnnamedFields(109, 7, 23), + data2: 4, + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_fields_with_unnamed_array_fields_data() { + let first = NamedFieldsUnnamedArrayFieldData { + data1: UnnamedArrayField([75u8; 8]), + data2: 5, + }; + let second = NamedFieldsUnnamedArrayFieldData { + data1: UnnamedArrayField([75u8; 8]), + data2: 5, + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_fields_with_unnamed_array_fields_data() { + let first = NamedFieldsUnnamedArrayFieldData { + data1: UnnamedArrayField([86u8; 8]), + data2: 11, + }; + let second = NamedFieldsUnnamedArrayFieldData { + data1: UnnamedArrayField([32u8; 8]), + data2: 9, + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_enum_fields() { + let first = EnumFields::Field2; + let second = EnumFields::Field2; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_enum_fields() { + let first = EnumFields::Field1; + let second = EnumFields::Field2; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_field_lifetime() { + let first = NamedFieldsLifetime { + data1: &[75u8; 16], + data2: 5, + }; + let second = NamedFieldsLifetime { + data1: &[75u8; 16], + data2: 5, + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_field_lifetime() { + let first = NamedFieldsLifetime { + data1: &[75u8; 16], + data2: 5, + }; + let second = NamedFieldsLifetime { + data1: &[2u8; 16], + data2: 9, + }; + assert!(bool::from(!first.ct_eq(&second))); + } + + #[test] + fn ct_eq_named_field_multiple_lifetime() { + let first = NamedFieldsMultipleLifetime { + data1: &[75u8; 16], + data2: &[38u8; 16], + }; + let second = NamedFieldsMultipleLifetime { + data1: &[75u8; 16], + data2: &[38u8; 16], + }; + assert!(bool::from(first.ct_eq(&second))); + } + + #[test] + fn ct_not_eq_named_field_multiple_lifetime() { + let first = NamedFieldsMultipleLifetime { + data1: &[75u8; 16], + data2: &[75u8; 16], + }; + let second = NamedFieldsMultipleLifetime { + data1: &[2u8; 16], + data2: &[86u8; 16], + }; + assert!(bool::from(!first.ct_eq(&second))); + } +}