Binary Merkle tree
BinaryMerkleTree<H> is a balanced binary tree over an ordered list of leaves. It stores leaves
and internal nodes in one flat, cache-friendly array, pads the leaf layer to a power of two, and
gives O(log n) inclusion proofs addressed by LeafIndex.
Use it for transaction batches, logs, file chunks, snapshot commitments, and Bitcoin-style SPV.
Create a tree
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::Sha256;
fn main() -> Result<(), MerkleError> {
// Empty tree, grows as you insert.
let mut tree = BinaryMerkleTree::<Sha256>::new();
assert!(tree.is_empty());
assert_eq!(tree.root(), None);
// Pre-size the leaf layer for at least 1,000 leaves (rounded up to 1,024).
// Reserving storage adds no logical leaves.
let reserved = BinaryMerkleTree::<Sha256>::with_capacity(1_000);
assert_eq!(reserved.leaf_count(), 0);
// Build from a known leaf set in one O(n) pass.
let batch = BinaryMerkleTree::<Sha256>::from_leaves([b"tx1".as_slice(), b"tx2", b"tx3"])?;
assert_eq!(batch.leaf_count(), 3);
tree.insert(b"tx1")?;
tree.insert(b"tx2")?;
tree.insert(b"tx3")?;
assert_eq!(tree.root(), batch.root());
Ok(())
}from_leaves accepts anything that iterates over AsRef<[u8]>: &str, &[u8], Vec<u8>,
String. It returns MerkleError::EmptyLeafData if any leaf is empty.
Insert and remove
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::Sha256;
fn main() -> Result<(), MerkleError> {
let mut tree = BinaryMerkleTree::<Sha256>::new();
// insert returns the stable index of the new leaf.
let alice = tree.insert(b"alice:100")?;
let bob = tree.insert(b"bob:250")?;
let carol = tree.insert(b"carol:75")?;
assert_eq!((alice, bob, carol), (LeafIndex(0), LeafIndex(1), LeafIndex(2)));
assert_eq!(tree.height(), 3); // 3 leaves pad to 4 slots: 4 -> 2 -> 1
// Removing an interior leaf replaces it with H::empty(); later indices stay put.
tree.remove(bob)?;
assert_eq!(tree.leaf_count(), 3);
// Removing the last leaf shrinks the logical leaf count.
tree.remove(carol)?;
assert_eq!(tree.leaf_count(), 1);
// Empty data is rejected so it can't be confused with padding.
assert_eq!(tree.insert(b""), Err(MerkleError::EmptyLeafData));
Ok(())
}Generate and verify proofs
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::Sha256;
fn main() -> Result<(), MerkleError> {
let txs = ["tx:alice->bob:100", "tx:bob->carol:50", "tx:carol->dave:25"];
let tree = BinaryMerkleTree::<Sha256>::from_leaves(txs)?;
let root = tree.root().expect("non-empty");
for (i, tx) in txs.iter().enumerate() {
let proof = tree.generate_proof(LeafIndex(i))?;
assert_eq!(proof.leaf_index, LeafIndex(i));
assert_eq!(proof.depth(), 2);
assert!(BinaryMerkleTree::<Sha256>::verify(root, tx.as_bytes(), &proof));
}
// Out-of-range indices are an error, not a panic.
assert_eq!(
tree.generate_proof(LeafIndex(9)).unwrap_err(),
MerkleError::IndexOutOfBounds { index: 9, len: 3 },
);
Ok(())
}verify is an associated function: it needs no tree instance. It returns false (never panics)
for empty leaf data, a wrong leaf, a tampered sibling, a path of the wrong length, or sides that
don't match the leaf index.
Height and padding
| Leaves | Padded slots | height() | Proof depth |
|---|---|---|---|
| 0 | 0 | 0 | – |
| 1 | 1 | 1 | 0 (trivial proof) |
| 2 | 2 | 2 | 1 |
| 3–4 | 4 | 3 | 2 |
| 5–8 | 8 | 4 | 3 |
| 1,000 | 1,024 | 11 | 10 |
A single-leaf tree has a trivial proof (proof.is_trivial()): the leaf hash is the root.
Through the generic trait
BinaryMerkleTree implements both MerkleTree<H> and ProofVerifier<H>:
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::Blake3;
fn verify_with<V: ProofVerifier<Blake3>>(
root: &[u8; 32],
leaf: &[u8],
proof: &MerkleProof<[u8; 32]>,
) -> bool {
V::verify(root, leaf, proof)
}
fn main() -> Result<(), MerkleError> {
let mut tree = BinaryMerkleTree::<Blake3>::new();
MerkleTree::insert(&mut tree, b"leaf")?;
let proof = MerkleTree::generate_proof(&tree, LeafIndex(0))?;
let root = *MerkleTree::root(&tree).expect("non-empty");
assert!(verify_with::<BinaryMerkleTree<Blake3>>(&root, b"leaf", &proof));
Ok(())
}Clone, Debug, and serde
BinaryMerkleTree implements Clone, Debug, Default, Serialize, and Deserialize. The
serialized form stores only the hash algorithm name and the leaf hashes; internal nodes are
recomputed on load. See Serialization.
Performance
On the reference machine (i7-1185G7), building a 100,000-leaf SHA-256 tree takes about 268 ms, proof generation about 79 ns, and verification about 3 µs. See Benchmarks for the full tables.