Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement constant-time hash ordering #420

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ neon = []
# --no-default-features, the only way to use the SIMD implementations in this
# crate is to enable the corresponding instruction sets statically for the
# entire build, with e.g. RUSTFLAGS="-C target-cpu=native".
std = []
std = ["subtle/std"]

# The `rayon` feature (disabled by default, but enabled for docs.rs) adds the
# `update_rayon` and (in combination with `mmap` below) `update_mmap_rayon`
Expand Down Expand Up @@ -103,6 +103,7 @@ digest = { version = "0.10.1", features = [ "mac" ], optional = true }
memmap2 = { version = "0.9", optional = true }
rayon-core = { version = "1.12.1", optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
subtle = { version = "2.6", default-features = false }
zeroize = { version = "1", default-features = false, optional = true }

[dev-dependencies]
Expand Down
23 changes: 23 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@ mod join;
use arrayref::{array_mut_ref, array_ref};
use arrayvec::{ArrayString, ArrayVec};
use core::cmp;
use core::cmp::Ordering;
use core::fmt;
use platform::{Platform, MAX_SIMD_DEGREE, MAX_SIMD_DEGREE_OR_2};
use subtle::{ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;

Expand Down Expand Up @@ -341,6 +343,27 @@ impl PartialEq<[u8]> for Hash {

impl Eq for Hash {}

impl PartialOrd for Hash {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Hash {
/// This ordering is [lexigraphical](https://doc.rust-lang.org/std/cmp/trait.Ord.html#lexicographical-comparison), and is done in constant time.
fn cmp(&self, other: &Self) -> cmp::Ordering {
let mut order = Ordering::Equal;

// Iterate over all corresponding bytes, but only set a non-equal ordering on the first mismatch
for (l, r) in self.0.iter().zip(other.0.iter()) {
order.conditional_assign(&Ordering::Less, l.ct_lt(r) & order.ct_eq(&Ordering::Equal));
order.conditional_assign(&Ordering::Greater, l.ct_gt(r) & order.ct_eq(&Ordering::Equal));
}

order
}
}

impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Formatting field as `&str` to reduce code size since the `Debug`
Expand Down
22 changes: 21 additions & 1 deletion src/test.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{CVBytes, CVWords, IncrementCounter, BLOCK_LEN, CHUNK_LEN, OUT_LEN};
use arrayref::array_ref;
use arrayvec::ArrayVec;
use core::usize;
use core::{cmp::Ordering, usize};
use rand::prelude::*;

// Interesting input lengths to run tests on.
Expand Down Expand Up @@ -488,6 +488,26 @@ fn reference_hash(input: &[u8]) -> crate::Hash {
bytes.into()
}

#[test]
fn test_ordering() {
// Test equality behavior
let hash = reference_hash(&[0]);
assert_eq!(hash.0.cmp(&hash.0), Ordering::Equal);
assert_eq!(hash.cmp(&hash), Ordering::Equal);

// Test less-than behavior
let l = reference_hash(&[0]);
let r = reference_hash(&[1]);
assert_eq!(l.0.cmp(&r.0), Ordering::Less);
assert_eq!(l.cmp(&r), Ordering::Less);

// Test greater-than behavior
let l = reference_hash(&[3]);
let r = reference_hash(&[4]);
assert_eq!(l.0.cmp(&r.0), Ordering::Greater);
assert_eq!(l.cmp(&r), Ordering::Greater);
}

#[test]
fn test_compare_update_multiple() {
// Don't use all the long test cases here, since that's unnecessarily slow
Expand Down