Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Commit

Permalink
Merge pull request #292 from tendermint/bucky/merkle
Browse files Browse the repository at this point in the history
Implement the Tendermint Merkle tree (RFC6962)
  • Loading branch information
tarcieri authored Jul 16, 2019
2 parents f4ddd3b + 2e8d419 commit 04cd14b
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
1 change: 1 addition & 0 deletions tendermint-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub mod evidence;
#[cfg(feature = "rpc")]
pub mod genesis;
pub mod hash;
pub mod merkle;
mod moniker;
pub mod net;
pub mod node;
Expand Down
116 changes: 116 additions & 0 deletions tendermint-rs/src/merkle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Merkle tree used in Tendermint networks
use sha2::{Digest, Sha256};

/// Size of Merkle root hash
pub const HASH_SIZE: usize = 32;

/// Compute a simple Merkle root from the arbitrary sized byte slices
pub fn simple_hash_from_byte_slices(byte_slices: &[&[u8]]) -> [u8; HASH_SIZE] {
let length = byte_slices.len();
match length {
0 => [0; HASH_SIZE],
1 => leaf_hash(byte_slices[0]),
_ => {
let k = get_split_point(length);
let left = simple_hash_from_byte_slices(&byte_slices[..k]);
let right = simple_hash_from_byte_slices(&byte_slices[k..]);
inner_hash(&left, &right)
}
}
}

// returns the largest power of 2 less than length
fn get_split_point(length: usize) -> usize {
match length {
0 => panic!("tree is empty!"),
1 => panic!("tree has only one element!"),
2 => 1,
_ => length.next_power_of_two() / 2,
}
}

// tmhash(0x00 || leaf)
fn leaf_hash(bytes: &[u8]) -> [u8; HASH_SIZE] {
// make a new array starting with 0 and copy in the bytes
let mut leaf_bytes = Vec::with_capacity(bytes.len() + 1);
leaf_bytes.push(0x00);
leaf_bytes.extend_from_slice(bytes);

// hash it !
let digest = Sha256::digest(&leaf_bytes);

// copy the GenericArray out
let mut hash_bytes = [0u8; HASH_SIZE];
hash_bytes.copy_from_slice(&digest);
hash_bytes
}

// tmhash(0x01 || left || right)
fn inner_hash(left: &[u8], right: &[u8]) -> [u8; HASH_SIZE] {
// make a new array starting with 0x1 and copy in the bytes
let mut inner_bytes = Vec::with_capacity(left.len() + right.len() + 1);
inner_bytes.push(0x01);
inner_bytes.extend_from_slice(left);
inner_bytes.extend_from_slice(right);

// hash it !
let digest = Sha256::digest(&inner_bytes);

// copy the GenericArray out
let mut hash_bytes = [0u8; HASH_SIZE];
hash_bytes.copy_from_slice(&digest);
hash_bytes
}

#[cfg(test)]
mod tests {
use super::*;
use subtle_encoding::hex; // TODO: use non-subtle ?

#[test]
fn test_get_split_point() {
assert_eq!(get_split_point(2), 1);
assert_eq!(get_split_point(3), 2);
assert_eq!(get_split_point(4), 2);
assert_eq!(get_split_point(5), 4);
assert_eq!(get_split_point(10), 8);
assert_eq!(get_split_point(20), 16);
assert_eq!(get_split_point(100), 64);
assert_eq!(get_split_point(255), 128);
assert_eq!(get_split_point(256), 128);
assert_eq!(get_split_point(257), 256);
}

#[test]
fn test_rfc6962_empty_leaf() {
let empty_leaf_root_hex =
"6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d";
let empty_leaf_root = &hex::decode(empty_leaf_root_hex).unwrap();
let empty_tree: &[&[u8]] = &[&[]];
let root = simple_hash_from_byte_slices(empty_tree);
assert_eq!(empty_leaf_root, &root);
}

#[test]
fn test_rfc6962_leaf() {
let leaf_root_hex = "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56";
let leaf_string = "L123456";

let leaf_root = &hex::decode(leaf_root_hex).unwrap();
let leaf_tree: &[&[u8]] = &[leaf_string.as_bytes()];
let root = simple_hash_from_byte_slices(leaf_tree);
assert_eq!(leaf_root, &root);
}

#[test]
fn test_rfc6962_node() {
let node_hash_hex = "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb";
let left_string = "N123";
let right_string = "N456";

let node_hash = &hex::decode(node_hash_hex).unwrap();
let hash = inner_hash(left_string.as_bytes(), right_string.as_bytes());
assert_eq!(node_hash, &hash);
}
}

0 comments on commit 04cd14b

Please sign in to comment.