Skip to content

Commit

Permalink
Implement constant-time hash ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronFeickert committed Oct 12, 2024
1 parent aa3e8ec commit 0e0f811
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 2 deletions.
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

0 comments on commit 0e0f811

Please sign in to comment.