Merkle Trees

Understanding Merkle trees and their use in efficient blockchain verification.

Intermediate⏱️ 45 min📚 Prerequisites: 1

Merkle Trees

A Merkle tree (also called a hash tree) is a data structure used in blockchains to efficiently verify large amounts of data.

Why Merkle Trees?

Instead of hashing all transactions together, we organize them in a tree:

  • Efficient verification: Verify a single transaction without downloading all data
  • Compact proofs: Small proof size even for many transactions
  • Parallel hashing: Can hash different branches simultaneously

Structure

        Root Hash
       /         \
   Hash(0-1)    Hash(2-3)
   /     \      /     \
 Hash0  Hash1 Hash2  Hash3
  Tx0    Tx1   Tx2    Tx3

How It Works

  1. Leaf nodes: Hash of each transaction
  2. Parent nodes: Hash of children concatenated
  3. Root: Final hash at the top (Merkle root)

Merkle Root in Blocks

RUST
struct Block {
    index: u64,
    transactions: Vec<Transaction>,
    merkle_root: String,  // Root of Merkle tree
    previous_hash: String,
    hash: String,
}

Benefits for Blockchain

  • Light clients: Can verify without full blockchain
  • SPV (Simplified Payment Verification): Bitcoin uses this
  • Efficient storage: Only need root hash in block header
  • Fast verification: O(log n) instead of O(n)

Implementation

RUST
// Simplified Merkle tree
fn merkle_root(transactions: &[String]) -> String {
    if transactions.is_empty() {
        return String::from("empty");
    }
    if transactions.len() == 1 {
        return hash(&transactions[0]);
    }
    // Recursively hash pairs
    // ...
}

Code Examples

Simple Merkle Tree

Basic Merkle tree structure

RUST
fn hash(data: &str) -> String {
    format!("hash_{}", data)
}
fn merkle_root(transactions: &[&str]) -> String {
    if transactions.is_empty() {
        return String::from("empty");
    }
    if transactions.len() == 1 {
        return hash(transactions[0]);
    }
    let combined: String = transactions.join("");
    hash(&combined)
}
fn main() {
    let transactions = vec!["tx1", "tx2", "tx3", "tx4"];
    let root = merkle_root(&transactions);
    println!("Merkle root: {}", root);
    println!("Number of transactions: {}", transactions.len());
}

Explanation:

This simplified version shows the concept. A real Merkle tree builds a binary tree structure, hashing pairs of nodes until reaching a single root.

Block with Merkle Root

Using Merkle root in block structure

RUST
struct Transaction {
    id: String,
    data: String,
}
struct Block {
    index: u64,
    transactions: Vec<Transaction>,
    merkle_root: String,
    previous_hash: String,
}
impl Block {
    fn calculate_merkle_root(&self) -> String {
        let tx_ids: Vec<String> = self.transactions
            .iter()
            .map(|tx| tx.id.clone())
            .collect();
        let combined = tx_ids.join("");
        format!("merkle_{}", combined)
    }
}
fn main() {
    let transactions = vec![
        Transaction { id: String::from("tx1"), data: String::from("data1") },
        Transaction { id: String::from("tx2"), data: String::from("data2") },
    ];
    let block = Block {
        index: 1,
        transactions,
        merkle_root: String::from(""),
        previous_hash: String::from("prev"),
    };
    let root = block.calculate_merkle_root();
    println!("Merkle root: {}", root);
}

Explanation:

The Merkle root is stored in the block header. It provides a single hash that represents all transactions, enabling efficient verification.

Exercises

Calculate Merkle Root

Create a function that calculates a Merkle root from transaction IDs!

Medium

Starter Code:

RUST
fn main() {
    let tx_ids = vec![
        String::from("tx1"),
        String::from("tx2"),
        String::from("tx3"),
    ];
}