Merkle
Merkle: content-addressed hash tree.
Each leaf stores a value and the SHA-256 hash of its byte representation. Each branch stores the SHA-256 hash of (left_hash ++ right_hash). The root hash summarises the entire tree; comparing root hashes detects any difference in O(1), while diff() localises changes in O(k log n) by recursing only into subtrees whose hashes differ.
build/2 constructs a balanced tree from a list of items in O(n). The caller supplies a to_bytes : a -> String function for leaf hashing. Hashes are lowercase hex SHA-256 strings (Crypto.sha256).
Types
A node: MLeaf(sha256_hex, value) or MBranch(sha256_hex, left, right).
Functions
A branch over l and r, whose hash is the SHA-256 of the two children's root hashes concatenated (left first, as hex strings).
Examples
march> Merkle.size(Merkle.branch(Merkle.leaf(1, int_to_string), Merkle.leaf(2, int_to_string)))
2
march> Merkle.root_hash(Merkle.branch(Merkle.leaf("a", fn s -> s), Merkle.leaf("b", fn s -> s))) == Crypto.sha256(Crypto.sha256("a") ++ Crypto.sha256("b"))
trueBuild a tree from items, hashing each with to_bytes. Leaves are paired left to right, layer by layer; an odd node at the end of a layer is carried up unpaired. Returns None for an empty list. O(n).
Examples
march> Merkle.build([], int_to_string)
None
march> Option.map(Merkle.build([1, 2, 3], int_to_string), Merkle.leaves)
Some([1, 2, 3])True when any node of the tree, leaf or branch, has hash h. A branch match stops the search, so this also answers "is this subtree present". O(n) in the worst case.
Examples
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> Merkle.contains_hash(mt1, Crypto.sha256("2"))
true
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> Merkle.contains_hash(mt1, Merkle.root_hash(mt1))
true
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> Merkle.contains_hash(mt1, "nope")
falseThe hashes, taken from t1, of the leaves where t1 and t2 differ, left to right. Subtrees whose hashes match are skipped, so for k changed leaves this is O(k log n). Meant for trees of the same shape (built from lists of the same length); where the shapes diverge it reports t1's node at that point, which for a branch is a branch hash, not a leaf hash.
Examples
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> let mt2 = Option.unwrap(Merkle.build([1, 2, 30, 4], int_to_string))
march> Merkle.diff(mt1, mt1)
[]
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> let mt2 = Option.unwrap(Merkle.build([1, 2, 30, 4], int_to_string))
march> Merkle.diff(mt1, mt2) == [Merkle.root_hash(Merkle.leaf(3, int_to_string))]
trueA single-leaf tree holding x, whose hash is Crypto.sha256(to_bytes(x)).
Examples
march> Merkle.leaves(Merkle.leaf(42, int_to_string))
[42]
march> Merkle.root_hash(Merkle.leaf("abc", fn s -> s)) == Crypto.sha256("abc")
trueAll leaf hashes, left to right.
Examples
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> Merkle.leaf_hashes(mt1) == List.map([1, 2, 3, 4], fn x -> Crypto.sha256(int_to_string(x)))
trueAll values stored at the leaves, left to right (the order given to build).
Examples
march> let mt2 = Option.unwrap(Merkle.build([1, 2, 30, 4], int_to_string))
march> Merkle.leaves(mt2)
[1, 2, 30, 4]The hash stored at the root: the leaf's hash for a leaf, the branch hash otherwise. Two trees built the same way from equal leaves have equal root hashes. O(1).
Examples
march> String.byte_size(Merkle.root_hash(Merkle.leaf("abc", fn s -> s)))
64Number of leaves in the tree. O(n).
Examples
march> let mt1 = Option.unwrap(Merkle.build([1, 2, 3, 4], int_to_string))
march> Merkle.size(mt1)
4