-
Notifications
You must be signed in to change notification settings - Fork 0
cleanup(merkle-tree): reject non-power-of-two leaves, dedupe helpers #603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -127,21 +127,23 @@ where | |||||
| /// | ||||||
| /// This skips the `hash_leaves` step, useful when leaves have already been | ||||||
| /// hashed externally (e.g., to avoid materializing large intermediate data). | ||||||
| /// | ||||||
| /// Returns `None` unless the leaf count is a non-zero power of two. The tree | ||||||
| /// is intentionally not padded: padding by repeating the last leaf would | ||||||
| /// make the root of `[.., x]` collide with the root of `[.., x, x]`, so the | ||||||
| /// root would no longer bind the leaf count. Every prover commitment is | ||||||
| /// already over a power-of-two domain, so this is a caller-side invariant. | ||||||
| pub fn build_from_hashed_leaves(hashed_leaves: Vec<B::Node>) -> Option<Self> { | ||||||
| if hashed_leaves.is_empty() { | ||||||
| if hashed_leaves.is_empty() || !hashed_leaves.len().is_power_of_two() { | ||||||
| return None; | ||||||
| } | ||||||
|
|
||||||
| //The leaf must be a power of 2 set | ||||||
| let hashed_leaves = complete_until_power_of_two(hashed_leaves); | ||||||
| let leaves_len = hashed_leaves.len(); | ||||||
|
|
||||||
| //The length of leaves minus one inner node in the merkle tree | ||||||
| //The first elements are overwritten by build function, it doesn't matter what it's there | ||||||
| // `nodes` holds (leaves_len - 1) inner nodes followed by the leaves. | ||||||
| // The inner-node entries are placeholders, overwritten by `build`. | ||||||
| let mut nodes = vec![hashed_leaves[0].clone(); leaves_len - 1]; | ||||||
| nodes.extend(hashed_leaves); | ||||||
|
|
||||||
| //Build the inner nodes of the tree | ||||||
| build::<B>(&mut nodes, leaves_len); | ||||||
|
|
||||||
| Some(MerkleTree { | ||||||
|
|
@@ -192,38 +194,30 @@ where | |||||
| &self.nodes | ||||||
| } | ||||||
|
|
||||||
| /// Returns a Merkle proof for the element/s at position pos | ||||||
| /// For example, give me an inclusion proof for the 3rd element in the | ||||||
| /// Merkle tree | ||||||
| /// Returns a Merkle inclusion proof for the leaf at position `pos`. | ||||||
| pub fn get_proof_by_pos(&self, pos: usize) -> Option<Proof<B::Node>> { | ||||||
| let pos = pos + self.node_count() / 2; | ||||||
| let Ok(merkle_path) = self.build_merkle_path(pos) else { | ||||||
| return None; | ||||||
| }; | ||||||
|
|
||||||
| self.create_proof(merkle_path) | ||||||
| } | ||||||
|
|
||||||
| /// Creates a proof from a Merkle pasth | ||||||
| fn create_proof(&self, merkle_path: Vec<B::Node>) -> Option<Proof<B::Node>> { | ||||||
| let merkle_path = self.build_merkle_path(pos).ok()?; | ||||||
| Some(Proof { merkle_path }) | ||||||
| } | ||||||
|
|
||||||
| /// Returns the Merkle path for the element/s for the leaf at position pos | ||||||
| /// Returns the Merkle path (sibling hashes, leaf to root) for the node at `pos`. | ||||||
| fn build_merkle_path(&self, pos: usize) -> Result<Vec<B::Node>, Error> { | ||||||
| // Pre-allocate based on tree depth (log2 of tree size) | ||||||
| let tree_depth = (self.node_count() + 1).ilog2() as usize; | ||||||
| let mut merkle_path = Vec::with_capacity(tree_depth); | ||||||
| let mut pos = pos; | ||||||
|
|
||||||
| while pos != ROOT { | ||||||
| let Some(node) = self.node_get(sibling_index(pos)) else { | ||||||
| // `pos != ROOT` guarantees a sibling exists. | ||||||
| let sibling = get_sibling_pos(pos).expect("non-root node has a sibling"); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Suggested change
|
||||||
| let Some(node) = self.node_get(sibling) else { | ||||||
| // out of bounds, exit returning the current merkle_path | ||||||
| return Err(Error::OutOfBounds); | ||||||
| }; | ||||||
| merkle_path.push(node.clone()); | ||||||
|
|
||||||
| pos = parent_index(pos); | ||||||
| pos = get_parent_pos(pos); | ||||||
| } | ||||||
|
|
||||||
| Ok(merkle_path) | ||||||
|
|
@@ -305,7 +299,7 @@ where | |||||
| // Number of levels in tree | ||||||
| let num_levels = (self.node_count() + 1).ilog2(); | ||||||
|
|
||||||
| // Iter lefevel-by-level from leaves to root. | ||||||
| // Iterate level-by-level from leaves to root. | ||||||
| for _ in 0..num_levels - 1 { | ||||||
| let mut next_obtainable = BTreeSet::new(); | ||||||
|
|
||||||
|
|
@@ -329,7 +323,7 @@ where | |||||
| } | ||||||
|
|
||||||
| // Reverse to get descending order (larger indices first). | ||||||
| // This makes the proof ordered from bottom (nodes closer to leaves) to top (nodes loser to root). | ||||||
| // This makes the proof ordered from bottom (nodes closer to leaves) to top (nodes closer to root). | ||||||
| auth_path_set.into_iter().rev().collect() | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,8 @@ | ||
| use alloc::vec::Vec; | ||
|
|
||
| use super::traits::IsMerkleTreeBackend; | ||
| #[cfg(feature = "parallel")] | ||
| use rayon::prelude::*; | ||
|
|
||
| pub fn sibling_index(node_index: usize) -> usize { | ||
| if node_index.is_multiple_of(2) { | ||
| node_index - 1 | ||
| } else { | ||
| node_index + 1 | ||
| } | ||
| } | ||
|
|
||
| pub fn parent_index(node_index: usize) -> usize { | ||
| if node_index.is_multiple_of(2) { | ||
| (node_index - 1) / 2 | ||
| } else { | ||
| node_index / 2 | ||
| } | ||
| } | ||
|
|
||
| /// Returns the sibling position for a given node index. | ||
| /// Returns `None` for the root node (index 0) since it has no sibling. | ||
| /// Sibling of `node_index` in the flat node array, or `None` for the root (0). | ||
| pub fn get_sibling_pos(node_index: usize) -> Option<usize> { | ||
| if node_index == 0 { | ||
| return None; | ||
|
|
@@ -33,8 +14,8 @@ pub fn get_sibling_pos(node_index: usize) -> Option<usize> { | |
| } | ||
| } | ||
|
|
||
| /// Parent of `node_index`. The root (0) has no parent and maps to itself. | ||
| pub fn get_parent_pos(node_index: usize) -> usize { | ||
| // Root node (index 0) has no parent, return itself to avoid underflow | ||
| if node_index == 0 { | ||
| return node_index; | ||
|
Comment on lines
18
to
20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low: Returning |
||
| } | ||
|
|
@@ -45,26 +26,11 @@ pub fn get_parent_pos(node_index: usize) -> usize { | |
| } | ||
| } | ||
|
|
||
| // The list of values is completed repeating the last value to a power of two length | ||
| pub fn complete_until_power_of_two<T: Clone>(mut values: Vec<T>) -> Vec<T> { | ||
| while !is_power_of_two(values.len()) { | ||
| values.push(values[values.len() - 1].clone()); | ||
| } | ||
| values | ||
| } | ||
|
|
||
| // ! NOTE ! | ||
| // In this function we say 2^0 = 1 is a power of two. | ||
| // In turn, this makes the smallest tree of one leaf, possible. | ||
| // The function is private and is only used to ensure the tree | ||
| // has a power of 2 number of leaves. | ||
| fn is_power_of_two(x: usize) -> bool { | ||
| (x & (x - 1)) == 0 | ||
| } | ||
|
|
||
| // ! CAUTION ! | ||
| // Make sure n=nodes.len()+1 is a power of two, and the last n/2 elements (leaves) are populated with hashes. | ||
| // This function takes no precautions for other cases. | ||
| /// Fills the inner nodes of a Merkle tree bottom-up. | ||
| /// | ||
| /// Precondition (caller-enforced, not checked): `nodes.len() + 1` is a power of | ||
| /// two and the last `leaves_len` entries of `nodes` already hold the leaf | ||
| /// hashes. Behaviour is unspecified for any other input. | ||
| pub fn build<B: IsMerkleTreeBackend>(nodes: &mut [B::Node], leaves_len: usize) | ||
| where | ||
| B::Node: Clone, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Low:
is_empty()is redundant here —0usize.is_power_of_two()returnsfalse, so the second condition already rejects empty inputs. The explicit check is harmless, but reads as if it guards a distinct case. Consider: