Getting started
This page takes you from an empty project to a verified inclusion proof in a few minutes.
Requirements
MerkleForge targets Rust 1.97.1 or newer (edition 2024), the rust-version declared by every
crate. Check your toolchain with:
rustc --versionInstall
Add the crates
cargo add merkle-core merkleforge-hash merkle-variants[dependencies]
merkle-core = "0.5.0"
merkleforge-hash = "0.5.0"
merkle-variants = "0.5.0"Most applications want all three:
merkle-coreprovides the traits (MerkleTree,ProofVerifier,HashFunction), the proof types, andMerkleError.merkleforge-hashprovides theSha256,Keccak256, andBlake3adapters.merkle-variantsprovides the trees.
Depend on merkle-core alone if you are writing your own tree or hash adapter.
Write your first proof
Replace src/main.rs with:
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::Sha256;
fn main() -> Result<(), MerkleError> {
let mut tree = BinaryMerkleTree::<Sha256>::new();
tree.insert(b"alice:100")?;
tree.insert(b"bob:250")?;
tree.insert(b"carol:75")?;
let root = tree.root().expect("tree is non-empty");
println!("root: {root:02x?}");
// Generate an inclusion proof for leaf 0 (alice).
let proof = tree.generate_proof(LeafIndex(0))?;
println!("proof depth: {}", proof.depth());
// Stateless verification: no tree needed, only the root, leaf bytes, and proof.
let valid = BinaryMerkleTree::<Sha256>::verify(root, b"alice:100", &proof);
assert!(valid);
// A different value for the same leaf is rejected.
assert!(!BinaryMerkleTree::<Sha256>::verify(root, b"alice:999", &proof));
Ok(())
}What just happened?
- Each
inserthashed the raw bytes into a leaf,H(0x00 ‖ data), and recomputed the path to the root. root()returned the 32-byte commitment to all three leaves.generate_proofcollected the sibling hashes from leaf 0 up to the root.verifyrebuilt the root fromb"alice:100"and those siblings, and compared it with the trusted root. It never touched the tree.
That last step is the light-client pattern: whoever holds a trusted root can check one value without the dataset. The Mental model page explains each piece.
Build from many leaves at once
When you already have the full leaf set, from_leaves builds the tree in one O(n) pass. The
root, indices, and proofs are identical to sequential inserts.
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::Sha256;
fn main() -> Result<(), MerkleError> {
let rows = ["alice:100", "bob:250", "carol:75"];
let batch = BinaryMerkleTree::<Sha256>::from_leaves(rows)?;
let mut sequential = BinaryMerkleTree::<Sha256>::new();
for row in rows {
sequential.insert(row.as_bytes())?;
}
assert_eq!(batch.root(), sequential.root());
Ok(())
}Swap the hash function
Every tree is generic over its hash. Change one type parameter and nothing else:
use merkle_core::prelude::*;
use merkle_variants::BinaryMerkleTree;
use merkleforge_hash::{Blake3, Keccak256};
fn main() -> Result<(), MerkleError> {
let mut fast = BinaryMerkleTree::<Blake3>::new();
let mut evm = BinaryMerkleTree::<Keccak256>::new();
fast.insert(b"alice:100")?;
evm.insert(b"alice:100")?;
// Different algorithms, different roots.
assert_ne!(fast.root(), evm.root());
Ok(())
}See Hash functions for when to pick each one.
Try the light-client example
The repository ships a runnable server/client split:
git clone https://github.com/1888-Labs/MerkleForge.git
cd MerkleForge
cargo run -p merkle-variants --example light_clientserver: built tree and exported root + proof
client: tree dropped before verification
Stateless verification: trueThe same flow runs in your browser on the live demo.
Next steps
- Choosing a tree when your data is a key-value map rather than a list.
- Proofs & stateless verification to ship proofs to clients.
- Serialization to store proofs and trees.