Skip to content

Commit 37a596c

Browse files
squashed commit
- copyright and rustfmt fix - add ConstantTimeCmp - use a lookup table to avoid branching - implement ConstantTimePartialOrd - expose `index_mutually_exclusive_logical_results()` - make the constants static - make ct_cmp() a default trait method for reasons described in docs - implement ct_eq() for Ordering
1 parent b4b070c commit 37a596c

File tree

1 file changed

+157
-8
lines changed

1 file changed

+157
-8
lines changed

src/lib.rs

+157-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// -*- mode: rust; -*-
22
//
33
// This file is part of subtle, part of the dalek cryptography project.
4-
// Copyright (c) 2016-2018 isis lovecruft, Henry de Valence
4+
// Copyright (c) 2016-2022 isis lovecruft, Henry de Valence
55
// See LICENSE for licensing information.
66
//
77
// Authors:
@@ -87,6 +87,10 @@
8787
#[macro_use]
8888
extern crate std;
8989

90+
#[cfg(test)]
91+
extern crate rand;
92+
93+
use core::cmp::Ordering;
9094
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
9195
use core::option::Option;
9296

@@ -111,6 +115,11 @@ use core::option::Option;
111115
pub struct Choice(u8);
112116

113117
impl Choice {
118+
/// Create an instance in `const` context.
119+
pub const fn of_bool(of: bool) -> Self {
120+
Self(of as u8)
121+
}
122+
114123
/// Unwrap the `Choice` wrapper to reveal the underlying `u8`.
115124
///
116125
/// # Note
@@ -236,7 +245,7 @@ impl From<u8> for Choice {
236245
}
237246
}
238247

239-
/// An `Eq`-like trait that produces a `Choice` instead of a `bool`.
248+
/// An [`Eq`]-like trait that produces a `Choice` instead of a `bool`.
240249
///
241250
/// # Example
242251
///
@@ -257,7 +266,6 @@ pub trait ConstantTimeEq {
257266
///
258267
/// * `Choice(1u8)` if `self == other`;
259268
/// * `Choice(0u8)` if `self != other`.
260-
#[inline]
261269
fn ct_eq(&self, other: &Self) -> Choice;
262270
}
263271

@@ -380,7 +388,6 @@ pub trait ConditionallySelectable: Copy {
380388
/// assert_eq!(z, y);
381389
/// # }
382390
/// ```
383-
#[inline]
384391
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self;
385392

386393
/// Conditionally assign `other` to `self`, according to `choice`.
@@ -530,7 +537,6 @@ pub trait ConditionallyNegatable {
530537
/// unchanged.
531538
///
532539
/// This function should execute in constant time.
533-
#[inline]
534540
fn conditional_negate(&mut self, choice: Choice);
535541
}
536542

@@ -801,7 +807,7 @@ macro_rules! generate_unsigned_integer_greater {
801807
Choice::from((bit & 1) as u8)
802808
}
803809
}
804-
}
810+
};
805811
}
806812

807813
generate_unsigned_integer_greater!(u8, 8);
@@ -813,7 +819,7 @@ generate_unsigned_integer_greater!(u128, 128);
813819

