Deterministic, no-std friendly Cartesian Merkle Tree for Rust. cmtree blends binary-search
ordering, heap-based balancing, and Merkle authentication so every node carries useful state
and proofs remain compact.
Table of contents
- π Deterministic treap β keys derive their priority from a configurable digest, so the resulting shape is independent of insertion order.
- π Merkle authentication β generate membership and non-membership proofs for any key in
O(log n)time, with hash ordering that matches the research paper. - π§΅ No-std first β uses
alloconly; works in embedded and wasm contexts. - π§© Pluggable hashers β swap
Sha256for anyDigest + Clonesuch asblake3orsha3. - π¦ Batch insertion β amortize rotations by feeding entire slices to
insert_batch. - ποΈ Key/value map β use
CMMapwhen you need authenticated values, not just membership. - π§ͺ Tested β extensive unit, doc, and large-structure tests plus a CI pipeline covering
cargo fmt,clippy,doc, andtest.
[dependencies]
cmtree = "0.1"use cmtree::CMTree;
fn main() {
let mut tree = CMTree::<Vec<u8>>::new();
tree.insert(b"alice".to_vec());
tree.insert(b"bob".to_vec());
tree.insert(b"carol".to_vec());
// Or populate in a single pass when you already have the collection:
tree.insert_batch([
b"alice".to_vec(),
b"bob".to_vec(),
b"carol".to_vec(),
]);
assert!(tree.contains(&b"bob".to_vec()));
assert_eq!(tree.len(), 3);
let root = tree.root_hash();
println!("Root digest: {:02x?}", root);
}use cmtree::CMTree;
let mut tree = CMTree::<Vec<u8>>::new();
for key in [b"x".to_vec(), b"y".to_vec(), b"z".to_vec()] {
tree.insert(key);
}
let root = tree.root_hash();
let proof = tree.generate_proof(&b"y".to_vec()).unwrap();
assert!(proof.existence);
assert!(proof.verify(&b"y".to_vec(), &root));
let missing = b"not-here".to_vec();
let proof = tree.generate_proof(&missing).unwrap();
assert!(!proof.existence);
assert!(proof.verify(&missing, &root));use cmtree::CMMap;
let mut map = CMMap::<Vec<u8>, Vec<u8>>::new();
map.insert_batch([
(b"alice".to_vec(), b"pubkey-alice".to_vec()),
(b"bob".to_vec(), b"pubkey-bob".to_vec()),
]);
let root = map.root_hash();
let membership = map.generate_proof(&b"bob".to_vec()).unwrap();
assert!(membership.existence);
assert!(membership.verify(&b"bob".to_vec(), Some(&b"pubkey-bob".to_vec()), &root));
let missing = map.generate_proof(&b"carol".to_vec()).unwrap();
assert!(!missing.existence);
assert!(missing.verify::<Vec<u8>, Vec<u8>>(&b"carol".to_vec(), None, &root));Proofs follow the definition in Cartesian Merkle Tree (Chystiakov et al., 2025), storing an ordered prefix of parent key digests and sibling hashes alongside the queried nodeβs children.
insert,remove,contains,generate_proof: expectedO(log n)fornstored keys (treap balancing).root_hash:O(1)thanks to cached node hashes.Prooflength:O(log n).- Space:
O(n); one node (with cached key digest) per key.
Determinism stems from hashing the key to produce the heap priority. Using a strong digest ensures priorities act like random values, maintaining balance with high probability.
- Alternative digests β instantiate
CMTree::<Vec<u8>, sha3::Sha3_256>::new(),CMTree::<Vec<u8>, Sha512>::new(), or any otherDigest + Clonehasher. - Smaller priorities β use
CMTree::<Vec<u8>, Sha256, u64>::new()(or anyPriorityimplementer) when memory pressure outweighs collision resistance. - Generic keys β keys only need
Clone + Ord + Hash; the tree hashes them into priorities and Merkle payloads. - No-std environments β enable
alloc, disable default features of your digest crate if necessary.
The library is production-ready, enforced by:
- exhaustive unit suite (including large-tree stress tests),
- doc tests that mirror README examples,
cargo fmt,clippy -- -D warnings,doc, andtestchecks in CI.
Planned enhancements:
- optional serde support for proofs,
- benchmarking utilities to compare digests,
- safe concurrency primitives for read-mostly workloads.
Feedback and contributions are welcome! Open an issue or pull request to discuss ideas or report edge cases.
Criterion benches cover inserts, removals, proofs, and the batch-loading workloads mentioned in the paper (including 5k and 10k batches applied to structures that already store 1 million keys). Run them with:
cargo bench -- merkle_structs
The million-element batch benchmarks are intentionally heavy; expect a few minutes on slower machines.