diff --git a/src/lib.rs b/src/lib.rs index 0a69c88..fc11d5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -267,6 +267,40 @@ impl Hash { } Ok(Hash::from(hash_bytes)) } + + /// Returns the ordering between `self` and `rhs` as an i8. + /// Values correspond to the Ordering enum: + /// -1 is Less + /// 0 is Equal + /// 1 is Greater + #[inline] + const fn cmp(lhs: &Self, rhs: &Self) -> i8 { + // Implementation is based on https://github.com/RustCrypto/crypto-bigint/blob/master/src/uint/cmp.rs#L80 + + let mut i = 0; + let mut borrow = 0; + let mut diff = 0; + + while i < 32 { + let (w, b) = sbb(rhs.0[i], lhs.0[i], borrow); + diff |= w; + borrow = b; + i += 1; + } + let sgn = ((borrow & 2) as i8) - 1; + + ((diff != 0) as i8) * sgn + } +} + +/// Computes `lhs - (rhs + borrow)`, returning the result along with the new borrow. +#[inline(always)] +pub const fn sbb(lhs: u8, rhs: u8, borrow: u8) -> (u8, u8) { + let a = lhs as u16; + let b = rhs as u16; + let borrow = (borrow >> (8 - 1)) as u16; + let ret = a.wrapping_sub(b + borrow); + (ret as u8, (ret >> 8) as u8) } impl From<[u8; OUT_LEN]> for Hash { @@ -317,6 +351,26 @@ impl PartialEq<[u8]> for Hash { impl Eq for Hash {} +/// This implementation is constant-time. +impl Ord for Hash { + fn cmp(&self, other: &Self) -> cmp::Ordering { + let c = Self::cmp(self, other); + match c { + -1 => cmp::Ordering::Less, + 0 => cmp::Ordering::Equal, + _ => cmp::Ordering::Greater, + } + } +} + +/// This implementation is constant-time. +impl PartialOrd for Hash { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + 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` diff --git a/src/test.rs b/src/test.rs index d8a4f19..253fcd9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,4 +1,4 @@ -use crate::{CVBytes, CVWords, IncrementCounter, BLOCK_LEN, CHUNK_LEN, OUT_LEN}; +use crate::{CVBytes, CVWords, Hash, IncrementCounter, BLOCK_LEN, CHUNK_LEN, OUT_LEN}; use arrayref::array_ref; use arrayvec::ArrayVec; use core::usize; @@ -689,3 +689,14 @@ fn test_zeroize() { )); assert_eq!(output_reader.position_within_block, 0); } + + +#[test] +fn test_ordering() { + let a = Hash::from_bytes([0u8; 32]); + let b = Hash::from_bytes([1u8; 32]); + + assert_eq!(a.cmp(&b), core::cmp::Ordering::Less); + assert_eq!(a.cmp(&a), core::cmp::Ordering::Equal); + assert_eq!(b.cmp(&a), core::cmp::Ordering::Greater); +}