diff options
| author | Santo Cariotti <santo@dcariotti.me> | 2025-06-16 09:07:20 +0000 |
|---|---|---|
| committer | Santo Cariotti <santo@dcariotti.me> | 2025-06-16 09:07:20 +0000 |
| commit | bb3eadc0756062e82a6feb73d4bae5bd1cb18917 (patch) | |
| tree | f1e89b1552e3a9e20f7b55a70d83262625362d37 | |
| parent | c470ba5f7b1a85bfd1cd96e5ec14d2f42a25ec55 (diff) | |
Use bytes instead of string for data values
| -rw-r--r-- | src/hasher.rs | 14 | ||||
| -rw-r--r-- | src/merkle/merkletree.rs | 21 | ||||
| -rw-r--r-- | src/merkle/mod.rs | 32 | ||||
| -rw-r--r-- | src/merkle/node.rs | 19 |
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 |
