Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
506ef6c
Initial commit
tarcieri Aug 8, 2016
5fa5127
Fix README.md banner
tarcieri Aug 8, 2016
bad137e
Prep for 0.1.0 release
tarcieri Aug 8, 2016
8cbc883
Flag features that depend on a hasher backend
tarcieri Aug 8, 2016
c1dc277
Fix extra colon in README
tarcieri Aug 8, 2016
c74ace0
Temporarily comment out *ring* dependency
tarcieri Aug 8, 2016
ac291f2
Add version constraint on rustc-serialize
tarcieri Aug 8, 2016
a41f626
Revert "Temporarily comment out *ring* dependency"
tarcieri Aug 8, 2016
8cd17f7
Fix crate links in README.md
tarcieri Aug 8, 2016
e9b73a8
Remove Vec<u8> from ObjectHash trait
tarcieri Aug 8, 2016
608ae25
Merge pull request #1 from cryptosphere/remove-vecs-from-trait
tarcieri Aug 8, 2016
88e1fff
str support
tarcieri Aug 9, 2016
396e451
Merge pull request #2 from cryptosphere/str
tarcieri Aug 9, 2016
6c8c909
Remove redundant test
tarcieri Aug 9, 2016
6e57d24
Vec<T> support
tarcieri Aug 11, 2016
73947c4
Merge pull request #3 from cryptosphere/vectors
tarcieri Aug 11, 2016
f944a82
Make objecthash-ring a default feature
tarcieri Aug 11, 2016
95522fe
Merge pull request #4 from cryptosphere/ring-by-default
tarcieri Aug 11, 2016
2102d28
Export objecthash_digest! macro
tarcieri Aug 11, 2016
067d147
Add comment about std::hash::BuildHasherDefault
tarcieri Aug 11, 2016
0c41fa8
Add #[inline] to all implementations
tarcieri Aug 11, 2016
af17ea5
[u8] support
tarcieri Aug 11, 2016
82e511d
Merge pull request #5 from cryptosphere/octet-strings
tarcieri Aug 11, 2016
30c3d76
Hasher::write -> update, update_nested
tarcieri Aug 12, 2016
fc9ee65
Merge pull request #6 from cryptosphere/update-nested
tarcieri Aug 12, 2016
216a8e6
Digest type
tarcieri Aug 12, 2016
5992ee7
Merge pull request #7 from cryptosphere/zero-copy
tarcieri Aug 12, 2016
db2a167
HashMap (and String) support
tarcieri Aug 12, 2016
ff77df6
Merge pull request #8 from cryptosphere/hashmap-and-string
tarcieri Aug 12, 2016
648f3db
Make 'types' module public
tarcieri Aug 13, 2016
d9b0c9d
Use the *ring* crate
tarcieri Aug 16, 2016
4a22e21
Bump version to 0.1.1 and update CHANGES.md
tarcieri Aug 16, 2016
2d63b26
Fix README URLs
tarcieri Aug 17, 2016
2da0d75
objecthash_struct! and objecthash_dict_entry! macros
tarcieri Aug 21, 2016
a02ea55
Merge pull request #9 from cryptosphere/struct-macros
tarcieri Aug 21, 2016
3e22897
Bump version to 0.2.0 and update CHANGES.md
tarcieri Aug 21, 2016
a3c0f46
Fix objecthash_struct macro
tarcieri Aug 21, 2016
f6a9163
Bump version to 0.2.1 and update CHANGES.md
tarcieri Aug 21, 2016
c078c87
Coerce struct key names to Strings
tarcieri Aug 21, 2016
10cd417
Bump version to 0.2.2 and update CHANGES.md
tarcieri Aug 21, 2016
bf5d415
objecthash_struct_member! macro
tarcieri Aug 21, 2016
a1ff822
Bump version to 0.3.0 and update CHANGES.md
tarcieri Aug 21, 2016
964a0e9
Support digests of dynamically sized types
tarcieri Jan 16, 2017
65f3c5c
Merge pull request #10 from cryptosphere/dst-support
tarcieri Jan 16, 2017
85e0833
Always use Digest newtype
tarcieri Jan 17, 2017
d92bfe6
Merge pull request #11 from cryptosphere/digest-newtype
tarcieri Jan 17, 2017
258ad23
Add DANGER: EXPERIMENTAL to README
tarcieri Jan 17, 2017
a32457e
New objecthash_member! macro
tarcieri Jan 17, 2017
1f0ca0a
Run tests on nightly/Linux only
tarcieri Jan 17, 2017
aba91f9
Merge pull request #12 from cryptosphere/simplify-macros
tarcieri Jan 17, 2017
801aec8
Macro fixups: take 2
tarcieri Jan 17, 2017
a161564
Merge pull request #13 from cryptosphere/macros-take-2
tarcieri Jan 17, 2017
ded97c0
Bump version to 0.4.0 and update CHANGES.md
tarcieri Jan 17, 2017
8a61c21
Implement Default for ring::Hasher
tarcieri Jan 17, 2017
6ef88cb
Minor clippy fixups
tarcieri Jan 17, 2017
11ea46e
Merge pull request #14 from cryptosphere/clippy-fixups
tarcieri Jan 17, 2017
4eb09d9
Bump version to 0.4.1 and update CHANGES.md
tarcieri Jan 17, 2017
5492123
Better emphasize link back to the original project
tarcieri Jan 17, 2017
c2cec3e
rust: Prepare to merge into benlaurie/objecthash
tarcieri Nov 11, 2017
0c032a5
Merge remote-tracking branch 'objecthash-rs/merge-prep' into rust
tarcieri Nov 11, 2017
f0f7b2c
.travis.yml: Rust configuration
tarcieri Nov 11, 2017
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
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ matrix:
- cd ruby
- bundle
- bundle exec rake
- language: rust
rust: stable
script:
- cd rust
- cargo test
35 changes: 35 additions & 0 deletions rust/CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## 0.4.1 (2017-01-16)

