smt: refactor MerklePath logic

Computation of the correct node indices to get is moved to
`NodeIndex::proof_indices()`, and getting a node's index based on its
parent is generalized into `SparseMerkleTree::get_hash()`.
This commit is contained in:
Qyriad 2025-02-25 21:02:08 +01:00
parent 78672585f1
commit 91aac69b8e
2 changed files with 69 additions and 18 deletions

View file

@ -164,6 +164,18 @@ impl NodeIndex {
self.depth = self.depth.saturating_sub(delta);
self.value >>= delta as u32;
}
// ITERATORS
// --------------------------------------------------------------------------------------------
/// Return an iterator of the indices required for a Merkle proof of inclusion of a node at
/// `self`.
///
/// This is *exclusive* on both ends: neither `self` nor the root index are included in the
/// returned iterator.
pub fn proof_indices(&self) -> impl ExactSizeIterator<Item = NodeIndex> + use<> {
ProofIter { next_index: self.sibling() }
}
}
impl Display for NodeIndex {
@ -188,6 +200,39 @@ impl Deserializable for NodeIndex {
}
}
/// Implementation for [`NodeIndex::proof_indices()`].
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
struct ProofIter {
next_index: NodeIndex,
}
impl Iterator for ProofIter {
type Item = NodeIndex;
fn next(&mut self) -> Option<NodeIndex> {
if self.next_index.is_root() {
return None;
}
let index = self.next_index;
self.next_index = index.parent().sibling();
Some(index)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = ExactSizeIterator::len(self);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for ProofIter {
fn len(&self) -> usize {
self.next_index.depth() as usize
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;

View file

@ -79,28 +79,34 @@ pub(crate) trait SparseMerkleTree<const DEPTH: u8> {
// PROVIDED METHODS
// ---------------------------------------------------------------------------------------------
/// Returns a [MerklePath] to the specified key.
///
/// Mostly this is an implementation detail of [`Self::open()`].
fn get_path(&self, key: &Self::Key) -> MerklePath {
let index = NodeIndex::from(Self::key_to_leaf_index(key));
index.proof_indices().map(|index| self.get_hash(index)).collect()
}
/// Get the hash of a node at an arbitrary index, including the root or leaf hashes.
///
/// The root index simply returns [`Self::root()`]. Other hashes are retrieved by calling
/// [`Self::get_inner_node()`] on the parent, and returning the respective child hash.
fn get_hash(&self, index: NodeIndex) -> RpoDigest {
if index.is_root() {
return self.root();
}
let InnerNode { left, right } = self.get_inner_node(index.parent());
let index_is_right = index.is_value_odd();
if index_is_right { right } else { left }
}
/// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
/// path to the leaf, as well as the leaf itself.
fn open(&self, key: &Self::Key) -> Self::Opening {
let leaf = self.get_leaf(key);
let mut index: NodeIndex = {
let leaf_index: LeafIndex<DEPTH> = Self::key_to_leaf_index(key);
leaf_index.into()
};
let merkle_path = {
let mut path = Vec::with_capacity(index.depth() as usize);
for _ in 0..index.depth() {
let is_right = index.is_value_odd();
index.move_up();
let InnerNode { left, right } = self.get_inner_node(index);
let value = if is_right { left } else { right };
path.push(value);
}
MerklePath::new(path)
};
let merkle_path = self.get_path(key);
Self::path_and_leaf_to_opening(merkle_path, leaf)
}