Golang program to implement a Merkle tree


A merkle tree is used in cryptography and computer science to verify the authenticity of a data efficiently. Merkle trees are extremely crucial in the fields of cryptography and blockchain technology for ensuring data integrity and security. In this article, we will learn the significance of Merkle trees and to implement a merkle tree in go. Here we are going to use two different examples to perform this implementation in the first example we will build the Merkle tree recursively through the `MerkleNode` struct with hash, left, and right pointer, and in the second example we will employ an iterative method for building the tree.

Explanation

A Merkle tree (also hash tree) is a fundamental data structure employed in efficient verification of integrity of large datasets. By breaking down the data into smaller chunks, hashing them, and then combining these hashes iteratively a single root hash is obtained for this purpose.

Visualiazation

       Root Hash
         /    \
    Hash 1    Hash 2
     / \        / \
Data 1 Data 2 Data 3 Data 4

Here we have a root hash, we have 4 data elements, data1, data2,data3 and data4. The root hash represents the complete merkel tree, we also have hash 1 and hash2 so if any of the data change the hash value at that level will also change, which leads to the change in the root hash. This enables you to validate the data integrity.

Syntax

func buildMerkleTree(data []string) *MerkleNode

The syntax defines a function named buildMerkleTree defined to take an array of strings as input and recursively construct a Merkle tree. By dividing the data into pairs to be hashed and combined continuously until a single node is created, it returns a pointer to this root node.

func calculateHash(data string) string

The syntax defines a function named calculateHash defined to accept a string as input and employs the SHA-256 hashing algorithm to process the data, transforming it into a fixed-length hexadecimal hash representation.

Algorithm

  • Start by defining a structure for the Merkle tree node.

  • Create a function for the computation of hash of a given data chunk.

  • Build leaf nodes from data chunks and calculate their respective hashes.

  • Pair up and combine hashes iteratively for creating the parent nodes.

  • Continue with the merging of nodes until a single root hash is obtained.

Example 1

In this example, a Merkle tree is recursively build through the `MerkleNode` struct with hash, left, and right pointers. We defined `calculateHash` function that computes SHA-256 hash of a given input. The `buildMerkleTree` function then constructs the tree from the given input data, recursively dividing and hashing data pairs. Finally the `printTree` function visually represents the tree's structure. In the `main` function, data chunks are used to build the Merkle tree, and the root hash is displayed.

package main
import (
    "crypto/sha256"
    "fmt"
)
type MerkleNode struct {
	Hash  string
	Left  *MerkleNode
	Right *MerkleNode
}
func calculateHash(data string) string {
	hash := sha256.Sum256([]byte(data))
	return fmt.Sprintf("%x", hash)
}
func buildMerkleTree(data []string) *MerkleNode {
    if len(data) == 1 {
    	return &MerkleNode{Hash: calculateHash(data[0]), Left: nil, Right: nil}
    }
    mid := len(data) / 2
    left := buildMerkleTree(data[:mid])
    right := buildMerkleTree(data[mid:])
    return &MerkleNode{Hash: calculateHash(left.Hash + right.Hash), Left: left, Right: right}
}
func printTree(node *MerkleNode, indent string) {
    if node != nil {
        fmt.Println(indent+"Hash:", node.Hash)
        if node.Left != nil {
        	printTree(node.Left, indent+"  ")
        }
        if node.Right != nil {
            printTree(node.Right, indent+"  ")
        }
    }
}
func main() {
    data := []string{"A", "B", "C", "D"}
    root := buildMerkleTree(data)
    printTree(root, "")
    fmt.Println("Root Hash:", root.Hash)
}

Output

Hash: 50a504831bd50fee3581d287168a85a8dcdd6aa777ffd0fe35e37290268a0153
  Hash: b30ab174f7459cdd40a3acdf15d0c9444fec2adcfb9d579aa154c084885edd0a
	Hash: 559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd
	Hash: df7e70e5021544f4834bbee64a9e3789febc4be81470df629cad6ddb03320a5c
  Hash: 26b5aabe804fe5d533c663dea833e8078188376ce5ca2b5c3371d09ef6b0657b
	Hash: 6b23c0d5f35d1b11f9b683f0b0a617355deb11277d91ae091d399c655b87940d
	Hash: 3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43
