From 48847ee7ee9e00573a20de3ccb1576b4feb44e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 21 Apr 2021 15:37:34 -0700 Subject: [PATCH] Implements hashing as a blanket trait for instances of `CanonicalSerialize` - 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 --- serialize/Cargo.toml | 8 +++++- serialize/src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/serialize/Cargo.toml b/serialize/Cargo.toml index 8e74f5723..991deed1a 100644 --- a/serialize/Cargo.toml +++ b/serialize/Cargo.toml @@ -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" ] diff --git a/serialize/src/lib.rs b/serialize/src/lib.rs index a375c5752..c259830ee 100644 --- a/serialize/src/lib.rs +++ b/serialize/src/lib.rs @@ -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`. @@ -94,6 +96,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 { + 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(&self) -> GenericArray::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 Hash for T {} + /// Deserializer in little endian format allowing flags to be encoded. pub trait CanonicalDeserializeWithFlags: Sized { /// Reads `Self` and `Flags` from `reader`. @@ -897,6 +930,18 @@ mod test { assert_eq!(data, de); } + fn test_hash(data: T) { + let h1 = data.hash::(); + + 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 @@ -1024,4 +1069,22 @@ mod test { fn test_phantomdata() { test_serialize(core::marker::PhantomData::); } + + #[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); + } }