pub mod batch;
pub mod iterator;
pub mod mem;
pub mod proof;
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
pub mod journaled;
}
}
pub use crate::merkle::Readable;
use crate::{
merkle,
merkle::{Family as _, Graftable},
};
pub use batch::{MerkleizedBatch, UnmerkleizedBatch};
pub type Proof<D> = merkle::proof::Proof<Family, D>;
pub type Position = merkle::Position<Family>;
pub type Location = merkle::Location<Family>;
pub type StandardHasher<H> = merkle::hasher::Standard<H>;
pub type Error = merkle::Error<Family>;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Family;
impl merkle::Family for Family {
const MAX_NODES: Position = Position::new(0x7FFF_FFFF_FFFF_FFFE);
const MAX_LEAVES: Location = Location::new(0x4000_0000_0000_001E);
fn location_to_position(loc: Location) -> Position {
let loc = loc.as_u64();
Position::new(2 * loc - (loc + 1).ilog2() as u64)
}
fn position_to_location(pos: Position) -> Option<Location> {
let pos = pos.as_u64();
let mut n = pos / 2;
n = (pos + (n + 1).ilog2() as u64) / 2;
loop {
let leaf_pos = 2 * n - (n + 1).ilog2() as u64;
if leaf_pos == pos {
return Some(Location::new(n));
}
if leaf_pos > pos {
return None;
}
n += 1;
}
}
fn to_nearest_size(size: Position) -> Position {
iterator::PeakIterator::to_nearest_size(size)
}
fn peaks(size: Position) -> impl Iterator<Item = (Position, u32)> {
iterator::PeakIterator::new(size)
}
fn children(pos: Position, height: u32) -> (Position, Position) {
iterator::children(pos, height)
}
fn is_valid_size(size: Position) -> bool {
Location::try_from(size).is_ok()
}
fn parent_heights(leaves: Location) -> impl Iterator<Item = u32> {
let leaf = *leaves;
let height = if (leaf + 2).is_power_of_two() {
None
} else {
Some((leaf + 1).trailing_ones() + 1)
};
height.into_iter()
}
fn pos_to_height(pos: Position) -> u32 {
if Self::position_to_location(pos).is_some() {
return 0;
}
let birth = Self::position_to_location(Position::new(*pos - 1))
.expect("position is neither leaf nor parent");
(*birth + 1).trailing_ones() + 1
}
}
impl Graftable for Family {
fn leftmost_leaf(pos: Position, height: u32) -> Location {
if height == 0 {
return Self::position_to_location(pos).expect("height-0 node must be a leaf");
}
let prev_pos = pos.checked_sub(1).expect("position underflow");
let birth =
Self::position_to_location(prev_pos).expect("position is neither leaf nor parent");
let term = 3u64
.checked_shl(height - 1)
.and_then(|v| v.checked_sub(2))
.expect("height excessively large");
birth.checked_sub(term).expect("location underflow")
}
fn subtree_root_position(leaf_start: Location, height: u32) -> Position {
if height == 0 {
return Self::location_to_position(leaf_start);
}
let offset = 3u64
.checked_shl(height - 1)
.and_then(|v| v.checked_sub(2))
.expect("height excessively large");
let birth_leaf = leaf_start.checked_add(offset).expect("location overflow");
let birth_pos = Self::location_to_position(birth_leaf);
birth_pos.checked_add(1).expect("position overflow")
}
fn chunk_peaks(
size: Position,
chunk_idx: u64,
grafting_height: u32,
) -> impl Iterator<Item = (Position, u32)> {
let chunk_size = 1u64 << grafting_height;
let chunk_start = chunk_idx * chunk_size;
let chunk_end = chunk_start + chunk_size;
let n = *Location::try_from(size).expect("chunk_peaks: invalid size");
assert!(
chunk_end <= n,
"chunk's leaf range exceeds the structure's leaf count"
);
let m = n + 1;
let p = m.ilog2();
let diff = m - chunk_start;
let maybe_x = diff.ilog2();
let x_power = 1u64 << maybe_x;
let x = if diff <= x_power + (m & (x_power - 1)) {
maybe_x
} else {
maybe_x + 1
};
let lo = p - x;
let initial_cursor = m - (1u64 << x) - (m & ((1u64 << x) - 1));
let mut leaf_cursor = initial_cursor;
(lo..p).map_while(move |k| {
let i = p - 1 - k;
let height = i as u64 + ((m >> i) & 1);
let peak_leaves = 1u64 << height;
let peak_start = leaf_cursor;
let peak_end = peak_start + peak_leaves;
leaf_cursor = peak_end;
if peak_start >= chunk_end {
return None;
}
let (pos, h) = if height <= grafting_height as u64 {
let last_leaf = peak_end - 1;
if height == 0 {
(Self::location_to_position(Location::new(last_leaf)), 0)
} else {
let birth = last_leaf + (1u64 << (height - 1)) - 1;
let pos = Position::new(
Self::location_to_position(Location::new(birth)).as_u64() + 1,
);
(pos, height as u32)
}
} else if grafting_height == 0 {
(Self::location_to_position(Location::new(chunk_start)), 0)
} else {
let chunk_last_leaf = chunk_end - 1;
let birth = chunk_last_leaf + (1u64 << (grafting_height - 1)) - 1;
let pos =
Position::new(Self::location_to_position(Location::new(birth)).as_u64() + 1);
(pos, grafting_height)
};
Some((pos, h))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mmb::mem::Mmb;
use commonware_cryptography::Sha256;
#[test]
fn test_parent_heights_schedule() {
let expected: [Option<u32>; 16] = [
None, Some(1), None, Some(1), Some(2), Some(1), None, Some(1), Some(2), Some(1), Some(3), Some(1), Some(2), Some(1), None, Some(1), ];
for (i, expected) in expected.iter().enumerate() {
let loc = Location::new(i as u64);
let height: Option<u32> = crate::merkle::Family::parent_heights(loc).next();
assert_eq!(height, *expected, "mismatch at loc={i}");
}
}
#[test]
fn test_pos_to_height() {
let mut next_pos = 0u64;
for leaf_idx in 0u64..500 {
let loc = Location::new(leaf_idx);
assert_eq!(
Family::pos_to_height(Position::new(next_pos)),
0,
"leaf at pos {next_pos} (loc {leaf_idx}) should be height 0"
);
next_pos += 1;
if let Some(h) = Family::parent_heights(loc).next() {
assert_eq!(
Family::pos_to_height(Position::new(next_pos)),
h,
"parent at pos {next_pos} (born at loc {leaf_idx}) should be height {h}"
);
next_pos += 1;
}
}
}
#[test]
fn test_leftmost_leaf() {
let hasher = StandardHasher::<Sha256>::new();
let mut mmb = Mmb::new(&hasher);
let digest = [1u8; 32];
for _ in 0..200 {
let merkleized = mmb
.new_batch()
.add(&hasher, &digest)
.merkleize(&mmb, &hasher);
mmb.apply_batch(&merkleized).unwrap();
}
for (peak_pos, peak_height) in Family::peaks(mmb.size()) {
let ll = Family::leftmost_leaf(peak_pos, peak_height);
let roundtrip = Family::subtree_root_position(ll, peak_height);
assert_eq!(
roundtrip, peak_pos,
"roundtrip failed for pos={peak_pos} height={peak_height}"
);
}
}
#[test]
fn test_subtree_root_position_virtual_roundtrip() {
for height in 0u32..10 {
let chunk_size = 1u64 << height;
for chunk_idx in 0u64..200 {
let leaf_start = Location::new(chunk_idx * chunk_size);
let pos = Family::subtree_root_position(leaf_start, height);
let roundtrip = Family::leftmost_leaf(pos, height);
assert_eq!(
roundtrip, leaf_start,
"virtual roundtrip failed: leaf_start={leaf_start}, height={height}, pos={pos}"
);
}
}
}
#[test]
fn test_chunk_peaks() {
let hasher = StandardHasher::<Sha256>::new();
let mut mmb = Mmb::new(&hasher);
let digest = [1u8; 32];
for _ in 0..200 {
let merkleized = mmb
.new_batch()
.add(&hasher, &digest)
.merkleize(&mmb, &hasher);
mmb.apply_batch(&merkleized).unwrap();
}
let size = mmb.size();
for grafting_height in 1..6 {
let chunk_size = 1u64 << grafting_height;
let num_chunks = 200 / chunk_size;
for chunk_idx in 0..num_chunks {
let chunk_start = chunk_idx * chunk_size;
let peaks: Vec<_> = Family::chunk_peaks(size, chunk_idx, grafting_height).collect();
assert!(
!peaks.is_empty(),
"chunk must have at least one covering peak"
);
let mut covered = 0u64;
for &(pos, h) in &peaks {
assert!(
mmb.get_node(pos).is_some(),
"chunk peak not in MMB at pos {pos} (gh={grafting_height}, chunk={chunk_idx})"
);
assert!(
h <= grafting_height,
"peak height {h} > grafting_height {grafting_height}"
);
let peak_leaves = 1u64 << h;
let expected_start = chunk_start + covered;
let leaf_loc = Location::new(expected_start);
let leaf_pos = Family::location_to_position(leaf_loc);
if h > 0 {
let mut p = pos;
let mut ph = h;
while ph > 0 {
let (left, _) = Family::children(p, ph);
p = left;
ph -= 1;
}
assert_eq!(
p, leaf_pos,
"peak's leftmost leaf mismatch (gh={grafting_height}, chunk={chunk_idx})"
);
} else {
assert_eq!(pos, leaf_pos, "height-0 peak should be the leaf itself");
}
covered += peak_leaves;
}
assert_eq!(
covered, chunk_size,
"peaks don't partition chunk (gh={grafting_height}, chunk={chunk_idx})"
);
}
}
}
#[test]
fn test_subtree_root_position() {
let mut next_pos = 0u64;
for leaf_idx in 0u64..500 {
let loc = Location::new(leaf_idx);
let pos = Family::subtree_root_position(loc, 0);
assert_eq!(
*pos, next_pos,
"height-0 subtree_root_position mismatch at leaf {leaf_idx}"
);
next_pos += 1;
if let Some(h) = Family::parent_heights(loc).next() {
let leftmost = leaf_idx + 2 - 3 * (1u64 << (h - 1));
let pos = Family::subtree_root_position(Location::new(leftmost), h);
assert_eq!(
*pos, next_pos,
"height-{h} subtree_root_position mismatch at leaf {leaf_idx}"
);
next_pos += 1;
}
}
}
}