Skip to content

Commit e84edda

Browse files
committed
Merge branch 'release/2.4.0'
2 parents 728d8d8 + 7eac378 commit e84edda

File tree

5 files changed

+253
-3
lines changed

5 files changed

+253
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
Entries are listed in reverse chronological order.
44

5+
## 2.4.0
6+
7+
* Add new `ConstantTimeGreater` and `ConstantTimeLess` traits, as well
8+
as implementations for unsigned integers, by @isislovecruft.
9+
510
## 2.3.0
611

712
* Add `impl ConstantTimeEq for Choice` by @tarcieri.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name = "subtle"
44
# - update CHANGELOG
55
# - update html_root_url
66
# - update README if necessary by semver
7-
version = "2.3.0"
7+
version = "2.4.0"
88
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>",
99
"Henry de Valence <hdevalence@hdevalence.ca>"]
1010
readme = "README.md"
@@ -23,6 +23,9 @@ exclude = [
2323
[badges]
2424
travis-ci = { repository = "dalek-cryptography/subtle", branch = "master"}
2525

26+
[dev-dependencies]
27+
rand = { version = "0.7" }
28+
2629
[features]
2730
default = ["std", "i128"]
2831
std = []

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ instead of `bool` which are intended to execute in constant-time. The `Choice`
77
type is a wrapper around a `u8` that holds a `0` or `1`.
88

99
```toml
10-
subtle = "2.3"
10+
subtle = "2.4"
1111
```
1212

1313
This crate represents a “best-effort” attempt, since side-channels

src/lib.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
1414
#![cfg_attr(feature = "nightly", deny(missing_docs))]
1515
#![doc(html_logo_url = "https://doc.dalek.rs/assets/dalek-logo-clear.png")]
16-
#![doc(html_root_url = "https://docs.rs/subtle/2.3.0")]
16+
#![doc(html_root_url = "https://docs.rs/subtle/2.4.0")]
1717

1818
//! Note that docs will only build on nightly Rust until
1919
//! [RFC 1990 stabilizes](https://github.com/rust-lang/rust/issues/44732).
@@ -22,6 +22,9 @@
2222
#[macro_use]
2323
extern crate std;
2424

25+
#[cfg(test)]
26+
extern crate rand;
27+
2528
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
2629
use core::option::Option;
2730

@@ -664,3 +667,136 @@ impl<T: ConstantTimeEq> ConstantTimeEq for CtOption<T> {
664667
(a & b & self.value.ct_eq(&rhs.value)) | (!a & !b)
665668
}
666669
}
670+
671+
/// A type which can be compared in some manner and be determined to be greater
672+
/// than another of the same type.
673+
pub trait ConstantTimeGreater {
674+
/// Determine whether `self > other`.
675+
///
676+
/// The bitwise-NOT of the return value of this function should be usable to
677+
/// determine if `self <= other`.
678+
///
679+
/// This function should execute in constant time.
680+
///
681+
/// # Returns
682+
///
683+
/// A `Choice` with a set bit if `self > other`, and with no set bits
684+
/// otherwise.
685+
///
686+
/// # Example
687+
///
688+
/// ```
689+
/// # extern crate subtle;
690+
/// use subtle::ConstantTimeGreater;
691+
///
692+
/// let x: u8 = 13;
693+
/// let y: u8 = 42;
694+
///
695+
/// let x_gt_y = x.ct_gt(&y);
696+
///
697+
/// assert_eq!(x_gt_y.unwrap_u8(), 0);
698+
///
699+
/// let y_gt_x = y.ct_gt(&x);
700+
///
701+
/// assert_eq!(y_gt_x.unwrap_u8(), 1);
702+
///
703+
/// let x_gt_x = x.ct_gt(&x);
704+
///
705+
/// assert_eq!(x_gt_x.unwrap_u8(), 0);
706+
/// ```
707+
fn ct_gt(&self, other: &Self) -> Choice;
708+
}
709+
710+
macro_rules! generate_unsigned_integer_greater {
711+
($t_u: ty, $bit_width: expr) => {
712+
impl ConstantTimeGreater for $t_u {
713+
/// Returns Choice::from(1) iff x > y, and Choice::from(0) iff x <= y.
714+
///
715+
/// # Note
716+
///
717+
/// This algoritm would also work for signed integers if we first
718+
/// flip the top bit, e.g. `let x: u8 = x ^ 0x80`, etc.
719+
#[inline]
720+
fn ct_gt(&self, other: &$t_u) -> Choice {
721+
let gtb = self & !other; // All the bits in self that are greater than their corresponding bits in other.
722+
let mut ltb = !self & other; // All the bits in self that are less than their corresponding bits in other.
723+
let mut pow = 1;
724+
725+
// Less-than operator is okay here because it's dependent on the bit-width.
726+
while pow < $bit_width {
727+
ltb |= ltb >> pow; // Bit-smear the highest set bit to the right.
728+
pow += pow;
729+
}
730+
let mut bit = gtb & !ltb; // Select the highest set bit.
731+
let mut pow = 1;
732+
733+
while pow < $bit_width {
734+
bit |= bit >> pow; // Shift it to the right until we end up with either 0 or 1.
735+
pow += pow;
736+
}
737+
// XXX We should possibly do the above flattening to 0 or 1 in the
738+
// Choice constructor rather than making it a debug error?
739+
Choice::from((bit & 1) as u8)
740+
}
741+
}
742+
}
743+
}
744+
745+
generate_unsigned_integer_greater!(u8, 8);
746+
generate_unsigned_integer_greater!(u16, 16);
747+
generate_unsigned_integer_greater!(u32, 32);
748+
generate_unsigned_integer_greater!(u64, 64);
749+
#[cfg(feature = "i128")]
750+
generate_unsigned_integer_greater!(u128, 128);
751+
752+
/// A type which can be compared in some manner and be determined to be less
753+
/// than another of the same type.
754+
pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater {
755+
/// Determine whether `self < other`.
756+
///
757+
/// The bitwise-NOT of the return value of this function should be usable to
758+
/// determine if `self >= other`.
759+
///
760+
/// A default implementation is provided and implemented for the unsigned
761+
/// integer types.
762+
///
763+
/// This function should execute in constant time.
764+
///
765+
/// # Returns
766+
///
767+
/// A `Choice` with a set bit if `self < other`, and with no set bits
768+
/// otherwise.
769+
///
770+
/// # Example
771+
///
772+
/// ```
773+
/// # extern crate subtle;
774+
/// use subtle::ConstantTimeLess;
775+
///
776+
/// let x: u8 = 13;
777+
/// let y: u8 = 42;
778+
///
779+
/// let x_lt_y = x.ct_lt(&y);
780+
///
781+
/// assert_eq!(x_lt_y.unwrap_u8(), 1);
782+
///
783+
/// let y_lt_x = y.ct_lt(&x);
784+
///
785+
/// assert_eq!(y_lt_x.unwrap_u8(), 0);
786+
///
787+
/// let x_lt_x = x.ct_lt(&x);
788+
///
789+
/// assert_eq!(x_lt_x.unwrap_u8(), 0);
790+
/// ```
791+
#[inline]
792+
fn ct_lt(&self, other: &Self) -> Choice {
793+
!self.ct_gt(other) & !self.ct_eq(other)
794+
}
795+
}
796+
797+
impl ConstantTimeLess for u8 {}
798+
impl ConstantTimeLess for u16 {}
799+
impl ConstantTimeLess for u32 {}
800+
impl ConstantTimeLess for u64 {}
801+
#[cfg(feature = "i128")]
802+
impl ConstantTimeLess for u128 {}

