From 17edc81c23e18640eeb4780d1ed330bc2ffa242b Mon Sep 17 00:00:00 2001 From: Brandon Pitman Date: Thu, 7 Sep 2023 16:35:36 -0700 Subject: [PATCH 1/2] Add ConstantTimeEq implementation for [T; N]. Only when the const-generics feature is enabled. I had to modify a few tests: they were relying on [u8; N] being automatically dereferenced into &[u8] to get the blanket ConstantTimeEq implementation for [T]. But once a blanket implementation is also available for [T; N], Rust attempts to use it instead & then complains that it can't compare arrays of different lengths. I suppose this technically counts as a backwards-compatibility break. --- src/lib.rs | 19 ++++++++++++++++++- tests/mod.rs | 23 ++++++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 795eade..d2e7cd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ //! 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. @@ -597,6 +597,23 @@ where } } +#[cfg(feature = "const-generics")] +impl ConstantTimeEq for [T; N] +where + T: ConstantTimeEq, +{ + fn ct_eq(&self, other: &Self) -> Choice { + // This loop shouldn't be shortcircuitable, since the compiler + // shouldn't be able to reason about the value of the `u8` + // unwrapped from the `ct_eq` result. + let mut x = 1u8; + for (ai, bi) in self.iter().zip(other.iter()) { + x &= ai.ct_eq(bi).unwrap_u8(); + } + x.into() + } +} + /// A type which can be conditionally negated in constant time. /// /// # Note diff --git a/tests/mod.rs b/tests/mod.rs index f6b3982..141a514 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -8,16 +8,16 @@ use subtle::*; #[test] #[should_panic] fn slices_equal_different_lengths() { - let a: [u8; 3] = [0, 0, 0]; - let b: [u8; 4] = [0, 0, 0, 0]; + let a: &[u8] = &[0, 0, 0]; + let b: &[u8] = &[0, 0, 0, 0]; assert_eq!((&a).ct_eq(&b).unwrap_u8(), 1); } #[test] fn slices_equal() { - let a: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; - let b: [u8; 8] = [1, 2, 3, 4, 4, 3, 2, 1]; + let a: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8]; + let b: &[u8] = &[1, 2, 3, 4, 4, 3, 2, 1]; let a_eq_a = (&a).ct_eq(&a); let a_eq_b = (&a).ct_eq(&b); @@ -25,12 +25,25 @@ fn slices_equal() { assert_eq!(a_eq_a.unwrap_u8(), 1); assert_eq!(a_eq_b.unwrap_u8(), 0); - let c: [u8; 16] = [0u8; 16]; + let c: &[u8] = &[0u8; 16]; let a_eq_c = (&a).ct_eq(&c); assert_eq!(a_eq_c.unwrap_u8(), 0); } +#[cfg(feature = "const-generics")] +#[test] +fn arrays_equal() { + let a: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; + let b: [u8; 8] = [1, 2, 3, 4, 4, 3, 2, 1]; + + let a_eq_a = (&a).ct_eq(&a); + let a_eq_b = (&a).ct_eq(&b); + + assert_eq!(a_eq_a.unwrap_u8(), 1); + assert_eq!(a_eq_b.unwrap_u8(), 0); +} + #[test] fn conditional_assign_i32() { let mut a: i32 = 5; From a829f6894e68d79f850ba125d69556e8b435fa7b Mon Sep 17 00:00:00 2001 From: Brandon Pitman Date: Thu, 7 Sep 2023 17:25:17 -0700 Subject: [PATCH 2/2] Fall back to slice ct_eq implementation. --- src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d2e7cd2..3df0655 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -603,14 +603,7 @@ where T: ConstantTimeEq, { fn ct_eq(&self, other: &Self) -> Choice { - // This loop shouldn't be shortcircuitable, since the compiler - // shouldn't be able to reason about the value of the `u8` - // unwrapped from the `ct_eq` result. - let mut x = 1u8; - for (ai, bi) in self.iter().zip(other.iter()) { - x &= ai.ct_eq(bi).unwrap_u8(); - } - x.into() + self.as_slice().ct_eq(other) } }