summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2025-06-16 09:07:20 +0000
committerSanto Cariotti <santo@dcariotti.me>2025-06-16 09:07:20 +0000
commitbb3eadc0756062e82a6feb73d4bae5bd1cb18917 (patch)
treef1e89b1552e3a9e20f7b55a70d83262625362d37 /src
parentc470ba5f7b1a85bfd1cd96e5ec14d2f42a25ec55 (diff)
Use bytes instead of string for data values
Diffstat (limited to 'src')
-rw-r--r--src/hasher.rs14
-rw-r--r--src/merkle/merkletree.rs21
-rw-r--r--src/merkle/mod.rs32
-rw-r--r--src/merkle/node.rs19
4 files changed, 65 insertions, 21 deletions
diff --git a/src/hasher.rs b/src/hasher.rs
index a26dd3c..101b23c 100644
--- a/src/hasher.rs
+++ b/src/hasher.rs
@@ -7,8 +7,8 @@ use sha2::{Digest, Sha256};
///
/// This allows the Merkle tree to use any hash function that conforms to this interface.
pub trait Hasher {
- /// Hashes an input string and returns the resulting hash as a hexadecimal string.
- fn hash(&self, input: &str) -> String;
+ /// Hashes a sequence of bytes and returns the resulting hash as a hexadecimal string.
+ fn hash(&self, input: &[u8]) -> String;
}
/// A dummy hasher used for testing or demonstration purposes.
@@ -17,7 +17,7 @@ pub trait Hasher {
pub struct DefaultHasher;
impl Hasher for DefaultHasher {
- fn hash(&self, _input: &str) -> String {
+ fn hash(&self, _input: &[u8]) -> String {
"0xc0ff3".to_string()
}
}
@@ -28,9 +28,9 @@ pub struct SHA256Hasher;
#[cfg(feature = "sha256")]
impl Hasher for SHA256Hasher {
- fn hash(&self, input: &str) -> String {
+ fn hash(&self, input: &[u8]) -> String {
let mut hasher = Sha256::new();
- hasher.update(input.as_bytes());
+ hasher.update(input);
hex::encode(hasher.finalize())
}
}
@@ -42,7 +42,7 @@ mod tests {
#[test]
fn test_sha256_hasher_with_known_input() {
let hasher = SHA256Hasher;
- let input = "hello";
+ let input = "hello".as_bytes();
let expected_hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
let actual_hash = hasher.hash(input);
assert_eq!(actual_hash, expected_hash);
@@ -51,7 +51,7 @@ mod tests {
#[test]
fn test_sha256_hasher_empty_string() {
let hasher = SHA256Hasher;
- let input = "";
+ let input = &[];
let expected_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
let actual_hash = hasher.hash(input);
assert_eq!(actual_hash, expected_hash);
diff --git a/src/merkle/merkletree.rs b/src/merkle/merkletree.rs
index fc2879a..4a6a214 100644
--- a/src/merkle/merkletree.rs
+++ b/src/merkle/merkletree.rs
@@ -34,14 +34,21 @@ impl MerkleTree {
///
/// If the number of leaf nodes is odd, the last node is duplicated to ensure all internal
/// nodes have exactly two children.
- pub fn new<T: ToString>(hasher: &dyn Hasher, data: Vec<T>) -> Self {
+ pub fn new<I, T>(hasher: &dyn Hasher, data: I) -> Self
+ where
+ I: IntoIterator<Item = T>,
+ T: AsRef<[u8]>,
+ {
+ let owned_data: Vec<T> = data.into_iter().collect();
+ let data_slices: Vec<&[u8]> = owned_data.iter().map(|item| item.as_ref()).collect();
+
assert!(
- !data.is_empty(),
+ !data_slices.is_empty(),
"Merkle Tree requires at least one element"
);
- let mut leaves: Vec<Node> = data
- .into_iter()
+ let mut leaves: Vec<Node> = data_slices
+ .iter()
.map(|x| Node::new_leaf(hasher, x))
.collect();
@@ -58,9 +65,15 @@ impl MerkleTree {
let mut height = 0;
while nodes.len() > 1 {
+ if nodes.len() % 2 != 0 {
+ // duplicate last node to make the count even
+ nodes.push(nodes[nodes.len() - 1].clone());
+ }
+
let mut next_level = Vec::new();
for pair in nodes.chunks(2) {
let (left, right) = (pair[0].clone(), pair[1].clone());
+
next_level.push(Node::new_internal(hasher, left, right));
}
nodes = next_level;
diff --git a/src/merkle/mod.rs b/src/merkle/mod.rs
index 4a6e46f..d00e350 100644
--- a/src/merkle/mod.rs
+++ b/src/merkle/mod.rs
@@ -13,30 +13,30 @@ mod tests {
#[test]
fn test_merkle_tree_with_default_hasher() {
- let data = vec!["a", "b", "c", "d"];
+ let data = &["hello".as_bytes(), "world".as_bytes()];
let tree = merkletree::MerkleTree::new(&DefaultHasher, data);
- assert_eq!(tree.height(), 3);
+ assert_eq!(tree.height(), 2);
assert_eq!(tree.root().hash(), "0xc0ff3");
}
#[test]
#[cfg(feature = "sha256")]
fn test_merkle_tree_hashing() {
- let data = vec!["a", "b", "c", "d"];
+ let data = &["hello".as_bytes(), "world".as_bytes()];
let tree = merkletree::MerkleTree::new(&SHA256Hasher, data);
- assert_eq!(tree.height(), 3);
+ assert_eq!(tree.height(), 2);
assert_eq!(
tree.root().hash(),
- "58c89d709329eb37285837b042ab6ff72c7c8f74de0446b091b6a0131c102cfd"
+ "15e178b71fae8849ee562c9cc0d7ea322fba6cd495411329d47234479167cc8b"
);
}
#[test]
#[cfg(feature = "sha256")]
fn test_merkle_tree_single_leaf() {
- let data = vec!["hello"];
+ let data = &["hello".as_bytes()];
let tree = merkletree::MerkleTree::new(&SHA256Hasher, data);
assert_eq!(tree.height(), 2);
@@ -46,4 +46,24 @@ mod tests {
"286d189fda11bf4e906b6973a173009f47ede16532f1bae726223f8ee155d73b"
);
}
+
+ #[test]
+ #[cfg(feature = "sha256")]
+ fn test_merkle_tree_with_10_elements() {
+ let inputs = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
+ let data: Vec<&[u8]> = inputs.iter().map(|s| s.as_bytes()).collect();
+
+ let tree = merkletree::MerkleTree::new(&SHA256Hasher, &data);
+
+ assert_eq!(tree.height(), 5); // 10 elements padded to 16 → log2(16) + 1 = 5
+
+ // You can print the root hash if you're unsure what it should be:
+ println!("Merkle root hash: {}", tree.root().hash());
+
+ // If you know the expected hash, use:
+ assert_eq!(
+ tree.root().hash(),
+ "9da1ff0dfa79217bdbea9ec96407b1e693646cc493f64059fa27182a37cadf94"
+ );
+ }
}
diff --git a/src/merkle/node.rs b/src/merkle/node.rs
index 0e5e904..021b1c3 100644
--- a/src/merkle/node.rs
+++ b/src/merkle/node.rs
@@ -36,6 +36,8 @@ pub struct Node {
hash: String,
/// Type of the node: leaf or internal.
kind: NodeType,
+ /// Data in bytes.
+ data: Vec<u8>,
}
impl Node {
@@ -45,10 +47,11 @@ impl Node {
///
/// * `hasher` - A reference to a hashing strategy.
/// * `data` - The data to be hashed and stored as a leaf.
- pub fn new_leaf<T: ToString>(hasher: &dyn Hasher, data: T) -> Self {
- let hash = hasher.hash(&data.to_string());
+ pub fn new_leaf(hasher: &dyn Hasher, data: &[u8]) -> Self {
+ let hash = hasher.hash(&data);
Self {
hash,
+ data: data.to_vec(),
kind: NodeType::Leaf,
}
}
@@ -65,10 +68,13 @@ impl Node {
///
/// The internal node hash is computed as the hash of the concatenated children's hashes.
pub fn new_internal(hasher: &dyn Hasher, left: Node, right: Node) -> Self {
- let combined = format!("{}{}", left.hash, right.hash);
- let hash = hasher.hash(&combined);
+ let mut buffer = Vec::<u8>::new();
+ buffer.extend_from_slice(left.hash().as_bytes());
+ buffer.extend_from_slice(right.hash().as_bytes());
+ let hash = hasher.hash(&buffer);
Self {
hash,
+ data: buffer,
kind: NodeType::Internal(Box::new(left), Box::new(right)),
}
}
@@ -78,6 +84,11 @@ impl Node {
&self.hash
}
+ /// Returns the data value in bytes format.
+ pub fn data(&self) -> &[u8] {
+ &self.data
+ }
+
/// Returns a reference to the node's type (leaf or internal).
pub fn kind(&self) -> &NodeType {
&self.kind