tests/mod.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
extern crate rand;
12
extern crate subtle;
23

4+
use rand::rngs::OsRng;
5+
use rand::RngCore;
6+
37
use subtle::*;
48

59
#[test]
@@ -281,3 +285,105 @@ fn unwrap_none_ctoption() {
281285
// compiler decides to optimize it away.
282286
CtOption::new(10, Choice::from(0)).unwrap();
283287
}
288+
289+
macro_rules! generate_greater_than_test {
290+
($ty: ty) => {
291+
for _ in 0..100 {
292+
let x = OsRng.next_u64() as $ty;
293+
let y = OsRng.next_u64() as $ty;
294+
let z = x.ct_gt(&y);
295+
296+
println!("x={}, y={}, z={:?}", x, y, z);
297+
298+
if x < y {
299+
assert!(z.unwrap_u8() == 0);
300+
} else if x == y {
301+
assert!(z.unwrap_u8() == 0);
302+
} else if x > y {
303+
assert!(z.unwrap_u8() == 1);
304+
}
305+
}
306+
}
307+
}
308+
309+
#[test]
310+
fn greater_than_u8() {
311+
generate_greater_than_test!(u8);
312+
}
313+
314+
#[test]
315+
fn greater_than_u16() {
316+
generate_greater_than_test!(u16);
317+
}
318+
319+
#[test]
320+
fn greater_than_u32() {
321+
generate_greater_than_test!(u32);
322+
}
323+
324+
#[test]
325+
fn greater_than_u64() {
326+
generate_greater_than_test!(u64);
327+
}
328+
329+
#[cfg(feature = "i128")]
330+
#[test]
331+
fn greater_than_u128() {
332+
generate_greater_than_test!(u128);
333+
}
334+
335+
#[test]
336+
/// Test that the two's compliment min and max, i.e. 0000...0001 < 1111...1110,
337+
/// gives the correct result. (This fails using the bit-twiddling algorithm that
338+
/// go/crypto/subtle uses.)
339+
fn less_than_twos_compliment_minmax() {
340+
let z = 1u32.ct_lt(&(2u32.pow(31)-1));
341+
342+
assert!(z.unwrap_u8() == 1);
343+
}
344+
345+
macro_rules! generate_less_than_test {
346+
($ty: ty) => {
347+
for _ in 0..100 {
348+
let x = OsRng.next_u64() as $ty;
349+
let y = OsRng.next_u64() as $ty;
350+
let z = x.ct_gt(&y);
351+
352+
println!("x={}, y={}, z={:?}", x, y, z);
353+
354+
if x < y {
355+
assert!(z.unwrap_u8() == 0);
356+
} else if x == y {
357+
assert!(z.unwrap_u8() == 0);
358+
} else if x > y {
359+
assert!(z.unwrap_u8() == 1);
360+
}
361+
}
362+
}
363+
}
364+
365+
#[test]
366+
fn less_than_u8() {
367+
generate_less_than_test!(u8);
368+
}
369+
370+
#[test]
371+
fn less_than_u16() {
372+
generate_less_than_test!(u16);
373+
}
374+
375+
#[test]
376+
fn less_than_u32() {
377+
generate_less_than_test!(u32);
378+
}
379+
380+
#[test]
381+
fn less_than_u64() {
382+
generate_less_than_test!(u64);
383+
}
384+
385+
#[cfg(feature = "i128")]
386+
#[test]
387+
fn less_than_u128() {
388+
generate_less_than_test!(u128);
389+
}

0 commit comments

Comments
 (0)