use super::nodes::{
AppendPlan, ConsistencyPlan, InclusionPlan, MAX_TREE_SIZE, RootPlan, size_from_stored_count,
stored_count, stored_index,
};
use super::{ConsistencyProof, Hash, InclusionProof, ProofError, TreeHead};
fn next_stored_count(count: u64) -> Result<usize, ProofError> {
let mut size = count >> 1;
loop {
let stored = stored_count(size)?;
if stored > count {
return Ok(usize::try_from(stored).unwrap_or(usize::MAX));
}
size = size.saturating_add(1);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Tree {
nodes: Vec<Hash>,
size: u64,
}
impl Tree {
#[must_use]
pub const fn new() -> Self {
Self {
nodes: Vec::new(),
size: 0,
}
}
pub fn from_stored(nodes: Vec<Hash>) -> Result<Self, ProofError> {
let count =
u64::try_from(nodes.len()).map_err(|_| ProofError::TooLarge { size: u64::MAX })?;
let Some(size) = size_from_stored_count(count)? else {
return Err(ProofError::MalformedNodes {
expected: next_stored_count(count)?,
found: nodes.len(),
});
};
let mut rebuilt = Self::new();
for index in 0..size {
let leaf = Self::node(&nodes, stored_index(0, index)?)?;
rebuilt.push(leaf)?;
}
if let Some(index) = rebuilt.nodes.iter().zip(&nodes).position(|(a, b)| a != b) {
return Err(ProofError::NodeMismatch {
index: u64::try_from(index).unwrap_or(u64::MAX),
});
}
Ok(rebuilt)
}
#[must_use]
pub const fn size(&self) -> u64 {
self.size
}
pub fn push(&mut self, leaf_hash: Hash) -> Result<u64, ProofError> {
self.push_with(leaf_hash, |_, _| {})
}
pub fn push_with(
&mut self,
leaf_hash: Hash,
mut visit: impl FnMut(u64, &Hash),
) -> Result<u64, ProofError> {
let position = self.size;
let size = position
.checked_add(1)
.filter(|size| *size <= MAX_TREE_SIZE)
.ok_or(ProofError::TooLarge { size: position })?;
let plan = AppendPlan::new(position)?;
let siblings = self.fetch(plan.ids())?;
for (index, hash) in plan.assemble(leaf_hash, &siblings)? {
visit(index, &hash);
self.nodes.push(hash);
}
self.size = size;
Ok(position)
}
#[must_use]
pub fn stored(&self) -> &[Hash] {
&self.nodes
}
#[must_use]
pub fn head(&self) -> TreeHead {
self.head_at(self.size).unwrap_or(TreeHead {
size: 0,
root: super::empty_root(),
})
}
pub fn head_at(&self, size: u64) -> Result<TreeHead, ProofError> {
self.check_size(size)?;
let plan = RootPlan::new(size)?;
let root = plan.assemble(&self.fetch(plan.ids())?)?;
Ok(TreeHead { size, root })
}
pub fn inclusion_proof(&self, index: u64, size: u64) -> Result<InclusionProof, ProofError> {
self.check_size(size)?;
let plan = InclusionPlan::new(index, size)?;
plan.assemble(&self.fetch(plan.ids())?)
}
pub fn consistency_proof(
&self,
old_size: u64,
new_size: u64,
) -> Result<ConsistencyProof, ProofError> {
self.check_size(new_size)?;
let plan = ConsistencyPlan::new(old_size, new_size)?;
plan.assemble(&self.fetch(plan.ids())?)
}
fn check_size(&self, size: u64) -> Result<(), ProofError> {
if size > self.size {
Err(ProofError::BeyondTree {
size,
tree_size: self.size,
})
} else {
Ok(())
}
}
fn node(nodes: &[Hash], index: u64) -> Result<Hash, ProofError> {
usize::try_from(index)
.ok()
.and_then(|index| nodes.get(index))
.copied()
.ok_or(ProofError::MalformedNodes {
expected: usize::try_from(index)
.unwrap_or(usize::MAX)
.saturating_add(1),
found: nodes.len(),
})
}
fn fetch(&self, ids: &[u64]) -> Result<Vec<Hash>, ProofError> {
ids.iter().map(|id| Self::node(&self.nodes, *id)).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::log::{leaf_hash, nodes::stored_count, root_of};
#[test]
fn push_with_visits_every_new_node_in_order() {
let mut tree = Tree::new();
let mut seen = Vec::new();
for i in 0u8..20 {
tree.push_with(leaf_hash(&[i]), |index, hash| seen.push((index, *hash)))
.unwrap();
assert_eq!(seen.len() as u64, stored_count(tree.size()).unwrap());
}
for (expected, (index, hash)) in seen.iter().enumerate() {
assert_eq!(*index, expected as u64);
assert_eq!(tree.stored()[expected], *hash);
}
}
#[test]
fn from_stored_rebuilds_and_refuses_bad_covers() {
let mut tree = Tree::new();
let leaves: Vec<_> = (0u8..13).map(|i| leaf_hash(&[i])).collect();
for leaf in &leaves {
tree.push(*leaf).unwrap();
}
let rebuilt = Tree::from_stored(tree.stored().to_vec()).unwrap();
assert_eq!(rebuilt.head(), tree.head());
assert_eq!(rebuilt.head().root, root_of(&leaves));
let mut short = tree.stored().to_vec();
short.truncate(21);
assert!(matches!(
Tree::from_stored(short),
Err(ProofError::MalformedNodes { .. })
));
let mut forged = tree.stored().to_vec();
forged[2] = leaf_hash(b"forged");
assert_eq!(
Tree::from_stored(forged),
Err(ProofError::NodeMismatch { index: 2 })
);
}
#[test]
fn sizes_beyond_the_tree_are_refused() {
let mut tree = Tree::new();
tree.push(leaf_hash(b"a")).unwrap();
assert_eq!(
tree.head_at(2),
Err(ProofError::BeyondTree {
size: 2,
tree_size: 1
})
);
assert!(tree.inclusion_proof(0, 2).is_err());
assert!(tree.consistency_proof(1, 2).is_err());
assert_eq!(Tree::new().head().root, crate::log::empty_root());
}
}