Skip to content

Commit

Permalink
Implements hashing as a blanket trait for instances of `CanonicalSeri…
Browse files Browse the repository at this point in the history
…alize`

- this saves an alllocation w.r.t the suggested approach by implementing `io::Write` on the input instance of `digest::Digest`,
- note that most instances of `digest::Digest` [already](https://gist.github.com/huitseeker/e827161413063e347ce5a496b66ff287) have an [`io::Write` instance](https://github.com/rustcrypto/hashes#hashing-readable-objects), but `CanonicalSerialize` consuming its `io::Write` argument prevents its usage,
- this hence implements `io::Write` on a [cheap newtype wrapper](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)

Fixes #86
  • Loading branch information
huitseeker committed Apr 21, 2021
1 parent bfa7043 commit e15b4c5
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 19 deletions.
8 changes: 7 additions & 1 deletion serialize/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ edition = "2018"
[dependencies]
ark-serialize-derive = { version = "^0.2.0", path = "../serialize-derive", optional = true }
ark-std = { version = "0.2.0", default-features = false }
digest = { version = "0.9", default-features = false }

[dev-dependencies]
sha2 = { version = "0.9.3", default-features = false}
sha3 = { version = "0.9.1", default-features = false}
blake2 = { version = "0.9.1", default-features = false}

[features]
default = []
std = [ "ark-std/std" ]
std = [ "ark-std/std", ]
derive = [ "ark-serialize-derive" ]
17 changes: 9 additions & 8 deletions serialize/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ pub trait Flags: Default + Clone + Copy + Sized {
// bit masks: `0` and `1 << 7`.
fn u8_bitmask(&self) -> u8;

// Tries to read `Self` from `value`. Should return `None` if the `Self::BIT_SIZE`
// most-significant bits of `value` do not correspond to those generated by
// `u8_bitmask`.
// Tries to read `Self` from `value`. Should return `None` if the
// `Self::BIT_SIZE` most-significant bits of `value` do not correspond to
// those generated by `u8_bitmask`.
//
// That is, this method ignores all but the top `Self::BIT_SIZE` bits, and
// decides whether these top bits correspond to a bitmask output by `u8_bitmask`.
// decides whether these top bits correspond to a bitmask output by
// `u8_bitmask`.
fn from_u8(value: u8) -> Option<Self>;

// Convenience method that reads `Self` from `value`, just like `Self::from_u8`, but
// additionally zeroes out the bits corresponding to the resulting flag in `value`.
// If `Self::from_u8(*value)` would return `None`, then this method should
// *not* modify `value`.
// Convenience method that reads `Self` from `value`, just like `Self::from_u8`,
// but additionally zeroes out the bits corresponding to the resulting flag
// in `value`. If `Self::from_u8(*value)` would return `None`, then this
// method should *not* modify `value`.
fn from_u8_remove_flags(value: &mut u8) -> Option<Self> {
let flags = Self::from_u8(*value);
if let Some(f) = flags {
Expand Down
84 changes: 74 additions & 10 deletions serialize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub use flags::*;
#[doc(hidden)]
pub use ark_serialize_derive::*;

use digest::{generic_array::GenericArray, Digest};

/// Serializer in little endian format allowing to encode flags.
pub trait CanonicalSerializeWithFlags: CanonicalSerialize {
/// Serializes `self` and `flags` into `writer`.
Expand All @@ -36,9 +38,9 @@ pub trait CanonicalSerializeWithFlags: CanonicalSerialize {
/// Serializer in little endian format.
/// The serialization format must be 'length-extension' safe.
/// e.g. if T implements Canonical Serialize and Deserialize,
/// then for all strings `x, y`, if `a = T::deserialize(Reader(x))` and `a` is not an error,
/// then it must be the case that `a = T::deserialize(Reader(x || y))`,
/// and that both readers read the same number of bytes.
/// then for all strings `x, y`, if `a = T::deserialize(Reader(x))` and `a` is
/// not an error, then it must be the case that `a = T::deserialize(Reader(x ||
/// y))`, and that both readers read the same number of bytes.
///
/// This trait can be derived if all fields of a struct implement
/// `CanonicalSerialize` and the `derive` feature is enabled.
Expand All @@ -62,14 +64,15 @@ pub trait CanonicalSerialize {
/// Serializes `self` into `writer`.
/// It is left up to a particular type for how it strikes the
/// serialization efficiency vs compression tradeoff.
/// For standard types (e.g. `bool`, lengths, etc.) typically an uncompressed
/// form is used, whereas for algebraic types compressed forms are used.
/// For standard types (e.g. `bool`, lengths, etc.) typically an
/// uncompressed form is used, whereas for algebraic types compressed
/// forms are used.
///
/// Particular examples of interest:
/// `bool` - 1 byte encoding
/// uints - Direct encoding
/// Length prefixing (for any container implemented by default) - 8 byte encoding
/// Elliptic curves - compressed point encoding
/// Length prefixing (for any container implemented by default) - 8 byte
/// encoding Elliptic curves - compressed point encoding
fn serialize<W: Write>(&self, writer: W) -> Result<(), SerializationError>;

fn serialized_size(&self) -> usize;
Expand All @@ -94,6 +97,37 @@ pub trait CanonicalSerialize {
}
}

// This private struct works around Serialize taking the pre-existing
// std::io::Write instance of most digest::Digest implementations by value
struct HashMarshaller<'a, H: Digest>(&'a mut H);

impl<'a, H: Digest> ark_std::io::Write for HashMarshaller<'a, H> {
#[inline]
fn write(&mut self, buf: &[u8]) -> ark_std::io::Result<usize> {
Digest::update(self.0, buf);
Ok(buf.len())
}

#[inline]
fn flush(&mut self) -> ark_std::io::Result<()> {
Ok(())
}
}

/// The Canonical serialization to bytes induces a natural way to hash the
/// corresponding value.
pub trait Hash: CanonicalSerialize {
fn hash<H: Digest>(&self) -> GenericArray<u8, <H as Digest>::OutputSize> {
let mut hasher = H::new();
self.serialize(HashMarshaller(&mut hasher))
.expect("HashMarshaller::flush should be infaillible!");
hasher.finalize()
}
}

// Nothing required in the extension -> derive it as a blanket impl
impl<T: CanonicalSerialize> Hash for T {}

/// Deserializer in little endian format allowing flags to be encoded.
pub trait CanonicalDeserializeWithFlags: Sized {
/// Reads `Self` and `Flags` from `reader`.
Expand Down Expand Up @@ -649,7 +683,8 @@ impl CanonicalDeserialize for bool {
}
}

// Serialize BTreeMap as `len(map) || key 1 || value 1 || ... || key n || value n`
// Serialize BTreeMap as `len(map) || key 1 || value 1 || ... || key n || value
// n`
impl<K, V> CanonicalSerialize for BTreeMap<K, V>
where
K: CanonicalSerialize,
Expand Down Expand Up @@ -815,8 +850,7 @@ impl<T: CanonicalDeserialize + Ord> CanonicalDeserialize for BTreeSet<T> {
#[cfg(test)]
mod test {
use super::*;
use ark_std::rand::RngCore;
use ark_std::vec;
use ark_std::{rand::RngCore, vec};

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
struct Dummy;
Expand Down Expand Up @@ -897,6 +931,18 @@ mod test {
assert_eq!(data, de);
}

fn test_hash<T: CanonicalSerialize, H: Digest + core::fmt::Debug>(data: T) {
let h1 = data.hash::<H>();

let mut hash = H::new();
let mut serialized = vec![0; data.serialized_size()];
data.serialize(&mut serialized[..]).unwrap();
hash.update(&serialized);
let h2 = hash.finalize();

assert_eq!(h1, h2);
}

// Serialize T, randomly mutate the data, and deserialize it.
// Ensure it fails.
// Up to the caller to provide a valid mutation criterion
Expand Down Expand Up @@ -1024,4 +1070,22 @@ mod test {
fn test_phantomdata() {
test_serialize(core::marker::PhantomData::<Dummy>);
}

#[test]
fn test_sha2() {
test_hash::<_, sha2::Sha256>(Dummy);
test_hash::<_, sha2::Sha512>(Dummy);
}

#[test]
fn test_blake2() {
test_hash::<_, blake2::Blake2b>(Dummy);
test_hash::<_, blake2::Blake2s>(Dummy);
}

#[test]
fn test_sha3() {
test_hash::<_, sha3::Sha3_256>(Dummy);
test_hash::<_, sha3::Sha3_512>(Dummy);
}
}

0 comments on commit e15b4c5

Please sign in to comment.