Root Hash: 50a504831bd50fee3581d287168a85a8dcdd6aa777ffd0fe35e37290268a0153

Example 2

In this example, we employ an iterative method to implement a merkle tree in go, using the `MerkleNode` struct represents nodes. Here, nodes are combined step by step instead of using recursive division. The `calculateHash` function is used to hash the input data. The `buildMerkleTree` function forms the tree by iterating through data, creating leaf nodes, finally pairing and hashing nodes to generating parent nodes iteratively. The `printTree` function finally displays the created tree's structure.

package main
import (
    "crypto/sha256"
    "fmt"
)
type MerkleNode struct {
	Hash  string
    Left  *MerkleNode
	Right *MerkleNode
}
func calculateHash(data string) string {
    hash := sha256.Sum256([]byte(data))
    return fmt.Sprintf("%x", hash)
}
func buildMerkleTree(data []string) *MerkleNode {
    var nodes []*MerkleNode
    for _, d := range data {
        node := &MerkleNode{Hash: calculateHash(d)}
        nodes = append(nodes, node)
    }
    for len(nodes) > 1 {
        var newLevel []*MerkleNode
        for i := 0; i < len(nodes); i += 2 {
            left := nodes[i]
            right := left
            if i+1 < len(nodes) {
                right = nodes[i+1]
            }
            parent := &MerkleNode{Hash: calculateHash(left.Hash + right.Hash), Left: left, Right: right}
            newLevel = append(newLevel, parent)
        }
        nodes = newLevel
    }
    return nodes[0]
}
func printTree(node *MerkleNode, indent string) {
    if node != nil {
        fmt.Println(indent + "Hash:", node.Hash)
        if node.Left != nil {
            printTree(node.Left, indent+"  ")
        }
        if node.Right != nil {
            printTree(node.Right, indent+"  ")
        }
	}
}
func main() {
    data := []string{"A", "B", "C", "D"}
    root := buildMerkleTree(data)
    printTree(root, "")
    fmt.Println("Root Hash:", root.Hash)
}

Output

Hash: 50a504831bd50fee3581d287168a85a8dcdd6aa777ffd0fe35e37290268a0153
  Hash: b30ab174f7459cdd40a3acdf15d0c9444fec2adcfb9d579aa154c084885edd0a
	Hash: 559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd
	Hash: df7e70e5021544f4834bbee64a9e3789febc4be81470df629cad6ddb03320a5c
  Hash: 26b5aabe804fe5d533c663dea833e8078188376ce5ca2b5c3371d09ef6b0657b
	Hash: 6b23c0d5f35d1b11f9b683f0b0a617355deb11277d91ae091d399c655b87940d
	Hash: 3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43
Root Hash: 50a504831bd50fee3581d287168a85a8dcdd6aa777ffd0fe35e37290268a0153

Real Life Implementation

  • Digital Certificates: Certificate Authorities employ Merkle trees inside their systems to manage and validate digital certificates. Certificate Authorities are in charge of providing certificates that provide secure internet connection. Merkle trees are used to generate certificate revocation lists (CRLs) and online certificate status protocol (OCSP) responses, which allow clients to validate a certificate's revocation status.

  • Logistics Management: Merkle trees are an excellent way to monitor and track the ancestry and validity of different objects in the field of supply chain management. A product's whole history may be confirmed by portraying each step of its journey as a node in a Merkle tree.

Conclusion

A Merkle tree is defined as a cryptographic structure that ensures data integrity by hashing smaller portions of data and then hierarchically combining them to form a single root hash. In this article, we tried to implement a Merkle tree in Go lang using 2 methods – namely the recursive and iterative approaches. The former highlights the self-similar nature of Merkle trees which makes it conceptually clear and intuitive. However, a loop-based mechanism that consumes fewer system resources is more appropriate for handling large datasets and avoiding stack-overflow issues.

Updated on: 18-Oct-2023

152 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements