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

feat: add serde feature & PartialOrd & Ord #7

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ std = []
# "signature" crate.)
traits-preview = ["digest"]

# Adds serialization and deserialization abilities to `Hash`
serde = ["dep:serde"]

# ---------- Features below this line are undocumented and unstable. ----------
# The following features are mainly intended for testing and benchmarking, and
# they might change or disappear at any time without a major version bump.
Expand Down Expand Up @@ -91,6 +94,7 @@ rayon = { version = "1.2.1", optional = true }
cfg-if = "1.0.0"
digest = { version = "0.10.1", features = [ "mac" ], optional = true }
zeroize_crate = { package = "zeroize", version = "1", default-features = false, features = ["zeroize_derive"], optional = true }
serde = { version = "1.0.213", optional = true }

[dev-dependencies]
hex = "0.4.2"
Expand All @@ -99,6 +103,8 @@ rand = "0.8.0"
rand_chacha = "0.3.0"
reference_impl = { path = "./reference_impl" }
hmac = "0.12.0"
postcard = { version = "1.0.10", features = ["use-std"] }
serde_json = "1.0.132"

[build-dependencies]
cc = "1.0.4"
57 changes: 57 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ extern crate zeroize_crate as zeroize; // Needed because `zeroize::Zeroize` assu
#[cfg(test)]
mod test;

#[cfg(feature = "serde")]
mod serde;

// The guts module is for incremental use cases like the `bao` crate that need
// to explicitly compute chunk and parent chaining values. It is semi-stable
// and likely to keep working, but largely undocumented and not intended for
Expand Down Expand Up @@ -264,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 {
Expand Down Expand Up @@ -314,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<cmp::Ordering> {
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`
Expand Down
58 changes: 58 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use super::Hash;

impl Serialize for Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
self.as_bytes().serialize(serializer)
}
}
}

impl<'de> Deserialize<'de> for Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
} else {
let data: [u8; 32] = Deserialize::deserialize(deserializer)?;
Ok(Hash::from(data))
}
}
}

#[cfg(test)]
mod tests {
use super::*;

use crate::hash;

#[test]
fn test_hash_postcard() {
let hash = hash(b"hello");
let ser = postcard::to_stdvec(&hash).unwrap();
let de: Hash = postcard::from_bytes(&ser).unwrap();
assert_eq!(hash, de);

assert_eq!(ser.len(), 32);
}

#[test]
fn test_hash_json() {
let hash = hash(b"hello");
let ser = serde_json::to_string(&hash).unwrap();
let de: Hash = serde_json::from_str(&ser).unwrap();
assert_eq!(hash, de);
// 64 bytes of hex + 2 quotes
assert_eq!(ser.len(), 66);
}
}
13 changes: 12 additions & 1 deletion src/test.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
Loading