Merkleberg
Merkleberg is a library providing asynchronous Merkle Mountain Range data structures.
Features
- Simple bagged peaks MMR and accumulator based MMRIVER (based on the IETF draft) with consistency proofs
- Fully asynchronous API without reliance on any specific runtime
- Fully extensible with custom hashing via
Mergetrait and custom storage via other traits - Protects against second preimage attacks by default
- Compatible with
no_std, just disable thestdfeature
| Feature | MMR | MMRIVER |
|---|---|---|
| Root | Single hash (bagged peaks) | List of peaks (accumulator) |
| Inclusion proof | ✓ | ✓ |
| Multi-leaf proof | ✓ | ✗ (single leaf only) |
| Consistency proof | ✗ | ✓ |
| IETF spec | OpenTimestamps | IETF MMRIVER Draft |
Core Concepts
MMR
An MMR is a series of complete binary trees ("mountains") stored in post-order traversal:
# An 11 leaves MMR
14
/ \
6 13
/ \ / \
2 5 9 12 17
/ \ / \ / \ / \ / \
0 1 3 4 7 8 10 11 15 16 18
Nodes are indexed by insertion order. To add a leaf:
- Append leaf at next position
- If position has left sibling at same height, merge them into parent node
- Repeat step 2 until no more merging possible
use ;
use Sha256;
async
MMRIVER
MMRIVER (Merkle Mountain Range for Immediately Verifiable and Replicable Commitments) is an experimental draft IETF RFC. It differs from regular MMR by storing a list (i.e., the accumulator) of peaks instead of bagging from right to left into a single hash.
This approach allows for consistency proofs alongside the existing inclusion proofs already supported by MMR, where any new accumulator can be verified against an existing old accumulator (also known as the Reyzin-Yakoubov property), which is useful for blockchain header chains, verifiable log replication, state evolution proofs, etc.
use ;
use Sha256;
async
The Merge Trait
Merkleberg does not specify any hashing logic internally. Hashing logic is specified by implementing the
Merge trait.
use Merge;
use Infallible;
;
However, a DigestMerge generic type is provided for convenience which provides a simple implementation without
specifying a hashing algorithm. Any algorithm implementing the Digest trait can be supplied to it. Common
hashing algorithms can be found as a part of the RustCrypto project.
By default, DigestMerge uses domain prefixes to prevent second preimage attacks:
- Leaves: prefixed with
0x00 - Nodes: prefixed with
0x01
This ensures a leaf hash cannot be crafted to match a node hash.
Should you choose to opt-out of this much recommended protection, the unsafe-digest feature
may be enabled to make use of DigestMergeUnsafe instead.
Custom Storage Backends
Implement MMRStoreReadOps and MMRStoreWriteOps:
use ;
For convenience, a lightweight MemStore backend is provided by default, which stores trees
in memory. Storage backends also support batching via MMRBatch; multiple items can be pushed
before committing and actually updating the underlying structure.
Error Handling
Errors use trait objects for flexibility:
type UserError = ;
Custom store / merge errors need only implement Error + Send + Sync + 'static.
Serde Support
Enabling the serde feature makes all proof types implement Serialize and Deserialize:
use InclusionProof;
use serde_json;
// Serialize proof to JSON
let proof = mmriver.gen_inclusion_proof.await?;
let json = to_string?;
// Deserialize from JSON
let decoded: = from_str?;
[!NOTE] The
Itemtype of theMergeimplementation being used must also implementSerializeandDeserializefor the proofs to implement them. The provided merge implementations already do so.
References
- OpenTimestamps MMR spec
- Grin documentation
- Nervos implementation
- IETF MMRIVER Draft - Merkle Mountain Range for Immediately Verifiable and Replicable Commitments
- Wikipedia: Merkle tree
License
This project is dual-licensed under:
- MIT License (original)
- Mozilla Public License 2.0 - MMRIVER and other modifications made by @pesde-pkg
See LICENSE for full details.