* Clippy fixups

## 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

## 0.2.2 (2016-08-21)

* Coerce `objecthash_struct!` key names to Strings

## 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

## 0.1.1 (2016-08-15)

* Add the newly released *ring* crate as an optional dependency

## 0.1.0 (2016-08-08)

* Initial release

25 changes: 25 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "objecthash"
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"
readme = "README.md"
keywords = ["hash", "digest", "signatures", "Merkle", "blockchain"]
license = "Apache-2.0"
authors = ["Tony Arcieri <bascule@gmail.com>"]

[dependencies.unicode-normalization]
version = ">= 0.1.2"

[dependencies.ring]
optional = true
version = ">= 0.2"

[dev-dependencies.rustc-serialize]
version = ">= 0.3.19"

[features]
default = ["objecthash-ring"]
objecthash-ring = ["ring"]
octet-strings = []
119 changes: 119 additions & 0 deletions rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# 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/objecthash.svg
[crate-link]: https://crates.io/crates/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/objecthash-rs/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

### 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
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 work!

## 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<H: ObjectHasher>(&self, hasher: &mut H);
}
```

There are built-in implementations of the `ObjectHash` trait for the
following types:

* `Vec<T: ObjectHash>`
* `HashMap<K: ObjectHash, V: ObjectHash>`
* `str`
* `String`
* **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<u8> = 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ideally this would be replaced with a procedural macro, possibly even leveraging serde visitors

a dict-type hash across their keys and values:

```rust
impl ObjectHash for MyStruct {
#[inline]
fn objecthash<H: ObjectHasher>(&self, hasher: &mut H) {
objecthash_struct!(
hasher,
"foo" => self.foo,
"bar" => self.bar,
"baz" => self.baz
)
}
}
```

## TODO

* 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-2017 Tony Arcieri. Distributed under the Apache 2.0 License.
See LICENSE file for further details.
8 changes: 8 additions & 0 deletions rust/src/hasher/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[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::default()
}
65 changes: 65 additions & 0 deletions rust/src/hasher/ring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
extern crate ring;

use Digest;
use ObjectHasher;

pub struct Hasher {
ctx: ring::digest::Context,
}

impl Hasher {
pub fn new(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)
}
}

impl ObjectHasher for Hasher {
#[inline]
fn output_len(&self) -> usize {
self.ctx.algorithm.output_len
}

#[inline]
fn update(&mut self, bytes: &[u8]) {
self.ctx.update(bytes);
}

#[inline]
fn update_nested<F>(&mut self, nested: F)
where F: Fn(&mut Self)
{
let mut nested_hasher = Hasher::new(self.ctx.algorithm);
nested(&mut nested_hasher);
self.update(nested_hasher.finish().as_ref());
}

#[inline]
fn finish(self) -> Digest {
Digest::new(self.ctx.finish().as_ref()).unwrap()
}
}

#[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() {
let mut hasher = Hasher::default();
hasher.update(SHA256_VECTOR_STRING.as_bytes());
assert_eq!(hasher.finish().as_ref().to_hex(), SHA256_VECTOR_DIGEST);
}
}
72 changes: 72 additions & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
extern crate unicode_normalization;

#[cfg(test)]
extern crate rustc_serialize;

#[macro_use]
pub mod macros;

pub mod hasher;
pub mod types;

const MAX_OUTPUT_LEN: usize = 32;

pub struct Digest {
output_len: usize,
value: [u8; MAX_OUTPUT_LEN],
}

impl Digest {
pub fn new(bytes: &[u8]) -> Result<Digest, ()> {
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]
fn as_ref(&self) -> &[u8] {
&self.value[..self.output_len]
}
}

#[cfg(feature = "objecthash-ring")]
pub fn digest<T: ObjectHash + ?Sized>(msg: &T) -> Digest {
let mut hasher = hasher::default();
msg.objecthash(&mut hasher);
hasher.finish()
}

pub trait ObjectHasher {
fn output_len(&self) -> usize;
fn update(&mut self, bytes: &[u8]);
fn update_nested<F>(&mut self, nested: F) where F: Fn(&mut Self);
fn finish(self) -> Digest;
}

pub trait ObjectHash {
fn objecthash<H: ObjectHasher>(&self, hasher: &mut H);
}

#[cfg(test)]
#[cfg(feature = "objecthash-ring")]
mod tests {
use digest;
use rustc_serialize::hex::ToHex;

#[test]
fn digest_test() {
let result = digest(&1000);
assert_eq!(result.as_ref().to_hex(),
"a3346d18105ef801c3598fec426dcc5d4be9d0374da5343f6c8dcbdf24cb8e0b");
}
}
53 changes: 53 additions & 0 deletions rust/src/macros.rs
Original file line number Diff line number Diff line change
@@ -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<u8>> = 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"
);
}
}
Loading