Crates.io | merkle-heapless |
lib.rs | merkle-heapless |
version | 0.0.7 |
source | src |
created_at | 2023-07-04 01:38:22.541425 |
updated_at | 2024-04-12 15:29:20.29893 |
description | Statically-allocated Merkle Tree and Mountain Range |
homepage | |
repository | https://github.com/Retamogordo/merkle-heapless |
max_upload_size | |
id | 907540 |
size | 60,520 |
This Merkle tree is implemented as a contiguous memory array and does not betake to dynamic allocations. As such it allows for certain optimizations and compile-time imposed constraints on arity and size boundaries.
&[u8]
and returns something that implements AsRef<[u8]>
The most basic tree is the StaticTree. This kind of tree is instantiated to its final size.
use merkle_heapless::{StaticBinaryTree};
use merkle_heapless::traits::{StaticTreeTrait, ProofValidator};
// tree height 3, 8 leaves
const MAX_HEIGHT: usize = 3;
const MAX_WORD_LEN: usize = 10;
// supposing the YourHash struct exists
let mut tree = StaticBinaryTree::<MAX_HEIGHT, YourHash, MAX_WORD_LEN>::try_from::<&[u8]>(
&[b"apple", b"banana"]
).unwrap();
let proof = tree.generate_proof(0);
assert!(proof.validate(b"apple"));
You can replace a leaf with another value
// snip
// replace
tree.replace(5, b"cherry");
let proof = tree.generate_proof(5);
assert!(proof.validate(b"cherry"));
// remove
tree.replace(1, &[]);
let proof = tree.generate_proof(1);
assert!(!proof.validate(b"banana"));
let proof = tree.generate_proof(1);
assert!(proof.validate(&[]));
It's a generalized form of the above tree.
use merkle_heapless::{StaticTree};
const ARITY: usize = 4;
let mut tree = StaticTree::<ARITY, MAX_HEIGHT, YourHash, MAX_WORD_LEN>::try_from::<&[u8]>(
&[b"apple", b"banana"]
).unwrap();
// same operations can be applied
Examples: blake256 and standard Rust's hash used for HashMaps
use merkle_heapless::traits::HashT;
#[derive(Debug)]
struct Blake2_256Hash;
#[derive(Hash, Clone, Copy, Default, PartialEq, Debug)]
pub struct Wrapped32([u8; 32]);
impl From<u8> for Wrapped32 {
fn from(n: u8) -> Self {
let mut arr = [0u8; 32];
arr[0] = n;
Self(arr)
}
}
impl HashT for Blake2_256Hash {
type Output = Wrapped32;
fn hash(input: &[u8]) -> Self::Output {
Wrapped32(sp_core::blake2_256(input))
}
}
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use merkle_heapless::traits::HashT;
#[derive(Debug)]
pub struct StdHash;
#[derive(Hash, Clone, Copy, Default, PartialEq, Debug)]
pub struct Wrapped8([u8; 8]);
impl From<u8> for Wrapped8 {
fn from(n: u8) -> Self {
let mut arr = [0u8; 8];
arr[0] = n;
Self(arr)
}
}
impl HashT for StdHash {
type Output = Wrapped8;
fn hash(input: &[u8]) -> Self::Output {
let mut s = DefaultHasher::new();
input.hash(&mut s);
Wrapped8(s.finish().to_ne_bytes())
}
}
These extentions provide limited dynamic behaviour as for tree size handling.
A tree is augmented by creating a new tree with a height bigger by one, so the new tree contains as twice as nodes the former tree had. Then the contents of the former tree are copied and hashes recalculated.
use merkle_heapless::augmentable::{DefaultAugmentable};
const ARITY: usize = 4;
const HEIGHT: usize = 3;
const MAX_WORD_LEN: usize = 10;
let mt1 = DefaultAugmentable::<ARITY, HEIGHT, StdHash, MAX_WORD_LEN>::try_from::<&[u8]>(&[
"apple", "apricot", "banana", "cherry",
]).unwrap();
let mut mt = mt1.augment();
assert_eq!(mt.height(), HEIGHT + 1);
You can try_merge
a smaller (or equally-sized) tree into the original tree.
This operation does not imply augmentation, rather it fails if merge is not possible.
// snip
let mt2 = DefaultAugmentable::<ARITY, HEIGHT_2, StdHash, MAX_WORD_LEN>::try_from::<&[u8]>(&[
"kiwi", "lemon",
]).unwrap();
mt1.try_merge(mt2).unwrap();
### Reduction
Similarly, if remove, compact and reduce semantics is needed it is achievable through a Compactable tree variation:
```rust
use merkle_heapless::compactable::{DefaultCompactable};
const ARITY: usize = 4;
const HEIGHT: usize = 3;
const MAX_WORD_LEN: usize = 10;
let mut cmt = DefaultCompactable::<ARITY, HEIGHT, StdHash, MAX_WORD_LEN>::try_from::<&[u8]>(&[
"apple", "apricot", "banana", "cherry",
]).unwrap();
cmt.try_remove(0).unwrap();
cmt.compact();
// will try to create a smaller tree from the compacted tree
let mut reduced = cmt.try_reduce().unwrap();
Merkle Mountain Range offers append-only growable Merkle Tree semantics optimized for space. The rules for this implementation of Mountain Range are:
Include features = ["mmr_macro"]
in the merkle-heapless
dependency in Cargo.toml
.
// compulsory at the beginning of the .rs file in order the macro to compile
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
// snip
use merkle_heapless::{mmr_macro};
// declaration with expicit type name for your MMR
mmr_macro::mmr!(Type = FooMMR, BranchFactor = 2, Peaks = 3, Hash = StdHash, MaxInputWordLength = 10);
let mmr = FooMMR::default();
// implicitly creates MerkleMountainRange type
mmr_macro::mmr!(BranchFactor = 2, Peaks = 5, Hash = StdHash, MaxInputWordLength = 10);
// create with default current peak of height 0
let mmr = MerkleMountainRange::default();
// or create with current peak of height 2
let mut mmr = MerkleMountainRange::from_peak(MerkleMountainRangePeak::Peak3(Default::default()));
assert_eq!(mmr.peaks()[0].height(), 5 - 3);
The functionality of Mountain Range is similar to that of the Merkle tree.
mmr.try_append(b"apple").unwrap();
// peak leaf numbers: [1, 0, 0, 0, 0]
assert_eq!(mmr.peaks()[0].height(), 0);
assert_eq!(mmr.peaks()[0].num_of_leaves(), 1);
assert_eq!(mmr.peaks()[1].num_of_leaves(), 0);
let proof = mmr.generate_proof(0);
assert!(proof.validate(b"apple"));