From 506ef6c08f0e052b2ecdb53855a8a6580dcfcf44 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 00:49:35 -0700 Subject: [PATCH 01/46] Initial commit * Initial ObjectHash and ObjectHasher traits * Support for calculating ObjectHashes of integers * Backend support for *ring* SHA-256 digests --- .gitignore | 2 ++ .travis.yml | 20 ++++++++++++ Cargo.toml | 15 +++++++++ README.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++ src/hasher/mod.rs | 7 ++++ src/hasher/ring.rs | 49 ++++++++++++++++++++++++++++ src/lib.rs | 33 +++++++++++++++++++ src/types.rs | 53 ++++++++++++++++++++++++++++++ 8 files changed, 259 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/hasher/mod.rs create mode 100644 src/hasher/ring.rs create mode 100644 src/lib.rs create mode 100644 src/types.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b6e25f7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: rust +sudo: false + +script: cargo test --verbose --features "objecthash-ring" + +branches: + only: + - master + +rust: + - stable + - beta + - nightly + +os: + - linux + - osx + +notifications: + irc: 'irc.freenode.org#cryptosphere' diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..781835b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "objecthash" +version = "0.1.0" +authors = ["Tony Arcieri "] + +[dependencies.ring] +optional = true +git = "https://github.com/briansmith/ring" + +[dev-dependencies.rustc-serialize] +version = "*" + +[features] +default = [] +objecthash-ring = ["ring"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..85d414a --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# ObjectHash for Rust [![Latest Version][crate-image]][crate-link] [![Build Status][build-image]][build-link] [![Apache 2 +licensed][license-image]][license-link] + +[crate-image]: https://img.shields.io/crates/v/rust-objecthash.svg +[crate-link]: https://crates.io/crates/rust-objecthash +[build-image]: https://travis-ci.org/cryptosphere/rust-objecthash.svg?branch=master +[build-link]: https://travis-ci.org/cryptosphere/rust-objecthash +[license-image]: https://img.shields.io/badge/license-Apache2-blue.svg +[license-link]: https://github.com/cryptosphere/rust-objecthash/blob/master/LICENSE + +A content hash algorithm which works across multiple encodings (JSON, Protobufs, etc). + +This crate provides a Rust implementation of an algorithm [originally created by Ben Laurie](https://github.com/benlaurie/objecthash). + +## Installation + +You will need to select a supported cryptography library to use as ObjectHash's backend. The following backend libraries +are supported: + +* [ring]: A safe, fast, small Rust crypto library based on BoringSSL's cryptography primitives + +[ring]: https://github.com/briansmith/ring + +Please make sure to add a crypto backend crate or the `objecthash` crate will not compile! + +## Usage + +ObjectHashes can be used to compute a content hash of a deeply nested structure. The intended use is to first +deserialize data into a nested structure, then perform an ObjectHash digest of its contents. This way, the same +content hash to be computed regardless of how the data is serialized, which allows the data to be transcoded between +formats without having to recompute the content hash. + +This crate defines a trait called ObjectHash: + +```rust +pub trait ObjectHash { + fn objecthash(&self, hasher: &mut H); +} +``` + +There are built-in implementations for the following types: + +* **Integers:**: + * `i8` + * `i16` + * `i32` + * `i64` + * `u8` + * `u16` + * `u32` + * `u64` + * `isize` + * `usize` + +To calculate the ObjectHash digest of some data, call the following: + +```rust +let digest: Vec = objecthash::digest(42); +``` + +This will compute a digest (using the SHA-256 algorithm) of the given value, provided the type of the value given +implements the ObjectHash trait. + +## TODO + +* Zero allocation API +* More types +* More test vectors +* Redaction support + +## Contributing + +* Fork this repository on Github +* Make your changes and send a pull request +* If your changes look good, we'll merge them + +## Copyright + +Copyright (c) 2016 Tony Arcieri. Distributed under the Apache 2.0 License. +See LICENSE file for further details. diff --git a/src/hasher/mod.rs b/src/hasher/mod.rs new file mode 100644 index 0000000..ec98df5 --- /dev/null +++ b/src/hasher/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "objecthash-ring")] +pub mod ring; + +#[cfg(feature = "objecthash-ring")] +pub fn default() -> ring::Hasher { + ring::Hasher::new() +} diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs new file mode 100644 index 0000000..3fef717 --- /dev/null +++ b/src/hasher/ring.rs @@ -0,0 +1,49 @@ +extern crate ring; + +use self::ring::digest; + +use ObjectHasher; + +pub struct Hasher { + ctx: digest::Context, +} + +impl Hasher { + pub fn new() -> Hasher { + Hasher::with_algorithm(&digest::SHA256) + } + + pub fn with_algorithm(alg: &'static digest::Algorithm) -> Hasher { + Hasher { ctx: digest::Context::new(&alg) } + } +} + +impl ObjectHasher for Hasher { + fn write(&mut self, bytes: &[u8]) { + self.ctx.update(bytes); + } + + fn finish(self) -> Vec { + let digest = self.ctx.finish(); + Vec::from(digest.as_ref()) + } +} + +#[cfg(test)] +mod tests { + use super::Hasher; + use ObjectHasher; + use rustc_serialize::hex::ToHex; + + // From Project NESSIE + // https://www.cosic.esat.kuleuven.be/nessie/testvectors/hash/sha/Sha-2-256.unverified.test-vectors + const SHA256_VECTOR_STRING: &'static str = "abcdefghijklmnopqrstuvwxyz"; + const SHA256_VECTOR_DIGEST: &'static str = "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"; + + #[test] + fn sha256_test_vector() { + let mut hasher = Hasher::new(); + hasher.write(SHA256_VECTOR_STRING.as_bytes()); + assert_eq!(hasher.finish().to_hex(), SHA256_VECTOR_DIGEST); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4e84fd0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,33 @@ +#[cfg(test)] +extern crate rustc_serialize; + +pub mod hasher; +mod types; + +pub fn digest(msg: &T) -> Vec { + let mut hasher = hasher::default(); + msg.objecthash(&mut hasher); + hasher.finish() +} + +pub trait ObjectHasher { + fn write(&mut self, bytes: &[u8]); + fn finish(self) -> Vec; +} + +pub trait ObjectHash { + fn objecthash(&self, hasher: &mut H); +} + +#[cfg(test)] +mod tests { + use digest; + use rustc_serialize::hex::ToHex; + + #[test] + fn digest_test() { + let result = digest(&1000); + assert_eq!(result.to_hex(), + "a3346d18105ef801c3598fec426dcc5d4be9d0374da5343f6c8dcbdf24cb8e0b"); + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..1163528 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,53 @@ +use {ObjectHash, ObjectHasher}; + +macro_rules! impl_inttype (($inttype:ident) => ( + impl ObjectHash for $inttype { + fn objecthash(&self, hasher: &mut H) { + hasher.write(b"i"); + hasher.write(self.to_string().as_bytes()) + } + } +)); + +impl_inttype!(i8); +impl_inttype!(i16); +impl_inttype!(i32); +impl_inttype!(i64); +impl_inttype!(u8); +impl_inttype!(u16); +impl_inttype!(u32); +impl_inttype!(u64); +impl_inttype!(isize); +impl_inttype!(usize); + +#[cfg(test)] +mod tests { + use {ObjectHash, ObjectHasher}; + use hasher; + use rustc_serialize::hex::ToHex; + + fn test_i32(val: i32, expected_digest_hex: &str) { + let mut hasher = hasher::default(); + val.objecthash(&mut hasher); + assert_eq!(hasher.finish().to_hex(), expected_digest_hex); + } + + fn test_u32(val: u32, expected_digest_hex: &str) { + let mut hasher = hasher::default(); + val.objecthash(&mut hasher); + assert_eq!(hasher.finish().to_hex(), expected_digest_hex); + } + + #[test] + fn integers() { + // TODO: test other integer types + test_i32(-1, + "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + test_i32(0, + "a4e167a76a05add8a8654c169b07b0447a916035aef602df103e8ae0fe2ff390"); + test_u32(10, + "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + test_u32(1000, + "a3346d18105ef801c3598fec426dcc5d4be9d0374da5343f6c8dcbdf24cb8e0b"); + } +} From 5fa5127b5e5806032431a20f99e55e2a756c1229 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 00:53:40 -0700 Subject: [PATCH 02/46] Fix README.md banner --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 85d414a..c39700c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# ObjectHash for Rust [![Latest Version][crate-image]][crate-link] [![Build Status][build-image]][build-link] [![Apache 2 -licensed][license-image]][license-link] +# ObjectHash for Rust [![Latest Version][crate-image]][crate-link] [![Build Status][build-image]][build-link] [![Apache 2 licensed][license-image]][license-link] [crate-image]: https://img.shields.io/crates/v/rust-objecthash.svg [crate-link]: https://crates.io/crates/rust-objecthash From bad137e4939674b4b54853ca0d1ecd4d24147938 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:01:24 -0700 Subject: [PATCH 03/46] Prep for 0.1.0 release --- CHANGES.md | 4 ++++ Cargo.toml | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..f84c6c8 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,4 @@ +## 0.1.0 (2016-08-08) + +* Initial release + diff --git a/Cargo.toml b/Cargo.toml index 781835b..bee3e31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,13 @@ [package] -name = "objecthash" -version = "0.1.0" -authors = ["Tony Arcieri "] +name = "objecthash" +version = "0.1.0" +description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" +homepage = "https://github.com/cryptosphere/rust-objecthash" +repository = "https://github.com/cryptosphere/rust-objecthash" +readme = "README.md" +keywords = ["hashing", "authenticity", "Merkle", "blockchain"] +license = "Apache-2.0" +authors = ["Tony Arcieri "] [dependencies.ring] optional = true From 8cbc883ccc4f689d8fa5a69e641e73bcd6b1f2ba Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:06:24 -0700 Subject: [PATCH 04/46] Flag features that depend on a hasher backend --- README.md | 2 +- src/lib.rs | 2 ++ src/types.rs | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c39700c..411da8e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ are supported: [ring]: https://github.com/briansmith/ring -Please make sure to add a crypto backend crate or the `objecthash` crate will not compile! +Please make sure to add a crypto backend crate or the `objecthash` crate will not work! ## Usage diff --git a/src/lib.rs b/src/lib.rs index 4e84fd0..0962cdd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate rustc_serialize; pub mod hasher; mod types; +#[cfg(feature = "objecthash-ring")] pub fn digest(msg: &T) -> Vec { let mut hasher = hasher::default(); msg.objecthash(&mut hasher); @@ -20,6 +21,7 @@ pub trait ObjectHash { } #[cfg(test)] +#[cfg(feature = "objecthash-ring")] mod tests { use digest; use rustc_serialize::hex::ToHex; diff --git a/src/types.rs b/src/types.rs index 1163528..a6df680 100644 --- a/src/types.rs +++ b/src/types.rs @@ -21,6 +21,7 @@ impl_inttype!(isize); impl_inttype!(usize); #[cfg(test)] +#[cfg(feature = "objecthash-ring")] mod tests { use {ObjectHash, ObjectHasher}; use hasher; From c1dc277c69d4decd5fc7bb98ef3c5e65e331401a Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:09:48 -0700 Subject: [PATCH 05/46] Fix extra colon in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 411da8e..e81499b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ pub trait ObjectHash { There are built-in implementations for the following types: -* **Integers:**: +* **Integers:** * `i8` * `i16` * `i32` From c74ace034eac5793d6a218b4ae12b8a1ba7d3b35 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:19:10 -0700 Subject: [PATCH 06/46] Temporarily comment out *ring* dependency For the 0.1.0 release. Unfortunately *ring* is not yet released as a crate. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bee3e31..b5182c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ keywords = ["hashing", "authenticity", "Merkle", "blockchain"] license = "Apache-2.0" authors = ["Tony Arcieri "] -[dependencies.ring] -optional = true -git = "https://github.com/briansmith/ring" +# [dependencies.ring] +# optional = true +# git = "https://github.com/briansmith/ring" [dev-dependencies.rustc-serialize] version = "*" [features] default = [] -objecthash-ring = ["ring"] \ No newline at end of file +objecthash-ring = [] # ["ring"] From ac291f2dc1b03423ae789ee505250d12abb1d476 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:20:48 -0700 Subject: [PATCH 07/46] Add version constraint on rustc-serialize --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b5182c7..ab0258a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ authors = ["Tony Arcieri "] # git = "https://github.com/briansmith/ring" [dev-dependencies.rustc-serialize] -version = "*" +version = ">= 0.3.19" [features] default = [] From a41f626206b84ed0aed83aefce5a51c34d7aca12 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:21:57 -0700 Subject: [PATCH 08/46] Revert "Temporarily comment out *ring* dependency" This reverts commit c74ace034eac5793d6a218b4ae12b8a1ba7d3b35. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab0258a..c83a8a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ keywords = ["hashing", "authenticity", "Merkle", "blockchain"] license = "Apache-2.0" authors = ["Tony Arcieri "] -# [dependencies.ring] -# optional = true -# git = "https://github.com/briansmith/ring" +[dependencies.ring] +optional = true +git = "https://github.com/briansmith/ring" [dev-dependencies.rustc-serialize] version = ">= 0.3.19" [features] default = [] -objecthash-ring = [] # ["ring"] +objecthash-ring = ["ring"] From 8cd17f7b43d906a1619fb09848d7a68735537a6d Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:22:25 -0700 Subject: [PATCH 09/46] Fix crate links in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e81499b..a7b7d7a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ObjectHash for Rust [![Latest Version][crate-image]][crate-link] [![Build Status][build-image]][build-link] [![Apache 2 licensed][license-image]][license-link] -[crate-image]: https://img.shields.io/crates/v/rust-objecthash.svg -[crate-link]: https://crates.io/crates/rust-objecthash +[crate-image]: https://img.shields.io/crates/v/objecthash.svg +[crate-link]: https://crates.io/crates/objecthash [build-image]: https://travis-ci.org/cryptosphere/rust-objecthash.svg?branch=master [build-link]: https://travis-ci.org/cryptosphere/rust-objecthash [license-image]: https://img.shields.io/badge/license-Apache2-blue.svg From e9b73a8337a2a45820ed09f9c826d855faad8fff Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 8 Aug 2016 01:34:50 -0700 Subject: [PATCH 10/46] Remove Vec from ObjectHash trait Instead have finish() return a digest object we can call as_ref() on --- src/hasher/ring.rs | 9 +++++---- src/lib.rs | 6 ++++-- src/types.rs | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index 3fef717..84ac29c 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -19,13 +19,14 @@ impl Hasher { } impl ObjectHasher for Hasher { + type D = digest::Digest; + fn write(&mut self, bytes: &[u8]) { self.ctx.update(bytes); } - fn finish(self) -> Vec { - let digest = self.ctx.finish(); - Vec::from(digest.as_ref()) + fn finish(self) -> digest::Digest { + self.ctx.finish() } } @@ -44,6 +45,6 @@ mod tests { fn sha256_test_vector() { let mut hasher = Hasher::new(); hasher.write(SHA256_VECTOR_STRING.as_bytes()); - assert_eq!(hasher.finish().to_hex(), SHA256_VECTOR_DIGEST); + assert_eq!(hasher.finish().as_ref().to_hex(), SHA256_VECTOR_DIGEST); } } diff --git a/src/lib.rs b/src/lib.rs index 0962cdd..e41d942 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,14 @@ mod types; pub fn digest(msg: &T) -> Vec { let mut hasher = hasher::default(); msg.objecthash(&mut hasher); - hasher.finish() + let digest = hasher.finish(); + Vec::from(digest.as_ref()) } pub trait ObjectHasher { + type D: AsRef<[u8]>; fn write(&mut self, bytes: &[u8]); - fn finish(self) -> Vec; + fn finish(self) -> Self::D; } pub trait ObjectHash { diff --git a/src/types.rs b/src/types.rs index a6df680..9b44d49 100644 --- a/src/types.rs +++ b/src/types.rs @@ -30,13 +30,13 @@ mod tests { fn test_i32(val: i32, expected_digest_hex: &str) { let mut hasher = hasher::default(); val.objecthash(&mut hasher); - assert_eq!(hasher.finish().to_hex(), expected_digest_hex); + assert_eq!(hasher.finish().as_ref().to_hex(), expected_digest_hex); } fn test_u32(val: u32, expected_digest_hex: &str) { let mut hasher = hasher::default(); val.objecthash(&mut hasher); - assert_eq!(hasher.finish().to_hex(), expected_digest_hex); + assert_eq!(hasher.finish().as_ref().to_hex(), expected_digest_hex); } #[test] From 88e1fff138471a09d2678e63f979e84e9cbd9e9e Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 9 Aug 2016 00:18:53 -0700 Subject: [PATCH 11/46] str support Calculate objecthashes of normalized Unicode strings --- Cargo.toml | 3 ++ src/lib.rs | 2 ++ src/types.rs | 80 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c83a8a6..7d2c026 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ keywords = ["hashing", "authenticity", "Merkle", "blockchain"] license = "Apache-2.0" authors = ["Tony Arcieri "] +[dependencies.unicode-normalization] +version = ">= 0.1.2" + [dependencies.ring] optional = true git = "https://github.com/briansmith/ring" diff --git a/src/lib.rs b/src/lib.rs index e41d942..bf31621 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +extern crate unicode_normalization; + #[cfg(test)] extern crate rustc_serialize; diff --git a/src/types.rs b/src/types.rs index 9b44d49..df3c294 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,10 +1,28 @@ use {ObjectHash, ObjectHasher}; +use unicode_normalization::UnicodeNormalization; + +const STRING_TAG: &'static [u8; 1] = b"u"; +const INTEGER_TAG: &'static [u8; 1] = b"i"; + +macro_rules! objecthash_digest { + ($hasher:expr, $tag:expr, $bytes:expr) => { + $hasher.write($tag); + $hasher.write($bytes); + }; +} + +impl ObjectHash for str { + fn objecthash(&self, hasher: &mut H) { + let normalized = self.nfc().collect::(); + objecthash_digest!(hasher, STRING_TAG, normalized.as_bytes()); + } +} + macro_rules! impl_inttype (($inttype:ident) => ( impl ObjectHash for $inttype { fn objecthash(&self, hasher: &mut H) { - hasher.write(b"i"); - hasher.write(self.to_string().as_bytes()) + objecthash_digest!(hasher, INTEGER_TAG, self.to_string().as_bytes()); } } )); @@ -23,32 +41,50 @@ impl_inttype!(usize); #[cfg(test)] #[cfg(feature = "objecthash-ring")] mod tests { - use {ObjectHash, ObjectHasher}; - use hasher; + use {hasher, ObjectHash, ObjectHasher}; use rustc_serialize::hex::ToHex; - fn test_i32(val: i32, expected_digest_hex: &str) { - let mut hasher = hasher::default(); - val.objecthash(&mut hasher); - assert_eq!(hasher.finish().as_ref().to_hex(), expected_digest_hex); + macro_rules! h { + ($value:expr) => { + { + let mut hasher = hasher::default(); + $value.objecthash(&mut hasher); + hasher.finish().as_ref().to_hex() + } + }; } - fn test_u32(val: u32, expected_digest_hex: &str) { - let mut hasher = hasher::default(); - val.objecthash(&mut hasher); - assert_eq!(hasher.finish().as_ref().to_hex(), expected_digest_hex); + #[test] + fn integers() { + assert_eq!(h!(-1), "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + assert_eq!(h!(0), "a4e167a76a05add8a8654c169b07b0447a916035aef602df103e8ae0fe2ff390"); + assert_eq!(h!(10), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + assert_eq!(h!(1000), "a3346d18105ef801c3598fec426dcc5d4be9d0374da5343f6c8dcbdf24cb8e0b"); + + assert_eq!(h!(-1 as i8), "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + assert_eq!(h!(-1 as i16), "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + assert_eq!(h!(-1 as i32), "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + assert_eq!(h!(-1 as i64), "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + assert_eq!(h!(-1 as isize), "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); + + assert_eq!(h!(10 as u8), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + assert_eq!(h!(10 as u16), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + assert_eq!(h!(10 as u32), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + assert_eq!(h!(10 as u64), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + assert_eq!(h!(10 as usize), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); + } #[test] - fn integers() { - // TODO: test other integer types - test_i32(-1, - "f105b11df43d5d321f5c773ef904af979024887b4d2b0fab699387f59e2ff01e"); - test_i32(0, - "a4e167a76a05add8a8654c169b07b0447a916035aef602df103e8ae0fe2ff390"); - test_u32(10, - "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); - test_u32(1000, - "a3346d18105ef801c3598fec426dcc5d4be9d0374da5343f6c8dcbdf24cb8e0b"); + fn strings() { + let u1n = "\u{03D3}"; + let u1d = "\u{03D2}\u{0301}"; + + let digest = "f72826713a01881404f34975447bd6edcb8de40b191dc57097ebf4f5417a554d"; + assert_eq!(h!(u1n), digest); + assert_eq!(h!(&u1d), digest); + + assert_eq!(h!("ԱԲաբ"), "2a2a4485a4e338d8df683971956b1090d2f5d33955a81ecaad1a75125f7a316c"); + assert_eq!(h!("\u{03D3}"), "f72826713a01881404f34975447bd6edcb8de40b191dc57097ebf4f5417a554d"); } } From 6c8c9097943b3c31d58513550db2cdc45768a8bb Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 9 Aug 2016 09:20:15 -0700 Subject: [PATCH 12/46] Remove redundant test --- src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index df3c294..fc7a9fd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -85,6 +85,5 @@ mod tests { assert_eq!(h!(&u1d), digest); assert_eq!(h!("ԱԲաբ"), "2a2a4485a4e338d8df683971956b1090d2f5d33955a81ecaad1a75125f7a316c"); - assert_eq!(h!("\u{03D3}"), "f72826713a01881404f34975447bd6edcb8de40b191dc57097ebf4f5417a554d"); } } From 6e57d24c8cdd5f82e5771ed4e1d96b8bdd9e305b Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 10 Aug 2016 22:41:19 -0700 Subject: [PATCH 13/46] Vec support --- README.md | 2 ++ src/types.rs | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7b7d7a..e09d82c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ pub trait ObjectHash { There are built-in implementations for the following types: +* `Vec` +* `str` * **Integers:** * `i8` * `i16` diff --git a/src/types.rs b/src/types.rs index fc7a9fd..98aa51d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,9 +1,11 @@ use {ObjectHash, ObjectHasher}; +use hasher; use unicode_normalization::UnicodeNormalization; -const STRING_TAG: &'static [u8; 1] = b"u"; const INTEGER_TAG: &'static [u8; 1] = b"i"; +const STRING_TAG: &'static [u8; 1] = b"u"; +const LIST_TAG: &'static [u8; 1] = b"l"; macro_rules! objecthash_digest { ($hasher:expr, $tag:expr, $bytes:expr) => { @@ -12,6 +14,18 @@ macro_rules! objecthash_digest { }; } +impl ObjectHash for Vec { + fn objecthash(&self, hasher: &mut H) { + hasher.write(LIST_TAG); + + for value in self { + let mut value_hasher = hasher::default(); + value.objecthash(&mut value_hasher); + hasher.write(value_hasher.finish().as_ref()); + } + } +} + impl ObjectHash for str { fn objecthash(&self, hasher: &mut H) { let normalized = self.nfc().collect::(); @@ -86,4 +100,12 @@ mod tests { assert_eq!(h!("ԱԲաբ"), "2a2a4485a4e338d8df683971956b1090d2f5d33955a81ecaad1a75125f7a316c"); } + + #[test] + fn vectors() { + assert_eq!(h!(vec![123]), "1b93f704451e1a7a1b8c03626ffcd6dec0bc7ace947ff60d52e1b69b4658ccaa"); + assert_eq!(h!(vec![1, 2, 3]), "157bf16c70bd4c9673ffb5030552df0ee2c40282042ccdf6167850edc9044ab7"); + assert_eq!(h!(vec![123456789012345u64]), "3488b9bc37cce8223a032760a9d4ef488cdfebddd9e1af0b31fcd1d7006369a4"); + assert_eq!(h!(vec![123456789012345u64, 678901234567890u64]), "031ef1aaeccea3bced3a1c6237a4fc00ed4d629c9511922c5a3f4e5c128b0ae4"); + } } From f944a82e215b8eff3c458a4ea2ea2a3834f71142 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 10 Aug 2016 23:07:44 -0700 Subject: [PATCH 14/46] Make objecthash-ring a default feature --- .travis.yml | 2 -- Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b6e25f7..7d79466 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: rust sudo: false -script: cargo test --verbose --features "objecthash-ring" - branches: only: - master diff --git a/Cargo.toml b/Cargo.toml index 7d2c026..46eccd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,5 @@ git = "https://github.com/briansmith/ring" version = ">= 0.3.19" [features] -default = [] +default = ["objecthash-ring"] objecthash-ring = ["ring"] From 2102d281c6d34cb7196e7e00dd309182ac93130a Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 10 Aug 2016 23:17:34 -0700 Subject: [PATCH 15/46] Export objecthash_digest! macro --- src/hasher/ring.rs | 2 +- src/lib.rs | 8 ++++++++ src/types.rs | 7 ------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index 84ac29c..764b105 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -42,7 +42,7 @@ mod tests { const SHA256_VECTOR_DIGEST: &'static str = "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"; #[test] - fn sha256_test_vector() { + fn sha256() { let mut hasher = Hasher::new(); hasher.write(SHA256_VECTOR_STRING.as_bytes()); assert_eq!(hasher.finish().as_ref().to_hex(), SHA256_VECTOR_DIGEST); diff --git a/src/lib.rs b/src/lib.rs index bf31621..a51ec35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,14 @@ extern crate unicode_normalization; #[cfg(test)] extern crate rustc_serialize; +#[macro_export] +macro_rules! objecthash_digest { + ($hasher:expr, $tag:expr, $bytes:expr) => { + $hasher.write($tag); + $hasher.write($bytes); + }; +} + pub mod hasher; mod types; diff --git a/src/types.rs b/src/types.rs index 98aa51d..798334e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,13 +7,6 @@ const INTEGER_TAG: &'static [u8; 1] = b"i"; const STRING_TAG: &'static [u8; 1] = b"u"; const LIST_TAG: &'static [u8; 1] = b"l"; -macro_rules! objecthash_digest { - ($hasher:expr, $tag:expr, $bytes:expr) => { - $hasher.write($tag); - $hasher.write($bytes); - }; -} - impl ObjectHash for Vec { fn objecthash(&self, hasher: &mut H) { hasher.write(LIST_TAG); From 067d14712f5d507dec7b805fcaa6b7f58a43d885 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 10 Aug 2016 23:26:02 -0700 Subject: [PATCH 16/46] Add comment about std::hash::BuildHasherDefault --- src/hasher/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hasher/mod.rs b/src/hasher/mod.rs index ec98df5..2cc88a3 100644 --- a/src/hasher/mod.rs +++ b/src/hasher/mod.rs @@ -1,6 +1,7 @@ #[cfg(feature = "objecthash-ring")] pub mod ring; +// TODO: Use std::hash::BuildHasherDefault or our own similar version #[cfg(feature = "objecthash-ring")] pub fn default() -> ring::Hasher { ring::Hasher::new() From 0c41fa82dd600d55178675441c2245919035bc93 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 10 Aug 2016 23:50:28 -0700 Subject: [PATCH 17/46] Add #[inline] to all implementations --- src/types.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types.rs b/src/types.rs index 798334e..bbe5939 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,6 +8,7 @@ const STRING_TAG: &'static [u8; 1] = b"u"; const LIST_TAG: &'static [u8; 1] = b"l"; impl ObjectHash for Vec { + #[inline] fn objecthash(&self, hasher: &mut H) { hasher.write(LIST_TAG); @@ -20,6 +21,7 @@ impl ObjectHash for Vec { } impl ObjectHash for str { + #[inline] fn objecthash(&self, hasher: &mut H) { let normalized = self.nfc().collect::(); objecthash_digest!(hasher, STRING_TAG, normalized.as_bytes()); @@ -28,6 +30,7 @@ impl ObjectHash for str { macro_rules! impl_inttype (($inttype:ident) => ( impl ObjectHash for $inttype { + #[inline] fn objecthash(&self, hasher: &mut H) { objecthash_digest!(hasher, INTEGER_TAG, self.to_string().as_bytes()); } From af17ea509460cef8f48603b4ed4f5eaac92f61a6 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 11 Aug 2016 00:17:38 -0700 Subject: [PATCH 18/46] [u8] support This adds an optional, nonstandard extension to ObjectHash for computing hashes of binary data. By default, ObjectHash only supports Unicode strings. I would like to get these changes more widely supported in other ObjectHash implementations, but for now they're flagged as optional because they're nonstandard. --- Cargo.toml | 1 + src/lib.rs | 8 -------- src/types.rs | 20 ++++++++++++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46eccd8..55f2180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,4 @@ version = ">= 0.3.19" [features] default = ["objecthash-ring"] objecthash-ring = ["ring"] +octet-strings = [] diff --git a/src/lib.rs b/src/lib.rs index a51ec35..bf31621 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,14 +3,6 @@ extern crate unicode_normalization; #[cfg(test)] extern crate rustc_serialize; -#[macro_export] -macro_rules! objecthash_digest { - ($hasher:expr, $tag:expr, $bytes:expr) => { - $hasher.write($tag); - $hasher.write($bytes); - }; -} - pub mod hasher; mod types; diff --git a/src/types.rs b/src/types.rs index bbe5939..86c40a6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,6 +7,16 @@ const INTEGER_TAG: &'static [u8; 1] = b"i"; const STRING_TAG: &'static [u8; 1] = b"u"; const LIST_TAG: &'static [u8; 1] = b"l"; +#[cfg(feature = "octet-strings")] +const OCTET_TAG: &'static [u8; 1] = b"o"; + +macro_rules! objecthash_digest { + ($hasher:expr, $tag:expr, $bytes:expr) => { + $hasher.write($tag); + $hasher.write($bytes); + }; +} + impl ObjectHash for Vec { #[inline] fn objecthash(&self, hasher: &mut H) { @@ -28,6 +38,16 @@ impl ObjectHash for str { } } +// Technically ObjectHash does not define a representation for binary data +// For now this is a non-standard extension of ObjectHash +#[cfg(feature = "octet-strings")] +impl ObjectHash for [u8] { + #[inline] + fn objecthash(&self, hasher: &mut H) { + objecthash_digest!(hasher, OCTET_TAG, self); + } +} + macro_rules! impl_inttype (($inttype:ident) => ( impl ObjectHash for $inttype { #[inline] From 30c3d76f89d69a6052c87255a4964c5d15454944 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 11 Aug 2016 19:02:04 -0700 Subject: [PATCH 19/46] Hasher::write -> update, update_nested Support for hashing nested object graphs, abstracting the creation of new digest contexts into ObjectHasher. --- src/hasher/ring.rs | 13 +++++++++++-- src/lib.rs | 3 ++- src/types.rs | 11 ++++------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index 764b105..d028a46 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -21,10 +21,19 @@ impl Hasher { impl ObjectHasher for Hasher { type D = digest::Digest; - fn write(&mut self, bytes: &[u8]) { + #[inline] + fn update(&mut self, bytes: &[u8]) { self.ctx.update(bytes); } + #[inline] + fn update_nested(&mut self, nested: F) where F: Fn(&mut Self) { + let mut nested_hasher = Hasher::with_algorithm(&self.ctx.algorithm); + nested(&mut nested_hasher); + self.update(nested_hasher.finish().as_ref()); + } + + #[inline] fn finish(self) -> digest::Digest { self.ctx.finish() } @@ -44,7 +53,7 @@ mod tests { #[test] fn sha256() { let mut hasher = Hasher::new(); - hasher.write(SHA256_VECTOR_STRING.as_bytes()); + hasher.update(SHA256_VECTOR_STRING.as_bytes()); assert_eq!(hasher.finish().as_ref().to_hex(), SHA256_VECTOR_DIGEST); } } diff --git a/src/lib.rs b/src/lib.rs index bf31621..068af43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ pub fn digest(msg: &T) -> Vec { pub trait ObjectHasher { type D: AsRef<[u8]>; - fn write(&mut self, bytes: &[u8]); + fn update(&mut self, bytes: &[u8]); + fn update_nested(&mut self, nested: F) where F: Fn(&mut Self); fn finish(self) -> Self::D; } diff --git a/src/types.rs b/src/types.rs index 86c40a6..2f8b82e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,5 @@ use {ObjectHash, ObjectHasher}; -use hasher; use unicode_normalization::UnicodeNormalization; const INTEGER_TAG: &'static [u8; 1] = b"i"; @@ -12,20 +11,18 @@ const OCTET_TAG: &'static [u8; 1] = b"o"; macro_rules! objecthash_digest { ($hasher:expr, $tag:expr, $bytes:expr) => { - $hasher.write($tag); - $hasher.write($bytes); + $hasher.update($tag); + $hasher.update($bytes); }; } impl ObjectHash for Vec { #[inline] fn objecthash(&self, hasher: &mut H) { - hasher.write(LIST_TAG); + hasher.update(LIST_TAG); for value in self { - let mut value_hasher = hasher::default(); - value.objecthash(&mut value_hasher); - hasher.write(value_hasher.finish().as_ref()); + hasher.update_nested(|h| value.objecthash(h)); } } } From 216a8e60ba7f849eb21993be2e23f8e0889b06af Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 11 Aug 2016 20:20:25 -0700 Subject: [PATCH 20/46] Digest type This allows us to avoid heap allocations (i.e. Vec) --- README.md | 1 - src/hasher/ring.rs | 5 +++++ src/lib.rs | 30 ++++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e09d82c..74b87bc 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ implements the ObjectHash trait. ## TODO -* Zero allocation API * More types * More test vectors * Redaction support diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index d028a46..2078cfb 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -21,6 +21,11 @@ impl Hasher { impl ObjectHasher for Hasher { type D = digest::Digest; + #[inline] + fn output_len(&self) -> usize { + self.ctx.algorithm.output_len + } + #[inline] fn update(&mut self, bytes: &[u8]) { self.ctx.update(bytes); diff --git a/src/lib.rs b/src/lib.rs index 068af43..bef5a44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,16 +6,38 @@ extern crate rustc_serialize; pub mod hasher; mod types; +const MAX_OUTPUT_LEN: usize = 32; + +pub struct Digest { + output_len: usize, + value: [u8; MAX_OUTPUT_LEN], +} + +impl AsRef<[u8]> for Digest { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + &self.value[..self.output_len] + } +} + #[cfg(feature = "objecthash-ring")] -pub fn digest(msg: &T) -> Vec { +pub fn digest(msg: &T) -> Digest { let mut hasher = hasher::default(); msg.objecthash(&mut hasher); - let digest = hasher.finish(); - Vec::from(digest.as_ref()) + + let output_len = hasher.output_len(); + let mut digest_bytes = [0u8; MAX_OUTPUT_LEN]; + digest_bytes.copy_from_slice(hasher.finish().as_ref()); + + Digest { + output_len: output_len, + value: digest_bytes + } } pub trait ObjectHasher { type D: AsRef<[u8]>; + fn output_len(&self) -> usize; fn update(&mut self, bytes: &[u8]); fn update_nested(&mut self, nested: F) where F: Fn(&mut Self); fn finish(self) -> Self::D; @@ -34,7 +56,7 @@ mod tests { #[test] fn digest_test() { let result = digest(&1000); - assert_eq!(result.to_hex(), + assert_eq!(result.as_ref().to_hex(), "a3346d18105ef801c3598fec426dcc5d4be9d0374da5343f6c8dcbdf24cb8e0b"); } } From db2a167391554d7d3fa7aec0b0732831739d6937 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 11 Aug 2016 21:33:07 -0700 Subject: [PATCH 21/46] HashMap (and String) support Implements the ObjectHash dict format for HashMaps. Also implements ObjectHash for String. --- README.md | 7 ++-- src/hasher/ring.rs | 4 ++- src/lib.rs | 2 +- src/types.rs | 90 ++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 74b87bc..b056b52 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,13 @@ pub trait ObjectHash { } ``` -There are built-in implementations for the following types: +There are built-in implementations of the `ObjectHash` trait for the +following types: -* `Vec` +* `Vec` +* `HashMap` * `str` +* `String` * **Integers:** * `i8` * `i16` diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index 2078cfb..6ab7bca 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -32,7 +32,9 @@ impl ObjectHasher for Hasher { } #[inline] - fn update_nested(&mut self, nested: F) where F: Fn(&mut Self) { + fn update_nested(&mut self, nested: F) + where F: Fn(&mut Self) + { let mut nested_hasher = Hasher::with_algorithm(&self.ctx.algorithm); nested(&mut nested_hasher); self.update(nested_hasher.finish().as_ref()); diff --git a/src/lib.rs b/src/lib.rs index bef5a44..5b3f86d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub fn digest(msg: &T) -> Digest { Digest { output_len: output_len, - value: digest_bytes + value: digest_bytes, } } diff --git a/src/types.rs b/src/types.rs index 2f8b82e..21a2421 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,13 +1,18 @@ -use {ObjectHash, ObjectHasher}; +use std; +use std::collections::HashMap; +use std::io::Write; + +use {digest, ObjectHash, ObjectHasher}; use unicode_normalization::UnicodeNormalization; -const INTEGER_TAG: &'static [u8; 1] = b"i"; -const STRING_TAG: &'static [u8; 1] = b"u"; -const LIST_TAG: &'static [u8; 1] = b"l"; +pub const INTEGER_TAG: &'static [u8; 1] = b"i"; +pub const STRING_TAG: &'static [u8; 1] = b"u"; +pub const LIST_TAG: &'static [u8; 1] = b"l"; +pub const DICT_TAG: &'static [u8; 1] = b"d"; #[cfg(feature = "octet-strings")] -const OCTET_TAG: &'static [u8; 1] = b"o"; +pub const OCTET_TAG: &'static [u8; 1] = b"o"; macro_rules! objecthash_digest { ($hasher:expr, $tag:expr, $bytes:expr) => { @@ -27,6 +32,34 @@ impl ObjectHash for Vec { } } +impl ObjectHash for HashMap + where K: ObjectHash + Eq + std::hash::Hash, + V: ObjectHash + PartialEq, + S: std::hash::BuildHasher +{ + #[inline] + fn objecthash(&self, hasher: &mut H) { + hasher.update(DICT_TAG); + + let mut digests: Vec> = self.iter() + .map(|(k, v)| { + let kd = digest(k); + let vd = digest(v); + let mut d = Vec::with_capacity(kd.as_ref().len() + vd.as_ref().len()); + d.write(&kd.as_ref()).unwrap(); + d.write(&vd.as_ref()).unwrap(); + d + }) + .collect(); + + digests.sort(); + + for value in &digests { + hasher.update(&value); + } + } +} + impl ObjectHash for str { #[inline] fn objecthash(&self, hasher: &mut H) { @@ -35,6 +68,14 @@ impl ObjectHash for str { } } +impl ObjectHash for String { + #[inline] + fn objecthash(&self, hasher: &mut H) { + let normalized = self.nfc().collect::(); + objecthash_digest!(hasher, STRING_TAG, normalized.as_bytes()); + } +} + // Technically ObjectHash does not define a representation for binary data // For now this is a non-standard extension of ObjectHash #[cfg(feature = "octet-strings")] @@ -68,6 +109,8 @@ impl_inttype!(usize); #[cfg(test)] #[cfg(feature = "objecthash-ring")] mod tests { + use std::collections::HashMap; + use {hasher, ObjectHash, ObjectHasher}; use rustc_serialize::hex::ToHex; @@ -99,7 +142,6 @@ mod tests { assert_eq!(h!(10 as u32), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); assert_eq!(h!(10 as u64), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); assert_eq!(h!(10 as usize), "73f6128db300f3751f2e509545be996d162d20f9e030864632f85e34fd0324ce"); - } #[test] @@ -112,6 +154,7 @@ mod tests { assert_eq!(h!(&u1d), digest); assert_eq!(h!("ԱԲաբ"), "2a2a4485a4e338d8df683971956b1090d2f5d33955a81ecaad1a75125f7a316c"); + assert_eq!(h!(String::from("ԱԲաբ")), "2a2a4485a4e338d8df683971956b1090d2f5d33955a81ecaad1a75125f7a316c"); } #[test] @@ -121,4 +164,39 @@ mod tests { assert_eq!(h!(vec![123456789012345u64]), "3488b9bc37cce8223a032760a9d4ef488cdfebddd9e1af0b31fcd1d7006369a4"); assert_eq!(h!(vec![123456789012345u64, 678901234567890u64]), "031ef1aaeccea3bced3a1c6237a4fc00ed4d629c9511922c5a3f4e5c128b0ae4"); } + + #[test] + fn hashmaps() { + { + let hashmap: HashMap = HashMap::new(); + assert_eq!(h!(hashmap), "18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4"); + } + + { + let mut hashmap = HashMap::new(); + hashmap.insert(String::from("foo"), 1); + assert_eq!(h!(hashmap), "bf4c58f5e308e31e2cd64bdbf7a01b9b595a13602438be5e912c7d94f6d8177a"); + } + } + + #[test] + fn hashmap_ordering() { + { + let mut hashmap = HashMap::new(); + hashmap.insert(String::from("k1"), String::from("v1")); + hashmap.insert(String::from("k2"), String::from("v2")); + hashmap.insert(String::from("k3"), String::from("v3")); + + assert_eq!(h!(hashmap), "ddd65f1f7568269a30df7cafc26044537dc2f02a1a0d830da61762fc3e687057"); + } + + { + let mut hashmap = HashMap::new(); + hashmap.insert(String::from("k2"), String::from("v2")); + hashmap.insert(String::from("k1"), String::from("v1")); + hashmap.insert(String::from("k3"), String::from("v3")); + + assert_eq!(h!(hashmap), "ddd65f1f7568269a30df7cafc26044537dc2f02a1a0d830da61762fc3e687057"); + } + } } From 648f3db11d100a8ce54062b4abb5f595fb9918fb Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 12 Aug 2016 20:31:26 -0700 Subject: [PATCH 22/46] Make 'types' module public --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5b3f86d..466efec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ extern crate unicode_normalization; extern crate rustc_serialize; pub mod hasher; -mod types; +pub mod types; const MAX_OUTPUT_LEN: usize = 32; From d9b0c9db2cd245be4a42c476fae7dec7be0904ce Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 15 Aug 2016 23:40:38 -0700 Subject: [PATCH 23/46] Use the *ring* crate This allows us to do a proper release --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 55f2180..350553e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ version = ">= 0.1.2" [dependencies.ring] optional = true -git = "https://github.com/briansmith/ring" +version = ">= 0.2" [dev-dependencies.rustc-serialize] version = ">= 0.3.19" From 4a22e210b1a73800582df229e31adaaa688785fd Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 15 Aug 2016 23:41:57 -0700 Subject: [PATCH 24/46] Bump version to 0.1.1 and update CHANGES.md --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f84c6c8..99d86cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## 0.1.1 (2016-08-15) + +* Add the newly released *ring* crate as an optional dependency + ## 0.1.0 (2016-08-08) * Initial release diff --git a/Cargo.toml b/Cargo.toml index 350553e..c5eca96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objecthash" -version = "0.1.0" +version = "0.1.1" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" From 2d63b26c4ddd20a5e49073ff562c1fdd08a48da3 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 16 Aug 2016 19:21:34 -0700 Subject: [PATCH 25/46] Fix README URLs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b056b52..d34925e 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [crate-image]: https://img.shields.io/crates/v/objecthash.svg [crate-link]: https://crates.io/crates/objecthash -[build-image]: https://travis-ci.org/cryptosphere/rust-objecthash.svg?branch=master -[build-link]: https://travis-ci.org/cryptosphere/rust-objecthash +[build-image]: https://travis-ci.org/cryptosphere/objecthash-rs.svg?branch=master +[build-link]: https://travis-ci.org/cryptosphere/objecthash-rs [license-image]: https://img.shields.io/badge/license-Apache2-blue.svg -[license-link]: https://github.com/cryptosphere/rust-objecthash/blob/master/LICENSE +[license-link]: https://github.com/cryptosphere/objecthash-rs/blob/master/LICENSE A content hash algorithm which works across multiple encodings (JSON, Protobufs, etc). From 2da0d75ece15f2845e34a24b8c1085f3349e6524 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 15:13:22 -0700 Subject: [PATCH 26/46] objecthash_struct! and objecthash_dict_entry! macros Macros for computing the objecthashes of structs --- README.md | 20 ++++++++++++++++++++ src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ src/types.rs | 10 +--------- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d34925e..4fa384f 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,26 @@ let digest: Vec = objecthash::digest(42); This will compute a digest (using the SHA-256 algorithm) of the given value, provided the type of the value given implements the ObjectHash trait. + +## Macros + +The `objecthash_struct!` macro is designed to simplify implementing the ObjectHash trait on structs, producing +a dict-type hash across their keys and values: + +```rust +impl ObjectHash for MyStruct { + #[inline] + fn objecthash(&self, hasher: &mut H) { + objecthash_struct!( + hasher, + "foo" => self.foo, + "bar" => self.bar, + "baz" => self.baz + ) + } +} +``` + ## TODO * More types diff --git a/src/lib.rs b/src/lib.rs index 466efec..0f6fb8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,42 @@ extern crate unicode_normalization; #[cfg(test)] extern crate rustc_serialize; +#[macro_export] +macro_rules! objecthash_dict_entry { + ($key:expr, $value:expr) => { + { + let (key, value) = ($key, $value); + let mut result = Vec::with_capacity(key.as_ref().len() + value.as_ref().len()); + result.extend_from_slice(&key.as_ref()); + result.extend_from_slice(&value.as_ref()); + result + } + } +} + +#[macro_export] +macro_rules! objecthash_struct( + { $hasher:expr, $($key:expr => $value:expr),+ } => { + { + let mut digests: Vec> = Vec::new(); + + $( + digests.push(objecthash_dict_entry!( + objecthash::digest($key), + objecthash::digest($value) + )); + )+ + + digests.sort(); + + $hasher.update(objecthash::types::DICT_TAG); + for value in &digests { + $hasher.update(&value); + } + } + }; +); + pub mod hasher; pub mod types; diff --git a/src/types.rs b/src/types.rs index 21a2421..2a7c296 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,5 @@ use std; use std::collections::HashMap; -use std::io::Write; use {digest, ObjectHash, ObjectHasher}; @@ -42,14 +41,7 @@ impl ObjectHash for HashMap hasher.update(DICT_TAG); let mut digests: Vec> = self.iter() - .map(|(k, v)| { - let kd = digest(k); - let vd = digest(v); - let mut d = Vec::with_capacity(kd.as_ref().len() + vd.as_ref().len()); - d.write(&kd.as_ref()).unwrap(); - d.write(&vd.as_ref()).unwrap(); - d - }) + .map(|(k, v)| objecthash_dict_entry!(digest(k), digest(v))) .collect(); digests.sort(); From 3e22897dcf9795ab19dad76b7da4f82b7c9587a2 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 15:39:12 -0700 Subject: [PATCH 27/46] Bump version to 0.2.0 and update CHANGES.md --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 99d86cb..5a555a6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## 0.2.0 (2016-08-21) + +* Add objecthash_struct! and objecthash_dict_entry! macros + ## 0.1.1 (2016-08-15) * Add the newly released *ring* crate as an optional dependency diff --git a/Cargo.toml b/Cargo.toml index c5eca96..e106d50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objecthash" -version = "0.1.1" +version = "0.2.0" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" From a3c0f46b2d3003244a75c045f48332ac86c4f1d4 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 15:51:43 -0700 Subject: [PATCH 28/46] Fix objecthash_struct macro --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0f6fb8d..697e956 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,8 @@ macro_rules! objecthash_struct( $( digests.push(objecthash_dict_entry!( - objecthash::digest($key), - objecthash::digest($value) + objecthash::digest(&$key), + objecthash::digest(&$value) )); )+ From f6a91636801f5526b380d02ed99c5ee5f02412df Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 15:52:26 -0700 Subject: [PATCH 29/46] Bump version to 0.2.1 and update CHANGES.md --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 5a555a6..3a89b8d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## 0.2.1 (2016-08-21) + +* Bugfix for objecthash_struct! macro + ## 0.2.0 (2016-08-21) * Add objecthash_struct! and objecthash_dict_entry! macros diff --git a/Cargo.toml b/Cargo.toml index e106d50..8757feb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objecthash" -version = "0.2.0" +version = "0.2.1" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" From c078c87358e4fb1f0192cc6c23e664733302ac5e Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 16:02:11 -0700 Subject: [PATCH 30/46] Coerce struct key names to Strings --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 697e956..3eeb27e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ macro_rules! objecthash_struct( $( digests.push(objecthash_dict_entry!( - objecthash::digest(&$key), + objecthash::digest(&String::from($key)), objecthash::digest(&$value) )); )+ From 10cd417bb09a01d6816900af761930bbd8f31154 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 16:04:01 -0700 Subject: [PATCH 31/46] Bump version to 0.2.2 and update CHANGES.md --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 3a89b8d..20e39d9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## 0.2.2 (2016-08-21) + +* Coerce objecthash_struct! key names to Strings + ## 0.2.1 (2016-08-21) * Bugfix for objecthash_struct! macro diff --git a/Cargo.toml b/Cargo.toml index 8757feb..8ffb147 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objecthash" -version = "0.2.1" +version = "0.2.2" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" From bf5d4151d9f5f8f3ed804183644a20c5323efb79 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 16:09:46 -0700 Subject: [PATCH 32/46] objecthash_struct_member! macro --- src/lib.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3eeb27e..c7b58ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,18 @@ macro_rules! objecthash_dict_entry { } } +#[macro_export] +macro_rules! objecthash_struct_member { + ($key:expr, $value:expr) => { + { + objecthash_dict_entry!( + objecthash::digest(&String::from($key)), + objecthash::digest(&$value) + ) + } + } +} + #[macro_export] macro_rules! objecthash_struct( { $hasher:expr, $($key:expr => $value:expr),+ } => { @@ -23,10 +35,7 @@ macro_rules! objecthash_struct( let mut digests: Vec> = Vec::new(); $( - digests.push(objecthash_dict_entry!( - objecthash::digest(&String::from($key)), - objecthash::digest(&$value) - )); + digests.push(objecthash_struct_member!($key, $value)); )+ digests.sort(); From a1ff82271c5c147d54c0a314f2ac18d574cf4b6e Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Aug 2016 16:10:35 -0700 Subject: [PATCH 33/46] Bump version to 0.3.0 and update CHANGES.md --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 20e39d9..a891bf4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## 0.3.0 (2016-08-21) + +* objecthash_struct_member! macro + ## 0.2.2 (2016-08-21) * Coerce objecthash_struct! key names to Strings diff --git a/Cargo.toml b/Cargo.toml index 8ffb147..56b1807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objecthash" -version = "0.2.2" +version = "0.3.0" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" From 964a0e92c005b865b6b854482f9e2ff03da4812e Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 12:10:52 -0800 Subject: [PATCH 34/46] Support digests of dynamically sized types --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c7b58ec..2ecaa41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ impl AsRef<[u8]> for Digest { } #[cfg(feature = "objecthash-ring")] -pub fn digest(msg: &T) -> Digest { +pub fn digest(msg: &T) -> Digest { let mut hasher = hasher::default(); msg.objecthash(&mut hasher); From 85e08332fbdc825b8e97c3849a933bd2955d1aea Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 16:14:59 -0800 Subject: [PATCH 35/46] Always use Digest newtype --- src/hasher/ring.rs | 17 +++++++---------- src/lib.rs | 29 ++++++++++++++++++----------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index 6ab7bca..71c9725 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -1,26 +1,23 @@ extern crate ring; -use self::ring::digest; - +use Digest; use ObjectHasher; pub struct Hasher { - ctx: digest::Context, + ctx: ring::digest::Context, } impl Hasher { pub fn new() -> Hasher { - Hasher::with_algorithm(&digest::SHA256) + Hasher::with_algorithm(&ring::digest::SHA256) } - pub fn with_algorithm(alg: &'static digest::Algorithm) -> Hasher { - Hasher { ctx: digest::Context::new(&alg) } + pub fn with_algorithm(alg: &'static ring::digest::Algorithm) -> Hasher { + Hasher { ctx: ring::digest::Context::new(&alg) } } } impl ObjectHasher for Hasher { - type D = digest::Digest; - #[inline] fn output_len(&self) -> usize { self.ctx.algorithm.output_len @@ -41,8 +38,8 @@ impl ObjectHasher for Hasher { } #[inline] - fn finish(self) -> digest::Digest { - self.ctx.finish() + fn finish(self) -> Digest { + Digest::new(self.ctx.finish().as_ref()).unwrap() } } diff --git a/src/lib.rs b/src/lib.rs index 2ecaa41..9f9ce89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,22 @@ pub struct Digest { value: [u8; MAX_OUTPUT_LEN], } +impl Digest { + pub fn new(bytes: &[u8]) -> Result { + if bytes.len() > MAX_OUTPUT_LEN { + return Err(()); + } + + let mut digest_bytes = [0u8; MAX_OUTPUT_LEN]; + digest_bytes.copy_from_slice(bytes); + + Ok(Digest { + output_len: bytes.len(), + value: digest_bytes + }) + } +} + impl AsRef<[u8]> for Digest { #[inline(always)] fn as_ref(&self) -> &[u8] { @@ -69,23 +85,14 @@ impl AsRef<[u8]> for Digest { pub fn digest(msg: &T) -> Digest { let mut hasher = hasher::default(); msg.objecthash(&mut hasher); - - let output_len = hasher.output_len(); - let mut digest_bytes = [0u8; MAX_OUTPUT_LEN]; - digest_bytes.copy_from_slice(hasher.finish().as_ref()); - - Digest { - output_len: output_len, - value: digest_bytes, - } + hasher.finish() } pub trait ObjectHasher { - type D: AsRef<[u8]>; fn output_len(&self) -> usize; fn update(&mut self, bytes: &[u8]); fn update_nested(&mut self, nested: F) where F: Fn(&mut Self); - fn finish(self) -> Self::D; + fn finish(self) -> Digest; } pub trait ObjectHash { From 258ad23a3580c9001e0b522b852876467f45c8d9 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 16:21:43 -0800 Subject: [PATCH 36/46] Add DANGER: EXPERIMENTAL to README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 4fa384f..f53575a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,20 @@ A content hash algorithm which works across multiple encodings (JSON, Protobufs, This crate provides a Rust implementation of an algorithm [originally created by Ben Laurie](https://github.com/benlaurie/objecthash). +### Is it any good? + +[Yes.](http://news.ycombinator.com/item?id=3067434) + +### Is it "Production Ready™"? + +![DANGER: EXPERIMENTAL](https://raw.github.com/cryptosphere/cryptosphere/master/images/experimental.png) + +**No!** ObjectHash is an *experimental* algorithm, and is subject to change. Please do not depend on it yet. + +Additionally, this is a project of a cryptographic nature and has not received any expert review. + +Use at your own risk. + ## Installation You will need to select a supported cryptography library to use as ObjectHash's backend. The following backend libraries From a32457e04ed344d27835ceaa9b9ef033e350c8a4 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 16:49:08 -0800 Subject: [PATCH 37/46] New objecthash_member! macro Combines objecthash_dict_entry! and objecthash_struct_member! into a single macro. --- src/lib.rs | 28 +++++++++------------------- src/types.rs | 4 ++-- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9f9ce89..58b98f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,26 +4,16 @@ extern crate unicode_normalization; extern crate rustc_serialize; #[macro_export] -macro_rules! objecthash_dict_entry { - ($key:expr, $value:expr) => { +macro_rules! objecthash_member { + ($key:expr => $value:expr) => { { - let (key, value) = ($key, $value); - let mut result = Vec::with_capacity(key.as_ref().len() + value.as_ref().len()); - result.extend_from_slice(&key.as_ref()); - result.extend_from_slice(&value.as_ref()); - result - } - } -} + let key_digest = $crate::digest(&$key); + let value_digest = $crate::digest(&$value); + let mut result = Vec::with_capacity(key_digest.as_ref().len() + value_digest.as_ref().len()); -#[macro_export] -macro_rules! objecthash_struct_member { - ($key:expr, $value:expr) => { - { - objecthash_dict_entry!( - objecthash::digest(&String::from($key)), - objecthash::digest(&$value) - ) + result.extend_from_slice(key_digest.as_ref()); + result.extend_from_slice(value_digest.as_ref()); + result } } } @@ -35,7 +25,7 @@ macro_rules! objecthash_struct( let mut digests: Vec> = Vec::new(); $( - digests.push(objecthash_struct_member!($key, $value)); + digests.push(objecthash_member!($key, $value)); )+ digests.sort(); diff --git a/src/types.rs b/src/types.rs index 2a7c296..0eee383 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,7 +1,7 @@ use std; use std::collections::HashMap; -use {digest, ObjectHash, ObjectHasher}; +use {ObjectHash, ObjectHasher}; use unicode_normalization::UnicodeNormalization; @@ -41,7 +41,7 @@ impl ObjectHash for HashMap hasher.update(DICT_TAG); let mut digests: Vec> = self.iter() - .map(|(k, v)| objecthash_dict_entry!(digest(k), digest(v))) + .map(|(k, v)| objecthash_member!(*k => *v)) .collect(); digests.sort(); From 1f0ca0ab22cfc917aa0360a61b57da34adf90950 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 17:12:36 -0800 Subject: [PATCH 38/46] Run tests on nightly/Linux only --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d79466..c1eb536 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,10 @@ branches: - master rust: - - stable - - beta - nightly os: - linux - - osx notifications: irc: 'irc.freenode.org#cryptosphere' From 801aec86b227a9e7481044f5964983a0987f0c49 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 17:40:50 -0800 Subject: [PATCH 39/46] Macro fixups: take 2 - Move all macros to macros.rs - Remove auto-refing of macro params - Actually test objecthash_struct! --- src/lib.rs | 36 ++-------------------------------- src/macros.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/types.rs | 2 +- 3 files changed, 56 insertions(+), 35 deletions(-) create mode 100644 src/macros.rs diff --git a/src/lib.rs b/src/lib.rs index 58b98f3..c5ad60b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,40 +3,8 @@ extern crate unicode_normalization; #[cfg(test)] extern crate rustc_serialize; -#[macro_export] -macro_rules! objecthash_member { - ($key:expr => $value:expr) => { - { - let key_digest = $crate::digest(&$key); - let value_digest = $crate::digest(&$value); - let mut result = Vec::with_capacity(key_digest.as_ref().len() + value_digest.as_ref().len()); - - result.extend_from_slice(key_digest.as_ref()); - result.extend_from_slice(value_digest.as_ref()); - result - } - } -} - -#[macro_export] -macro_rules! objecthash_struct( - { $hasher:expr, $($key:expr => $value:expr),+ } => { - { - let mut digests: Vec> = Vec::new(); - - $( - digests.push(objecthash_member!($key, $value)); - )+ - - digests.sort(); - - $hasher.update(objecthash::types::DICT_TAG); - for value in &digests { - $hasher.update(&value); - } - } - }; -); +#[macro_use] +pub mod macros; pub mod hasher; pub mod types; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..017ff2e --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,53 @@ +#[macro_export] +macro_rules! objecthash_member { + ($key:expr => $value:expr) => { + { + let key_digest = $crate::digest($key); + let value_digest = $crate::digest($value); + let mut result = Vec::with_capacity(key_digest.as_ref().len() + value_digest.as_ref().len()); + + result.extend_from_slice(key_digest.as_ref()); + result.extend_from_slice(value_digest.as_ref()); + result + } + } +} + +#[macro_export] +macro_rules! objecthash_struct( + { $hasher:expr, $($key:expr => $value:expr),+ } => { + { + let mut digests: Vec> = Vec::new(); + + $( + digests.push(objecthash_member!($key => $value)); + )+ + + digests.sort(); + + $hasher.update($crate::types::DICT_TAG); + for value in &digests { + $hasher.update(&value); + } + } + }; +); + +#[cfg(test)] +#[cfg(feature = "objecthash-ring")] +mod tests { + use {hasher, ObjectHasher}; + use rustc_serialize::hex::ToHex; + + #[test] + fn objecthash_struct_test() { + let mut h = hasher::default(); + + objecthash_struct!(h, "foo" => &1); + + assert_eq!( + h.finish().as_ref().to_hex(), + "bf4c58f5e308e31e2cd64bdbf7a01b9b595a13602438be5e912c7d94f6d8177a" + ); + } +} diff --git a/src/types.rs b/src/types.rs index 0eee383..cb2e945 100644 --- a/src/types.rs +++ b/src/types.rs @@ -41,7 +41,7 @@ impl ObjectHash for HashMap hasher.update(DICT_TAG); let mut digests: Vec> = self.iter() - .map(|(k, v)| objecthash_member!(*k => *v)) + .map(|(k, v)| objecthash_member!(k => v)) .collect(); digests.sort(); From ded97c01a3669dbc35cffa242202878f84c104c9 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 17:52:24 -0800 Subject: [PATCH 40/46] Bump version to 0.4.0 and update CHANGES.md --- CHANGES.md | 15 +++++++++++---- Cargo.toml | 4 ++-- README.md | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a891bf4..ba5b60f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,18 +1,25 @@ +## 0.4.0 (2017-01-16) + +* Rename `objecthash_struct_member!` to `objecthash_member!` +* Do not automatically create references from objecthash macro args +* Remove associated digest type from ObjectHasher trait. Always use + objecthash::Digest instead + ## 0.3.0 (2016-08-21) -* objecthash_struct_member! macro +* `objecthash_struct_member!` macro ## 0.2.2 (2016-08-21) -* Coerce objecthash_struct! key names to Strings +* Coerce `objecthash_struct!` key names to Strings ## 0.2.1 (2016-08-21) -* Bugfix for objecthash_struct! macro +* Bugfix for `objecthash_struct!` macro ## 0.2.0 (2016-08-21) -* Add objecthash_struct! and objecthash_dict_entry! macros +* Add `objecthash_struct!` and `objecthash_dict_entry!` macros ## 0.1.1 (2016-08-15) diff --git a/Cargo.toml b/Cargo.toml index 56b1807..b97169f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "objecthash" -version = "0.3.0" +version = "0.4.0" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" readme = "README.md" -keywords = ["hashing", "authenticity", "Merkle", "blockchain"] +keywords = ["hash", "digest", "signatures", "Merkle", "blockchain"] license = "Apache-2.0" authors = ["Tony Arcieri "] diff --git a/README.md b/README.md index f53575a..89f04df 100644 --- a/README.md +++ b/README.md @@ -113,5 +113,5 @@ impl ObjectHash for MyStruct { ## Copyright -Copyright (c) 2016 Tony Arcieri. Distributed under the Apache 2.0 License. +Copyright (c) 2016-2017 Tony Arcieri. Distributed under the Apache 2.0 License. See LICENSE file for further details. From 8a61c212e3612b167aae61a2237e4ee13aaf1368 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 17:57:34 -0800 Subject: [PATCH 41/46] Implement Default for ring::Hasher --- src/hasher/mod.rs | 2 +- src/hasher/ring.rs | 14 ++++++++------ src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/hasher/mod.rs b/src/hasher/mod.rs index 2cc88a3..1cbdf25 100644 --- a/src/hasher/mod.rs +++ b/src/hasher/mod.rs @@ -4,5 +4,5 @@ pub mod ring; // TODO: Use std::hash::BuildHasherDefault or our own similar version #[cfg(feature = "objecthash-ring")] pub fn default() -> ring::Hasher { - ring::Hasher::new() + ring::Hasher::default() } diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index 71c9725..eac45f9 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -8,12 +8,14 @@ pub struct Hasher { } impl Hasher { - pub fn new() -> Hasher { - Hasher::with_algorithm(&ring::digest::SHA256) + pub fn new(alg: &'static ring::digest::Algorithm) -> Hasher { + Hasher { ctx: ring::digest::Context::new(&alg) } } +} - pub fn with_algorithm(alg: &'static ring::digest::Algorithm) -> Hasher { - Hasher { ctx: ring::digest::Context::new(&alg) } +impl Default for Hasher { + fn default() -> Self { + Self::new(&ring::digest::SHA256) } } @@ -32,7 +34,7 @@ impl ObjectHasher for Hasher { fn update_nested(&mut self, nested: F) where F: Fn(&mut Self) { - let mut nested_hasher = Hasher::with_algorithm(&self.ctx.algorithm); + let mut nested_hasher = Hasher::new(&self.ctx.algorithm); nested(&mut nested_hasher); self.update(nested_hasher.finish().as_ref()); } @@ -56,7 +58,7 @@ mod tests { #[test] fn sha256() { - let mut hasher = Hasher::new(); + let mut hasher = Hasher::default(); hasher.update(SHA256_VECTOR_STRING.as_bytes()); assert_eq!(hasher.finish().as_ref().to_hex(), SHA256_VECTOR_DIGEST); } diff --git a/src/lib.rs b/src/lib.rs index c5ad60b..c208791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ impl Digest { Ok(Digest { output_len: bytes.len(), - value: digest_bytes + value: digest_bytes, }) } } From 6ef88cb7e5dd54b3b6c9a52519165eafb66d9b32 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 17:58:46 -0800 Subject: [PATCH 42/46] Minor clippy fixups --- src/hasher/ring.rs | 4 ++-- src/lib.rs | 2 +- src/types.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hasher/ring.rs b/src/hasher/ring.rs index eac45f9..0d5077f 100644 --- a/src/hasher/ring.rs +++ b/src/hasher/ring.rs @@ -9,7 +9,7 @@ pub struct Hasher { impl Hasher { pub fn new(alg: &'static ring::digest::Algorithm) -> Hasher { - Hasher { ctx: ring::digest::Context::new(&alg) } + Hasher { ctx: ring::digest::Context::new(alg) } } } @@ -34,7 +34,7 @@ impl ObjectHasher for Hasher { fn update_nested(&mut self, nested: F) where F: Fn(&mut Self) { - let mut nested_hasher = Hasher::new(&self.ctx.algorithm); + let mut nested_hasher = Hasher::new(self.ctx.algorithm); nested(&mut nested_hasher); self.update(nested_hasher.finish().as_ref()); } diff --git a/src/lib.rs b/src/lib.rs index c208791..92f42f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ impl Digest { } impl AsRef<[u8]> for Digest { - #[inline(always)] + #[inline] fn as_ref(&self) -> &[u8] { &self.value[..self.output_len] } diff --git a/src/types.rs b/src/types.rs index cb2e945..a491bf7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -47,7 +47,7 @@ impl ObjectHash for HashMap digests.sort(); for value in &digests { - hasher.update(&value); + hasher.update(value); } } } From 4eb09d9a729503e9e93474b01f22a9cb2eaceada Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 18:12:33 -0800 Subject: [PATCH 43/46] Bump version to 0.4.1 and update CHANGES.md --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ba5b60f..a9ce717 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +## 0.4.1 (2017-01-16) + +* Clippy fixups + ## 0.4.0 (2017-01-16) * Rename `objecthash_struct_member!` to `objecthash_member!` diff --git a/Cargo.toml b/Cargo.toml index b97169f..c1f974b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objecthash" -version = "0.4.0" +version = "0.4.1" description = "A content hashing algorithm which works across multiple encodings (JSON, Protobufs, etc)" homepage = "https://github.com/cryptosphere/rust-objecthash" repository = "https://github.com/cryptosphere/rust-objecthash" From 5492123c543c5b0ad7301bbd2e27afb2b82793af Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 16 Jan 2017 18:16:27 -0800 Subject: [PATCH 44/46] Better emphasize link back to the original project --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 89f04df..477b8b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ A content hash algorithm which works across multiple encodings (JSON, Protobufs, etc). -This crate provides a Rust implementation of an algorithm [originally created by Ben Laurie](https://github.com/benlaurie/objecthash). +This crate provides a Rust implementation of an algorithm originally created by Ben Laurie: + +https://github.com/benlaurie/objecthash ### Is it any good? From c2cec3e67ac0a005b258487f318022ec9a988470 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 11 Nov 2017 14:21:13 -0800 Subject: [PATCH 45/46] rust: Prepare to merge into benlaurie/objecthash --- .gitignore | 2 -- .travis.yml | 15 --------------- CHANGES.md => rust/CHANGES.md | 0 Cargo.toml => rust/Cargo.toml | 0 README.md => rust/README.md | 0 {src => rust/src}/hasher/mod.rs | 0 {src => rust/src}/hasher/ring.rs | 0 {src => rust/src}/lib.rs | 0 {src => rust/src}/macros.rs | 0 {src => rust/src}/types.rs | 0 10 files changed, 17 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml rename CHANGES.md => rust/CHANGES.md (100%) rename Cargo.toml => rust/Cargo.toml (100%) rename README.md => rust/README.md (100%) rename {src => rust/src}/hasher/mod.rs (100%) rename {src => rust/src}/hasher/ring.rs (100%) rename {src => rust/src}/lib.rs (100%) rename {src => rust/src}/macros.rs (100%) rename {src => rust/src}/types.rs (100%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a9d37c5..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c1eb536..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: rust -sudo: false - -branches: - only: - - master - -rust: - - nightly - -os: - - linux - -notifications: - irc: 'irc.freenode.org#cryptosphere' diff --git a/CHANGES.md b/rust/CHANGES.md similarity index 100% rename from CHANGES.md rename to rust/CHANGES.md diff --git a/Cargo.toml b/rust/Cargo.toml similarity index 100% rename from Cargo.toml rename to rust/Cargo.toml diff --git a/README.md b/rust/README.md similarity index 100% rename from README.md rename to rust/README.md diff --git a/src/hasher/mod.rs b/rust/src/hasher/mod.rs similarity index 100% rename from src/hasher/mod.rs rename to rust/src/hasher/mod.rs diff --git a/src/hasher/ring.rs b/rust/src/hasher/ring.rs similarity index 100% rename from src/hasher/ring.rs rename to rust/src/hasher/ring.rs diff --git a/src/lib.rs b/rust/src/lib.rs similarity index 100% rename from src/lib.rs rename to rust/src/lib.rs diff --git a/src/macros.rs b/rust/src/macros.rs similarity index 100% rename from src/macros.rs rename to rust/src/macros.rs diff --git a/src/types.rs b/rust/src/types.rs similarity index 100% rename from src/types.rs rename to rust/src/types.rs From f0f7b2c237453db3c6de75b7a78d7db7d6cb5ccd Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 11 Nov 2017 14:25:37 -0800 Subject: [PATCH 46/46] .travis.yml: Rust configuration --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f8bfc42..cdcac7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,3 +16,8 @@ matrix: - cd ruby - bundle - bundle exec rake + - language: rust + rust: stable + script: + - cd rust + - cargo test