814820
/// A type which can be compared in some manner and be determined to be less
815821
/// than another of the same type.
816-
pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater {
822+
pub trait ConstantTimeLess: ConstantTimeGreater {
817823
/// Determine whether `self < other`.
818824
///
819825
/// The bitwise-NOT of the return value of this function should be usable to
@@ -852,7 +858,7 @@ pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater {
852858
/// ```
853859
#[inline]
854860
fn ct_lt(&self, other: &Self) -> Choice {
855-
!self.ct_gt(other) & !self.ct_eq(other)
861+
other.ct_gt(self)
856862
}
857863
}
858864

@@ -862,3 +868,146 @@ impl ConstantTimeLess for u32 {}
862868
impl ConstantTimeLess for u64 {}
863869
#[cfg(feature = "i128")]
864870
impl ConstantTimeLess for u128 {}
871+
872+
/// A [`PartialOrd`][core::cmp::PartialOrd]-like trait for constant-time comparisons.
873+
///
874+
/// This trait is automatically implemented for types supporting the "equals", "less", and
875+
/// "greater" comparisons.
876+
///
877+
/// # Example
878+
///
879+
/// ```
880+
/// use std::cmp::Ordering;
881+
/// use subtle::{ConstantTimePartialOrd, CtOption};
882+
/// let x: u8 = 5;
883+
/// let y: u8 = 13;
884+
///
885+
/// assert_eq!(x.ct_partial_cmp(&x).unwrap(), Ordering::Equal);
886+
/// assert_eq!(x.ct_partial_cmp(&y).unwrap(), Ordering::Less);
887+
/// assert_eq!(y.ct_partial_cmp(&x).unwrap(), Ordering::Greater);
888+
/// ```
889+
pub trait ConstantTimePartialOrd {
890+
/// This method returns an ordering between `self` and `other`, if it exists.
891+
///
892+
/// This method should execute in constant time.
893+
fn ct_partial_cmp(&self, other: &Self) -> CtOption<Ordering>;
894+
}
895+
896+
impl ConstantTimeEq for Ordering {
897+
/// Use our `#[repr(i8)]` to get a `ct_eq()` implementation without relying on any `match`es.
898+
///
899+
/// This also means `CtOption<Ordering>` implements `ConstantTimeEq`.
900+
#[inline]
901+
fn ct_eq(&self, other: &Self) -> Choice {
902+
let a = *self as i8;
903+
let b = *other as i8;
904+
a.ct_eq(&b)
905+
}
906+
}
907+
908+
/// Select among `N + 1` results given `N` logical values, of which at most one should be true.
909+
///
910+
/// This method requires a whole set of logical checks to be performed before evaluating their
911+
/// result, and uses a lookup table to avoid branching in a `match` expression.
912+
///
913+
///```
914+
/// use subtle::index_mutually_exclusive_logical_results;
915+
///
916+
/// let r = [0xA, 0xB, 0xC];
917+
///
918+
/// let a = index_mutually_exclusive_logical_results(&r, [0.into(), 0.into()]);
919+
/// assert_eq!(*a, 0xA);
920+
/// let b = index_mutually_exclusive_logical_results(&r, [1.into(), 0.into()]);
921+
/// assert_eq!(*b, 0xB);
922+
/// let c = index_mutually_exclusive_logical_results(&r, [0.into(), 1.into()]);
923+
/// assert_eq!(*c, 0xC);
924+
///```
925+
pub fn index_mutually_exclusive_logical_results<T, const N: usize>(
926+
results: &[T],
927+
logicals: [Choice; N],
928+
) -> &T {
929+
assert_eq!(results.len(), N + 1);
930+
let combined_result: u8 = logicals.iter().enumerate().fold(0u8, |x, (i, choice)| {
931+
x + ((i as u8) + 1) * choice.unwrap_u8()
932+
});
933+
results
934+
.get(combined_result as usize)
935+
.expect("multiple inconsistent mutually exclusive logical operations returned true")
936+
}
937+
938+
impl<T: ConstantTimeGreater + ConstantTimeLess + ConstantTimeEq> ConstantTimePartialOrd for T {
939+
/// We do not assume a total ordering for `T`, so we have to individually check "less than",
940+
/// "equal", and "greater". This also respects non-default implementations of `ct_lt()`.
941+
fn ct_partial_cmp(&self, other: &Self) -> CtOption<Ordering> {
942+
let is_eq = self.ct_eq(other);
943+
let is_lt = self.ct_lt(other);
944+
let is_gt = self.ct_gt(other);
945+
946+
static PARTIAL_ORDERS: [CtOption<Ordering>; 4] = [
947+
CtOption {
948+
value: Ordering::Equal,
949+
is_some: Choice::of_bool(false),
950+
},
951+
CtOption {
952+
value: Ordering::Equal,
953+
is_some: Choice::of_bool(true),
954+
},
955+
CtOption {
956+
value: Ordering::Less,
957+
is_some: Choice::of_bool(true),
958+
},
959+
CtOption {
960+
value: Ordering::Greater,
961+
is_some: Choice::of_bool(true),
962+
},
963+
];
964+
*index_mutually_exclusive_logical_results(&PARTIAL_ORDERS, [is_eq, is_lt, is_gt])
965+
}
966+
}
967+
968+
/// An [`Ord`][core::cmp::Ord]-like trait for constant-time comparisons.
969+
///
970+
/// This trait can be automatically implemented for types supporting the "equals" and "greater"
971+
/// comparisons.
972+
///
973+
/// # Example
974+
///
975+
/// ```
976+
/// use std::cmp::Ordering;
977+
/// use subtle::ConstantTimeOrd;
978+
/// let x: u8 = 5;
979+
/// let y: u8 = 13;
980+
///
981+
/// assert_eq!(x.ct_cmp(&x), Ordering::Equal);
982+
/// assert_eq!(x.ct_cmp(&y), Ordering::Less);
983+
/// assert_eq!(y.ct_cmp(&x), Ordering::Greater);
984+
/// ```
985+
pub trait ConstantTimeOrd: ConstantTimeEq + ConstantTimeGreater {
986+
/// This method returns an ordering between `self` and other`.
987+
///
988+
/// Although this method should never need to be overridden, it is exposed as a default method
989+
/// here to force types to explicitly implement this trait. This ensures that types which are
990+
/// *only* partially orderable do not pick up an incorrect `ConstantTimeOrd` impl just by
991+
/// implementing the pairwise comparison operations. Contrast this with
992+
/// [`ConstantTimePartialOrd`], which is automatically implemented for all types implementing
993+
/// [`ConstantTimeGreater`], [`ConstantTimeLess`], and [`ConstantTimeEq`].
994+
///
995+
/// Here we assume a total ordering for `T`, so we need to check only "equal" and "greater", and
996+
/// can assume "less" if both `ct_eq()` and `ct_gt()` are false.
997+
///
998+
/// This method should execute in constant time.
999+
fn ct_cmp(&self, other: &Self) -> Ordering {
1000+
let is_gt = self.ct_gt(other);
1001+
let is_eq = self.ct_eq(other);
1002+
1003+
static ORDERS: [Ordering; 3] = [Ordering::Less, Ordering::Greater, Ordering::Equal];
1004+
*index_mutually_exclusive_logical_results(&ORDERS, [is_gt, is_eq])
1005+
}
1006+
}
1007+
1008+
impl ConstantTimeOrd for u8 {}
1009+
impl ConstantTimeOrd for u16 {}
1010+
impl ConstantTimeOrd for u32 {}
1011+
impl ConstantTimeOrd for u64 {}
1012+
#[cfg(feature = "i128")]
1013+
impl ConstantTimeOrd for u128 {}

0 commit comments

Comments
 (0)