From f5fbed4b114ee4b953b7cf5a040a581fdf6dc64f Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sun, 13 Apr 2025 14:19:51 +0200 Subject: [PATCH 01/19] refactor!(`MerkleTree`): Use index type aliases This commit introduces type aliases: - `MerkleTreeNodeIndex` for `u64` - `MerkleTreeLeafIndex` for `u64` - `MerkleTreeHeight` for `u32`. It also refactors `MerkleTree` to avoid using `usize` where-ever possible, particularly in public functions. Instead, these types aliases are used. `MerkleTree` internally stores a `Vec`, which induces two limitations: 1. The index type is `usize`, which complicates matters when targeting compilation on 32-bit machines (including 32-bit WASM). 2. This vector can store at most 2^25 nodes, whereas we would like functions associated with Merkle trees to work for Merkle trees that are far larger (without needing to store all the internal nodes explicitly). The second limitation persists. However, the first limitation is fixed by this commit. Also, public functions `nodes()` and `leafs()` now return iterators instead of slices. As these functions were public, this change is breaking. Addresses #250. --- .../benches/merkle_tree_authenticate.rs | 6 +- twenty-first/src/util_types/merkle_tree.rs | 196 +++++++++++------- .../util_types/mmr/mmr_membership_proof.rs | 3 +- .../src/util_types/mmr/mmr_successor_proof.rs | 5 +- .../src/util_types/mmr/shared_basic.rs | 4 +- 5 files changed, 134 insertions(+), 80 deletions(-) diff --git a/twenty-first/benches/merkle_tree_authenticate.rs b/twenty-first/benches/merkle_tree_authenticate.rs index 31dec64ee..d1eaca73b 100644 --- a/twenty-first/benches/merkle_tree_authenticate.rs +++ b/twenty-first/benches/merkle_tree_authenticate.rs @@ -56,8 +56,8 @@ impl Default for MerkleTreeSampler { } impl MerkleTreeSampler { - fn num_leafs(&self) -> usize { - 1 << self.tree_height + fn num_leafs(&self) -> MerkleTreeLeafIndex { + (1 << self.tree_height) as MerkleTreeLeafIndex } fn leaf_digests(&mut self) -> Vec { @@ -72,7 +72,7 @@ impl MerkleTreeSampler { MerkleTree::par_new(&leaf_digests).unwrap() } - fn indices_to_open(&mut self) -> Vec { + fn indices_to_open(&mut self) -> Vec { (0..self.num_opened_indices) .map(|_| self.rng.random_range(0..self.num_leafs())) .collect() diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index eb1b817c5..c0d8e1531 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -20,28 +20,48 @@ lazy_static! { .unwrap_or(DEFAULT_PARALLELIZATION_CUTOFF); } -/// Enforces that all compilation targets have a consistent [`MAX_TREE_HEIGHT`]. -/// In particular, if `usize` has more than 32 bits, the maximum height of a -/// Merkle tree is limited as if only 32 bits were available. If `usize` has -/// less than 32 bits, compilation will fail. +pub type MerkleTreeNodeIndex = u64; +pub type MerkleTreeLeafIndex = u64; +pub type MerkleTreeHeight = u32; + +/// The maximum number of nodes in Merkle trees that functions in this module +/// support. +/// +/// This constant enforces that all compilation targets have a consistent +/// [`MAX_TREE_HEIGHT`] and [`MAX_NUM_LEAFS`]. +/// +/// Note that the struct [`MerkleTree`] can hold only 2^25 nodes. This constant +/// applies to associated functions that do not take `self`. +const MAX_NUM_NODES: MerkleTreeNodeIndex = + MerkleTreeNodeIndex::MAX ^ (MerkleTreeNodeIndex::MAX >> 1); + +/// The maximum number of leafs in Merkle trees that functions in this module +/// support. /// -/// Using a type other than `usize` could enable a higher maximum height, but -/// would require a different storage mechanism for the Merkle tree's nodes: -/// indexing into a `Vec<_>` can only be done with `usize`. -const MAX_NUM_NODES: usize = 1 << 32; -const MAX_NUM_LEAFS: usize = MAX_NUM_NODES / 2; +/// See also: [`MAX_NUM_NODES`], [`MAX_TREE_HEIGHT`]. +const MAX_NUM_LEAFS: MerkleTreeLeafIndex = MAX_NUM_NODES / 2; -/// The maximum height of a Merkle tree. -pub const MAX_TREE_HEIGHT: usize = MAX_NUM_LEAFS.ilog2() as usize; +/// The maximum height of Merkle trees that functions in this module support. +/// +/// See also: [`MAX_NUM_NODES`], [`MAX_NUM_LEAFS`]. +pub const MAX_TREE_HEIGHT: MerkleTreeHeight = MAX_NUM_LEAFS.ilog2() as MerkleTreeHeight; + +/// The index of the root node. +pub(crate) const ROOT_INDEX: MerkleTreeNodeIndex = 1; type Result = result::Result; /// A [Merkle tree][merkle_tree] is a binary tree of [digests](Digest) that is /// used to efficiently prove the inclusion of items in a set. Set inclusion can -/// be verified through an [inclusion proof](MerkleTreeInclusionProof). +/// be verified through an [inclusion proof](MerkleTreeInclusionProof). This +/// struct can hold at most 2^25 digests[^1], limiting the height of the tree to +/// 2^24. However, the associated functions (*i.e.*, the ones that don't take +/// `self`) make abstraction of this limitation and work for Merkle trees of up +/// to 2^63 nodes, 2^62 leafs, or height up to 62. /// /// The used hash function is [`Tip5`]. /// +/// [^1]: https://github.com/Neptune-Crypto/twenty-first/pull/250#issuecomment-2782490889 /// [merkle_tree]: https://en.wikipedia.org/wiki/Merkle_tree #[derive(Debug, Clone, PartialEq, Eq)] pub struct MerkleTree { @@ -55,7 +75,7 @@ pub struct MerkleTree { #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct MerkleTreeInclusionProof { /// The stated height of the Merkle tree this proof is relative to. - pub tree_height: usize, + pub tree_height: MerkleTreeHeight, /// The leafs the proof is about, _i.e._, the revealed leafs. /// @@ -63,7 +83,7 @@ pub struct MerkleTreeInclusionProof { /// relevant for [`into_authentication_paths`][paths]. /// /// [paths]: MerkleTreeInclusionProof::into_authentication_paths - pub indexed_leafs: Vec<(usize, Digest)>, + pub indexed_leafs: Vec<(MerkleTreeLeafIndex, Digest)>, /// The proof's witness: de-duplicated authentication structure for the /// leafs this proof is about. See [`authentication_structure`][auth_structure] @@ -93,16 +113,20 @@ pub struct MerkleTreeInclusionProof { /// [auth_structure]: MerkleTree::authentication_structure #[derive(Debug, Clone, PartialEq, Eq, Default)] pub(crate) struct PartialMerkleTree { - tree_height: usize, - leaf_indices: Vec, - nodes: HashMap, + tree_height: MerkleTreeHeight, + leaf_indices: Vec, + nodes: HashMap, } impl MerkleTree { - /// The index of the root node. + /// When iterating over [`Self::nodes`] it pays to have an starting index + /// with the same type as the index type used by `Vec`. /// /// If you need to read the root, try [`root()`](Self::root) instead. - pub(crate) const ROOT_INDEX: usize = 1; + const ROOT_INDEX: usize = 1_usize; + + const MAX_NUM_NODES: usize = 1_usize << 25; + const MAX_NUM_LEAFS: usize = Self::MAX_NUM_NODES / 2; /// Build a MerkleTree with the given leafs. /// @@ -169,7 +193,7 @@ impl MerkleTree { if !num_leafs.is_power_of_two() { return Err(MerkleTreeError::IncorrectNumberOfLeafs); } - if num_leafs > MAX_NUM_LEAFS { + if num_leafs > Self::MAX_NUM_LEAFS { return Err(MerkleTreeError::TreeTooHigh); } @@ -185,9 +209,9 @@ impl MerkleTree { // This function is not defined as a method (taking self as argument) since it's // needed by the verifier, who does not have access to the Merkle tree. fn authentication_structure_node_indices( - num_leafs: usize, - leaf_indices: &[usize], - ) -> Result + use<>> { + num_leafs: MerkleTreeLeafIndex, + leaf_indices: &[MerkleTreeLeafIndex], + ) -> Result + use<>> { // The set of indices of nodes that need to be included in the authentications // structure. In principle, every node of every authentication path is needed. // The root is never needed. Hence, it is not considered below. @@ -205,7 +229,7 @@ impl MerkleTree { } let mut node_index = leaf_index + num_leafs; - while node_index > Self::ROOT_INDEX { + while node_index > ROOT_INDEX { let sibling_index = node_index ^ 1; node_can_be_computed.insert(node_index); node_is_needed.insert(sibling_index); @@ -251,10 +275,13 @@ impl MerkleTree { /// This is the other part of the de-duplication. /// /// [verify]: MerkleTreeInclusionProof::verify - pub fn authentication_structure(&self, leaf_indices: &[usize]) -> Result> { + pub fn authentication_structure( + &self, + leaf_indices: &[MerkleTreeLeafIndex], + ) -> Result> { let num_leafs = self.num_leafs(); let indices = Self::authentication_structure_node_indices(num_leafs, leaf_indices)?; - let auth_structure = indices.map(|idx| self.nodes[idx]).collect(); + let auth_structure = indices.map(|idx| self.node(idx).unwrap()).collect(); Ok(auth_structure) } @@ -262,41 +289,49 @@ impl MerkleTree { self.nodes[Self::ROOT_INDEX] } - pub fn num_leafs(&self) -> usize { - let node_count = self.nodes.len(); + pub fn num_leafs(&self) -> MerkleTreeLeafIndex { + let node_count = MerkleTreeNodeIndex::try_from(self.nodes.len()).expect("usize to u64"); debug_assert!(node_count.is_power_of_two()); node_count / 2 } - pub fn height(&self) -> usize { + pub fn height(&self) -> MerkleTreeHeight { let leaf_count = self.num_leafs(); debug_assert!(leaf_count.is_power_of_two()); - leaf_count.ilog2() as usize + MerkleTreeHeight::try_from(leaf_count.ilog2()).expect("log of num leafs should fit in u32") } /// All nodes of the Merkle tree. - pub fn nodes(&self) -> &[Digest] { - &self.nodes + pub fn nodes(&self) -> impl Iterator { + self.nodes.iter() } /// The node at the given node index, if it exists. - pub fn node(&self, index: usize) -> Option { - self.nodes.get(index).copied() + pub fn node(&self, index: MerkleTreeNodeIndex) -> Option { + usize::try_from(index) + .ok() + .and_then(|idx| self.nodes.get(idx).copied()) } /// All leafs of the Merkle tree. - pub fn leafs(&self) -> &[Digest] { - let first_leaf = self.nodes.len() / 2; - &self.nodes[first_leaf..] + pub fn leafs(&self) -> impl Iterator { + self.nodes.iter().skip( + (self.num_nodes() / 2) + .try_into() + .expect("MerkleTreeNodeIndex to usize conversion error"), + ) } /// The leaf at the given index, if it exists. - pub fn leaf(&self, index: usize) -> Option { - let first_leaf_index = self.nodes.len() / 2; - self.nodes.get(first_leaf_index + index).copied() + pub fn leaf(&self, index: MerkleTreeLeafIndex) -> Option { + let first_leaf_index = self.num_nodes() / 2; + self.node(first_leaf_index + index) } - pub fn indexed_leafs(&self, indices: &[usize]) -> Result> { + pub fn indexed_leafs( + &self, + indices: &[MerkleTreeLeafIndex], + ) -> Result> { let num_leafs = self.num_leafs(); let invalid_index = MerkleTreeError::LeafIndexInvalid { num_leafs }; let maybe_indexed_leaf = |&i| self.leaf(i).ok_or(invalid_index).map(|leaf| (i, leaf)); @@ -304,6 +339,11 @@ impl MerkleTree { indices.iter().map(maybe_indexed_leaf).collect() } + fn num_nodes(&self) -> MerkleTreeNodeIndex { + MerkleTreeNodeIndex::try_from(self.nodes.len()) + .expect("`MerkleTreeNodeIndex` should be large enough to hold any usize") + } + /// A full inclusion proof for the leafs at the supplied indices, including the /// leafs. Generally, using [`authentication_structure`][auth_structure] is /// preferable. Use this method only if the verifier needs explicit access to the @@ -312,7 +352,7 @@ impl MerkleTree { /// [auth_structure]: Self::authentication_structure pub fn inclusion_proof_for_leaf_indices( &self, - indices: &[usize], + indices: &[MerkleTreeLeafIndex], ) -> Result { let proof = MerkleTreeInclusionProof { tree_height: self.height(), @@ -336,7 +376,7 @@ impl<'a> Arbitrary<'a> for MerkleTree { } impl MerkleTreeInclusionProof { - fn leaf_indices(&self) -> impl Iterator { + fn leaf_indices(&self) -> impl Iterator { self.indexed_leafs.iter().map(|(index, _)| index) } @@ -394,19 +434,19 @@ impl MerkleTreeInclusionProof { impl PartialMerkleTree { pub fn root(&self) -> Result { self.nodes - .get(&MerkleTree::ROOT_INDEX) + .get(&ROOT_INDEX) .copied() .ok_or(MerkleTreeError::RootNotFound) } - fn node(&self, index: usize) -> Result { + fn node(&self, index: MerkleTreeNodeIndex) -> Result { self.nodes .get(&index) .copied() .ok_or(MerkleTreeError::MissingNodeIndex(index)) } - fn num_leafs(&self) -> Result { + fn num_leafs(&self) -> Result { if self.tree_height > MAX_TREE_HEIGHT { return Err(MerkleTreeError::TreeTooHigh); } @@ -434,7 +474,7 @@ impl PartialMerkleTree { /// Any parent node index is included only once. This guarantees that the number /// of hash operations is minimal. - fn first_layer_parent_node_indices(&self) -> Result> { + fn first_layer_parent_node_indices(&self) -> Result> { let num_leafs = self.num_leafs()?; let leaf_to_parent_node_index = |&leaf_index| (leaf_index + num_leafs) / 2; @@ -445,7 +485,7 @@ impl PartialMerkleTree { Ok(parent_node_indices) } - fn insert_digest_for_index(&mut self, parent_index: usize) -> Result<()> { + fn insert_digest_for_index(&mut self, parent_index: MerkleTreeNodeIndex) -> Result<()> { let (left_child, right_child) = self.children_of_node(parent_index)?; let parent_digest = Tip5::hash_pair(left_child, right_child); @@ -455,7 +495,7 @@ impl PartialMerkleTree { } } - fn children_of_node(&self, parent_index: usize) -> Result<(Digest, Digest)> { + fn children_of_node(&self, parent_index: MerkleTreeNodeIndex) -> Result<(Digest, Digest)> { let left_child_index = parent_index * 2; let right_child_index = left_child_index ^ 1; @@ -465,7 +505,9 @@ impl PartialMerkleTree { } /// Indices are deduplicated to guarantee minimal number of hash operations. - fn move_indices_one_layer_up(mut indices: Vec) -> Vec { + fn move_indices_one_layer_up( + mut indices: Vec, + ) -> Vec { indices.iter_mut().for_each(|i| *i /= 2); indices.dedup(); indices @@ -484,11 +526,14 @@ impl PartialMerkleTree { /// /// Fails if the partial Merkle tree does not contain the entire /// authentication path. - fn authentication_path_for_index(&self, leaf_index: usize) -> Result> { + fn authentication_path_for_index( + &self, + leaf_index: MerkleTreeLeafIndex, + ) -> Result> { let num_leafs = self.num_leafs()?; let mut authentication_path = vec![]; let mut node_index = leaf_index + num_leafs; - while node_index > MerkleTree::ROOT_INDEX { + while node_index > ROOT_INDEX { let sibling_index = node_index ^ 1; let sibling = self.node(sibling_index)?; authentication_path.push(sibling); @@ -544,7 +589,7 @@ impl TryFrom for PartialMerkleTree { #[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] pub enum MerkleTreeError { #[error("All leaf indices must be valid, i.e., less than {num_leafs}.")] - LeafIndexInvalid { num_leafs: usize }, + LeafIndexInvalid { num_leafs: MerkleTreeLeafIndex }, #[error("The length of the supplied authentication structure must match the expected length.")] AuthenticationStructureLengthMismatch, @@ -553,10 +598,10 @@ pub enum MerkleTreeError { RepeatedLeafDigestMismatch, #[error("The partial tree must be minimal. Node {0} was supplied but can be computed.")] - SpuriousNodeIndex(usize), + SpuriousNodeIndex(MerkleTreeNodeIndex), #[error("The partial tree must contain all necessary information. Node {0} is missing.")] - MissingNodeIndex(usize), + MissingNodeIndex(MerkleTreeNodeIndex), #[error("Could not compute the root. Maybe no leaf indices were supplied?")] RootNotFound, @@ -582,7 +627,7 @@ pub mod merkle_tree_test { use crate::math::digest::digest_tests::DigestCorruptor; impl MerkleTree { - fn test_tree_of_height(tree_height: usize) -> Self { + fn test_tree_of_height(tree_height: MerkleTreeHeight) -> Self { let num_leafs = 1 << tree_height; let leafs = (0..num_leafs).map(BFieldElement::new); let leaf_digests = leafs.map(|bfe| Tip5::hash_varlen(&[bfe])).collect_vec(); @@ -593,10 +638,12 @@ pub mod merkle_tree_test { } impl PartialMerkleTree { - fn dummy_nodes_for_indices(node_indices: &[usize]) -> HashMap { + fn dummy_nodes_for_indices( + node_indices: &[MerkleTreeNodeIndex], + ) -> HashMap { node_indices .iter() - .map(|&i| (i, BFieldElement::new(i as u64))) + .map(|&i| (i, BFieldElement::new(i))) .map(|(i, leaf)| (i, Tip5::hash_varlen(&[leaf]))) .collect() } @@ -608,8 +655,8 @@ pub mod merkle_tree_test { #[strategy(arb())] pub tree: MerkleTree, - #[strategy(vec(0..#tree.num_leafs(), 0..#tree.num_leafs()))] - pub selected_indices: Vec, + #[strategy(vec((0 as MerkleTreeLeafIndex)..#tree.num_leafs(), 0..(#tree.num_leafs() as usize)))] + pub selected_indices: Vec, } impl MerkleTreeToTest { @@ -763,7 +810,8 @@ pub mod merkle_tree_test { fn removing_leafs_from_proof_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(Just(#test_tree.proof().indexed_leafs.len()))] _n_leafs: usize, - #[strategy(vec(0..#_n_leafs, 1..=#_n_leafs))] leaf_indices_to_remove: Vec, + #[strategy(vec(0..(#_n_leafs as MerkleTreeLeafIndex), 1..=#_n_leafs))] + leaf_indices_to_remove: Vec, ) { let mut proof = test_tree.proof(); let leafs_to_keep = proof @@ -783,8 +831,8 @@ pub mod merkle_tree_test { #[proptest(cases = 30)] fn checking_set_inclusion_of_items_not_in_set_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, - #[strategy(vec(0..#test_tree.tree.num_leafs(), 1..=#test_tree.tree.num_leafs()))] - spurious_indices: Vec, + #[strategy(vec(0..#test_tree.tree.num_leafs(), 1..=(#test_tree.tree.num_leafs() as usize)))] + spurious_indices: Vec, #[strategy(vec(any::(), #spurious_indices.len()))] spurious_digests: Vec, ) { let spurious_leafs = spurious_indices @@ -819,7 +867,7 @@ pub mod merkle_tree_test { #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(0..=MAX_TREE_HEIGHT)] #[filter(#test_tree.tree.height() != #incorrect_height)] - incorrect_height: usize, + incorrect_height: MerkleTreeHeight, ) { let mut proof = test_tree.proof(); proof.tree_height = incorrect_height; @@ -842,7 +890,8 @@ pub mod merkle_tree_test { #[proptest(cases = 30)] fn requesting_inclusion_proof_for_nonexistent_leaf_fails_with_expected_error( #[strategy(arb())] tree: MerkleTree, - #[filter(#leaf_indices.iter().any(|&i| i > #tree.num_leafs()))] leaf_indices: Vec, + #[filter(#leaf_indices.iter().any(|&i| i > (#tree.num_leafs() as MerkleTreeLeafIndex)))] + leaf_indices: Vec, ) { let maybe_proof = tree.inclusion_proof_for_leaf_indices(&leaf_indices); let err = maybe_proof.unwrap_err(); @@ -901,11 +950,13 @@ pub mod merkle_tree_test { #[proptest(cases = 10)] fn each_leaf_can_be_verified_individually(test_tree: MerkleTreeToTest) { let tree = test_tree.tree; - for (leaf_index, &leaf) in tree.leafs().iter().enumerate() { - let authentication_path = tree.authentication_structure(&[leaf_index]).unwrap(); + for (leaf_index, &leaf) in tree.leafs().enumerate() { + let authentication_path = tree + .authentication_structure(&[leaf_index.try_into().unwrap()]) + .unwrap(); let proof = MerkleTreeInclusionProof { tree_height: tree.height(), - indexed_leafs: [(leaf_index, leaf)].into(), + indexed_leafs: [(leaf_index as MerkleTreeLeafIndex, leaf)].into(), authentication_structure: authentication_path, }; let verdict = proof.verify(tree.root()); @@ -950,7 +1001,7 @@ pub mod merkle_tree_test { // // 0 2 <-- opened_leaf_indices - let node_indices = [3, 8, 9, 10, 11]; + let node_indices = [3 as MerkleTreeNodeIndex, 8, 9, 10, 11]; let mut partial_tree = PartialMerkleTree { tree_height: 3, leaf_indices: vec![0, 2], @@ -1009,15 +1060,16 @@ pub mod merkle_tree_test { #[test] fn converting_authentication_structure_to_authentication_paths_results_in_expected_paths() { - const TREE_HEIGHT: usize = 3; + const TREE_HEIGHT: MerkleTreeHeight = 3; let merkle_tree = MerkleTree::test_tree_of_height(TREE_HEIGHT); let proof = merkle_tree .inclusion_proof_for_leaf_indices(&[0, 2]) .unwrap(); let auth_paths = proof.into_authentication_paths().unwrap(); - let auth_path_with_nodes = - |indices: [usize; TREE_HEIGHT]| indices.map(|i| merkle_tree.nodes[i]).to_vec(); + let auth_path_with_nodes = |indices: [MerkleTreeNodeIndex; TREE_HEIGHT as usize]| { + indices.map(|i| merkle_tree.node(i).unwrap()).to_vec() + }; let expected_path_0 = auth_path_with_nodes([9, 5, 3]); let expected_path_1 = auth_path_with_nodes([11, 4, 3]); let expected_paths = vec![expected_path_0, expected_path_1]; diff --git a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs index e9e6a73ab..fe3805614 100644 --- a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs @@ -18,6 +18,7 @@ use super::shared_basic; use crate::error::U32_TO_USIZE_ERR; use crate::error::USIZE_TO_U64_ERR; use crate::prelude::*; +use crate::util_types::merkle_tree; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, GetSize, BFieldCodec, Arbitrary)] pub struct MmrMembershipProof { @@ -68,7 +69,7 @@ impl MmrMembershipProof { }; mt_index /= 2; } - debug_assert_eq!(MerkleTree::ROOT_INDEX as u64, mt_index); + debug_assert_eq!(merkle_tree::ROOT_INDEX, mt_index); let peak_index = usize::try_from(peak_index).expect(U32_TO_USIZE_ERR); diff --git a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs index d01ec421e..f38e076e6 100644 --- a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs @@ -4,6 +4,7 @@ use super::mmr_accumulator::MmrAccumulator; use super::shared_basic::leaf_index_to_mt_index_and_peak_index; use crate::error::USIZE_TO_U64_ERR; use crate::prelude::*; +use crate::util_types::merkle_tree; /// Asserts that one [MMR Accumulator] is the descendant of another, *i.e.*, /// that the second can be obtained by appending a set of leafs to the first. It @@ -60,7 +61,7 @@ impl MmrSuccessorProof { let mut old_peaks = mmra.peaks().into_iter(); let mut first_unused_new_leaf_idx = num_leafs_in_lowest_peak; - let merkle_tree_root_index = u64::try_from(MerkleTree::ROOT_INDEX).expect(USIZE_TO_U64_ERR); + let merkle_tree_root_index = merkle_tree::ROOT_INDEX; while merkle_tree_index > merkle_tree_root_index { let current_node_is_left_sibling = merkle_tree_index % 2 == 0; current_node = if current_node_is_left_sibling { @@ -194,7 +195,7 @@ impl MmrSuccessorProof { let mut current_node = *auth_path.next().ok_or(Error::AuthenticationPathTooShort)?; let mut merkle_tree_index = merkle_tree_index >> height_of_lowest_old_peak; - let merkle_tree_root_index = u64::try_from(MerkleTree::ROOT_INDEX).expect(USIZE_TO_U64_ERR); + let merkle_tree_root_index = merkle_tree::ROOT_INDEX; while merkle_tree_index > merkle_tree_root_index { let current_node_is_left_sibling = merkle_tree_index % 2 == 0; current_node = if current_node_is_left_sibling { diff --git a/twenty-first/src/util_types/mmr/shared_basic.rs b/twenty-first/src/util_types/mmr/shared_basic.rs index 0465c6dbd..c94326d71 100644 --- a/twenty-first/src/util_types/mmr/shared_basic.rs +++ b/twenty-first/src/util_types/mmr/shared_basic.rs @@ -1,6 +1,6 @@ use crate::error::U32_TO_USIZE_ERR; -use crate::error::USIZE_TO_U64_ERR; use crate::prelude::*; +use crate::util_types::merkle_tree; #[inline] pub fn left_child(node_index: u64, height: u32) -> u64 { @@ -111,7 +111,7 @@ pub fn calculate_new_peaks_from_leaf_mutation( leaf_index: u64, membership_proof: &MmrMembershipProof, ) -> Vec { - let merkle_tree_root_index = u64::try_from(MerkleTree::ROOT_INDEX).expect(USIZE_TO_U64_ERR); + let merkle_tree_root_index = merkle_tree::ROOT_INDEX; let (mut acc_mt_index, peak_index) = leaf_index_to_mt_index_and_peak_index(leaf_index, num_leafs); From 825c1c5407658521347c2895dff46102c26743fe Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sun, 13 Apr 2025 14:48:02 +0200 Subject: [PATCH 02/19] refactor: Expose `authentication_structure_node_indices` Needed downstream, for compressing `RemovalRecord` lists. --- twenty-first/src/util_types/merkle_tree.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index c0d8e1531..76140ba53 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -203,12 +203,15 @@ impl MerkleTree { Ok(nodes) } - /// Given a list of leaf indices, return the indices of exactly those nodes that - /// are needed to prove (or verify) that the indicated leafs are in the Merkle - /// tree. - // This function is not defined as a method (taking self as argument) since it's - // needed by the verifier, who does not have access to the Merkle tree. - fn authentication_structure_node_indices( + /// Compute the node indices for an authentication structure. + /// + /// Given a list of leaf indices, return the indices of exactly those nodes + /// that are needed to prove (or verify) that the indicated leafs are in the + /// Merkle tree. + /// + /// Returns an error if any of the leaf indices is bigger than the number of + /// leafs. + pub fn authentication_structure_node_indices( num_leafs: MerkleTreeLeafIndex, leaf_indices: &[MerkleTreeLeafIndex], ) -> Result + use<>> { From a442ee62090eeb9e519370207ffc9bbc66fecc3a Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sun, 13 Apr 2025 19:03:54 +0200 Subject: [PATCH 03/19] fix: Decrease line length for CI Also: increase line count. --- twenty-first/src/util_types/merkle_tree.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 76140ba53..a4b8e7063 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -658,7 +658,14 @@ pub mod merkle_tree_test { #[strategy(arb())] pub tree: MerkleTree, - #[strategy(vec((0 as MerkleTreeLeafIndex)..#tree.num_leafs(), 0..(#tree.num_leafs() as usize)))] + #[ + strategy( + vec( + (0 as MerkleTreeLeafIndex)..#tree.num_leafs(), + 0..(#tree.num_leafs() as usize) + ) + ) + ] pub selected_indices: Vec, } @@ -834,7 +841,14 @@ pub mod merkle_tree_test { #[proptest(cases = 30)] fn checking_set_inclusion_of_items_not_in_set_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, - #[strategy(vec(0..#test_tree.tree.num_leafs(), 1..=(#test_tree.tree.num_leafs() as usize)))] + #[ + strategy( + vec( + 0..#test_tree.tree.num_leafs(), + 1..=(#test_tree.tree.num_leafs() as usize) + ) + ) + ] spurious_indices: Vec, #[strategy(vec(any::(), #spurious_indices.len()))] spurious_digests: Vec, ) { From 185df8e6eb4e79ffded8c918f8acc8f6b8c832ea Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sun, 13 Apr 2025 19:09:11 +0200 Subject: [PATCH 04/19] docs: Fix links --- twenty-first/src/util_types/merkle_tree.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index a4b8e7063..fba5db791 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -42,8 +42,6 @@ const MAX_NUM_NODES: MerkleTreeNodeIndex = const MAX_NUM_LEAFS: MerkleTreeLeafIndex = MAX_NUM_NODES / 2; /// The maximum height of Merkle trees that functions in this module support. -/// -/// See also: [`MAX_NUM_NODES`], [`MAX_NUM_LEAFS`]. pub const MAX_TREE_HEIGHT: MerkleTreeHeight = MAX_NUM_LEAFS.ilog2() as MerkleTreeHeight; /// The index of the root node. @@ -51,18 +49,18 @@ pub(crate) const ROOT_INDEX: MerkleTreeNodeIndex = 1; type Result = result::Result; -/// A [Merkle tree][merkle_tree] is a binary tree of [digests](Digest) that is +/// A [Merkle tree][1] is a binary tree of [digests](Digest) that is /// used to efficiently prove the inclusion of items in a set. Set inclusion can /// be verified through an [inclusion proof](MerkleTreeInclusionProof). This -/// struct can hold at most 2^25 digests[^1], limiting the height of the tree to +/// struct can hold at most 2^25 digests[^2], limiting the height of the tree to /// 2^24. However, the associated functions (*i.e.*, the ones that don't take /// `self`) make abstraction of this limitation and work for Merkle trees of up /// to 2^63 nodes, 2^62 leafs, or height up to 62. /// /// The used hash function is [`Tip5`]. /// -/// [^1]: https://github.com/Neptune-Crypto/twenty-first/pull/250#issuecomment-2782490889 -/// [merkle_tree]: https://en.wikipedia.org/wiki/Merkle_tree +/// [1]: +/// [^2]: #[derive(Debug, Clone, PartialEq, Eq)] pub struct MerkleTree { nodes: Vec, From 53b3ee2182326a419788521c6214dc40879f90d0 Mon Sep 17 00:00:00 2001 From: aszepieniec Date: Thu, 24 Apr 2025 16:45:48 +0200 Subject: [PATCH 05/19] chore: Accept reviewer suggestions Co-authored-by: Jan Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index fba5db791..90bee1362 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -121,7 +121,7 @@ impl MerkleTree { /// with the same type as the index type used by `Vec`. /// /// If you need to read the root, try [`root()`](Self::root) instead. - const ROOT_INDEX: usize = 1_usize; + const ROOT_INDEX: usize = ROOT_INDEX as usize; const MAX_NUM_NODES: usize = 1_usize << 25; const MAX_NUM_LEAFS: usize = Self::MAX_NUM_NODES / 2; @@ -299,7 +299,7 @@ impl MerkleTree { pub fn height(&self) -> MerkleTreeHeight { let leaf_count = self.num_leafs(); debug_assert!(leaf_count.is_power_of_two()); - MerkleTreeHeight::try_from(leaf_count.ilog2()).expect("log of num leafs should fit in u32") + leaf_count.ilog2() } /// All nodes of the Merkle tree. @@ -316,11 +316,14 @@ impl MerkleTree { /// All leafs of the Merkle tree. pub fn leafs(&self) -> impl Iterator { - self.nodes.iter().skip( - (self.num_nodes() / 2) - .try_into() - .expect("MerkleTreeNodeIndex to usize conversion error"), - ) + // This conversion can only fail if the number of leafs is larger than + // usize::MAX. This implies that the number of nodes is larger than + // usize::MAX. Since the nodes are stored in a Vec, the number of nodes + // can never exceed usize::MAX. + // This proof by contradiction shows that unwrapping is fine. + let num_leafs = usize::try_from(self.num_leafs()).unwrap(); + + self.nodes.iter().skip(num_leafs) } /// The leaf at the given index, if it exists. @@ -656,14 +659,7 @@ pub mod merkle_tree_test { #[strategy(arb())] pub tree: MerkleTree, - #[ - strategy( - vec( - (0 as MerkleTreeLeafIndex)..#tree.num_leafs(), - 0..(#tree.num_leafs() as usize) - ) - ) - ] + #[strategy(vec(0..#tree.num_leafs(), 0..(#tree.num_leafs() as usize)))] pub selected_indices: Vec, } From f97a04f0a71ddeb1b83c637f57d3467154793ce2 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 24 Apr 2025 18:30:23 +0200 Subject: [PATCH 06/19] chore: Integrate reviewer suggestions - chore: Use correct type alias - style: Use standard strings for primitive type conversion error - docs: Explain impossible source of error Co-authored-by: Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 90bee1362..7dcd7a1ff 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -9,6 +9,7 @@ use lazy_static::lazy_static; use rayon::prelude::*; use thiserror::Error; +use crate::error::USIZE_TO_U64_ERR; use crate::prelude::*; const DEFAULT_PARALLELIZATION_CUTOFF: usize = 512; @@ -212,7 +213,7 @@ impl MerkleTree { pub fn authentication_structure_node_indices( num_leafs: MerkleTreeLeafIndex, leaf_indices: &[MerkleTreeLeafIndex], - ) -> Result + use<>> { + ) -> Result + use<>> { // The set of indices of nodes that need to be included in the authentications // structure. In principle, every node of every authentication path is needed. // The root is never needed. Hence, it is not considered below. @@ -291,7 +292,7 @@ impl MerkleTree { } pub fn num_leafs(&self) -> MerkleTreeLeafIndex { - let node_count = MerkleTreeNodeIndex::try_from(self.nodes.len()).expect("usize to u64"); + let node_count = MerkleTreeNodeIndex::try_from(self.nodes.len()).expect(USIZE_TO_U64_ERR); debug_assert!(node_count.is_power_of_two()); node_count / 2 } @@ -309,6 +310,13 @@ impl MerkleTree { /// The node at the given node index, if it exists. pub fn node(&self, index: MerkleTreeNodeIndex) -> Option { + // If `MerkleTreeNodeIndex` aka u64 cannot be converted to usize, that + // means + // (1) the current architecture has a pointer width smaller than 64 + // bits, and + // (2) the current index is larger than the pointer width. + // Therefore, Merkle trees with the number of nodes implied by the + // requested index cannot even constructed on the current architecture. usize::try_from(index) .ok() .and_then(|idx| self.nodes.get(idx).copied()) @@ -344,8 +352,7 @@ impl MerkleTree { } fn num_nodes(&self) -> MerkleTreeNodeIndex { - MerkleTreeNodeIndex::try_from(self.nodes.len()) - .expect("`MerkleTreeNodeIndex` should be large enough to hold any usize") + MerkleTreeNodeIndex::try_from(self.nodes.len()).expect(USIZE_TO_U64_ERR) } /// A full inclusion proof for the leafs at the supplied indices, including the From d04e8aa544ba0bd65dca201afb861848cbc5f03b Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 24 Apr 2025 19:30:01 +0200 Subject: [PATCH 07/19] refactor!(`MerkleTree`): Kill public methods Specifically: kill `nodes()` and `num_nodes()`. If you want to get a specific node, use `merkle_tree.node(i)` instead of `merkle_tree.nodes()[i]`. --- twenty-first/src/util_types/merkle_tree.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 7dcd7a1ff..4985076e3 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -303,12 +303,10 @@ impl MerkleTree { leaf_count.ilog2() } - /// All nodes of the Merkle tree. - pub fn nodes(&self) -> impl Iterator { - self.nodes.iter() - } - /// The node at the given node index, if it exists. + /// + /// Note that nodes are 1-indexed, meaning that the root lives at index 1 + /// and all the other nodes have larger indices. pub fn node(&self, index: MerkleTreeNodeIndex) -> Option { // If `MerkleTreeNodeIndex` aka u64 cannot be converted to usize, that // means @@ -336,7 +334,7 @@ impl MerkleTree { /// The leaf at the given index, if it exists. pub fn leaf(&self, index: MerkleTreeLeafIndex) -> Option { - let first_leaf_index = self.num_nodes() / 2; + let first_leaf_index = self.num_leafs(); self.node(first_leaf_index + index) } @@ -351,10 +349,6 @@ impl MerkleTree { indices.iter().map(maybe_indexed_leaf).collect() } - fn num_nodes(&self) -> MerkleTreeNodeIndex { - MerkleTreeNodeIndex::try_from(self.nodes.len()).expect(USIZE_TO_U64_ERR) - } - /// A full inclusion proof for the leafs at the supplied indices, including the /// leafs. Generally, using [`authentication_structure`][auth_structure] is /// preferable. Use this method only if the verifier needs explicit access to the From 4612076bd96cbe71381db68f9defe838e03bc571 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 5 May 2025 14:09:39 +0200 Subject: [PATCH 08/19] docs(`MerkleTree`): Document index conventions in type aliases' docstrings --- twenty-first/src/util_types/merkle_tree.rs | 74 ++++++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 4985076e3..e5e7a6f92 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -21,8 +21,47 @@ lazy_static! { .unwrap_or(DEFAULT_PARALLELIZATION_CUTOFF); } +/// Indexes internal nodes of a [`MerkleTree`]. +/// +/// The following convention is used. +/// - Nothing lives at index 0. +/// - Index 1 points to the root. +/// - Indices 2 and 3 contain the two children of the root. +/// - Indices 4 and 5 contain the two children of node 2. +/// - Indices 6 and 7 contain the two children of node 3. +/// - And so on. In general, the position (starting at 0) of the top bit +/// indicates the number of layers of separation between this node and the +/// root. +/// - The node indices corresponding to leafs range from (1< Option { let first_leaf_index = self.num_leafs(); self.node(first_leaf_index + index) } + /// Produce a [`Vec`] of ([`MerkleTreeLeafIndex`], [`Digest`]) covering all + /// leafs. pub fn indexed_leafs( &self, indices: &[MerkleTreeLeafIndex], @@ -349,10 +393,12 @@ impl MerkleTree { indices.iter().map(maybe_indexed_leaf).collect() } - /// A full inclusion proof for the leafs at the supplied indices, including the - /// leafs. Generally, using [`authentication_structure`][auth_structure] is - /// preferable. Use this method only if the verifier needs explicit access to the - /// leafs, _i.e._, cannot compute them from other information. + /// A full inclusion proof for the leafs at the supplied + /// [`MerkleTreeLeafIndex`]es, *including* the leafs + /// + /// Generally, using [`authentication_structure`][auth_structure] is + /// preferable. Use this method only if the verifier needs explicit access + /// to the leafs, _i.e._, cannot compute them from other information. /// /// [auth_structure]: Self::authentication_structure pub fn inclusion_proof_for_leaf_indices( From 63fa287f07628ce7edd59d21cd7583cab33cb015 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 5 May 2025 14:30:16 +0200 Subject: [PATCH 09/19] refactor!(`MerkleTree`): Drop maxima Drop the `const`s `MAX_NUM_NODES`, `MAX_NUM_LEAFS`, and `MAX_TREE_HEIGHT`. The consts supposedly guarantee cross-platform support. However: - It is unclear if any platform supports trees with 2^64 nodes, so the support guarantee is kind of vacuous. - Transferring Merkle trees between different machines is not a supported use case. Cf. #251. Co-authored-by: Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 46 ++++++---------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index e5e7a6f92..8942658dc 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -64,26 +64,6 @@ pub type MerkleTreeLeafIndex = u64; /// Type alias for [u32]. pub type MerkleTreeHeight = u32; -/// The maximum number of nodes in Merkle trees that functions in this module -/// support. -/// -/// This constant enforces that all compilation targets have a consistent -/// [`MAX_TREE_HEIGHT`] and [`MAX_NUM_LEAFS`]. -/// -/// Note that the struct [`MerkleTree`] can hold only 2^25 nodes. This constant -/// applies to associated functions that do not take `self`. -const MAX_NUM_NODES: MerkleTreeNodeIndex = - MerkleTreeNodeIndex::MAX ^ (MerkleTreeNodeIndex::MAX >> 1); - -/// The maximum number of leafs in Merkle trees that functions in this module -/// support. -/// -/// See also: [`MAX_NUM_NODES`], [`MAX_TREE_HEIGHT`]. -const MAX_NUM_LEAFS: MerkleTreeLeafIndex = MAX_NUM_NODES / 2; - -/// The maximum height of Merkle trees that functions in this module support. -pub const MAX_TREE_HEIGHT: MerkleTreeHeight = MAX_NUM_LEAFS.ilog2() as MerkleTreeHeight; - /// The index of the root node. pub(crate) const ROOT_INDEX: MerkleTreeNodeIndex = 1; @@ -163,9 +143,6 @@ impl MerkleTree { /// If you need to read the root, try [`root()`](Self::root) instead. const ROOT_INDEX: usize = ROOT_INDEX as usize; - const MAX_NUM_NODES: usize = 1_usize << 25; - const MAX_NUM_LEAFS: usize = Self::MAX_NUM_NODES / 2; - /// Build a MerkleTree with the given leafs. /// /// [`MerkleTree::par_new`] is equivalent and usually faster. @@ -231,11 +208,16 @@ impl MerkleTree { if !num_leafs.is_power_of_two() { return Err(MerkleTreeError::IncorrectNumberOfLeafs); } - if num_leafs > Self::MAX_NUM_LEAFS { - return Err(MerkleTreeError::TreeTooHigh); - } - let mut nodes = vec![Digest::default(); 2 * num_leafs]; + let num_nodes = 2 * num_leafs; + let mut nodes = Vec::new(); + + // Use `try_reserve_exact` because we want to get an error not a panic + // if allocation fails. The error can be bubbled up. + nodes + .try_reserve_exact(num_nodes) + .map_err(|_| MerkleTreeError::TreeTooHigh)?; + nodes.resize(num_nodes, Digest::default()); nodes[num_leafs..].copy_from_slice(leafs); Ok(nodes) @@ -498,10 +480,8 @@ impl PartialMerkleTree { } fn num_leafs(&self) -> Result { - if self.tree_height > MAX_TREE_HEIGHT { - return Err(MerkleTreeError::TreeTooHigh); - } - Ok(1 << self.tree_height) + 1u64.checked_shl(self.tree_height) + .ok_or(MerkleTreeError::TreeTooHigh) } /// Compute all computable digests of the partial Merkle tree, modifying self. @@ -663,7 +643,7 @@ pub enum MerkleTreeError { #[error("The number of leafs must be a power of two.")] IncorrectNumberOfLeafs, - #[error("Tree height must not exceed {MAX_TREE_HEIGHT}.")] + #[error("Tree height must not exceed 63.")] TreeTooHigh, } @@ -923,7 +903,7 @@ pub mod merkle_tree_test { #[proptest(cases = 40)] fn incorrect_tree_height_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, - #[strategy(0..=MAX_TREE_HEIGHT)] + #[strategy(0_u32..64)] #[filter(#test_tree.tree.height() != #incorrect_height)] incorrect_height: MerkleTreeHeight, ) { From 5027e0ae8013427c1ad20272c8fde2f958af3c1e Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 5 May 2025 15:12:15 +0200 Subject: [PATCH 10/19] ci: Verify build on 32-bit targets Co-authored-by: Ferdinand Sauer --- .github/workflows/32bit.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/32bit.yml diff --git a/.github/workflows/32bit.yml b/.github/workflows/32bit.yml new file mode 100644 index 000000000..5f55d675e --- /dev/null +++ b/.github/workflows/32bit.yml @@ -0,0 +1,26 @@ +name: 32bit + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + name: Build for 32-bit targets + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Build for 32-bit architectures + run: | + rustup target add i686-unknown-linux-gnu wasm32-unknown-unknown + cargo build --target i686-unknown-linux-gnu + cargo build --target wasm32-unknown-unknown From 4469810ac81c38546996dcd19c6645c734c34692 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 5 May 2025 15:30:53 +0200 Subject: [PATCH 11/19] ci: Fix 32-bit workflow Co-authored-by: Perplexity.ai --- .github/workflows/32bit.yml | 15 ++++++++++----- twenty-first/Cargo.toml | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/32bit.yml b/.github/workflows/32bit.yml index 5f55d675e..e4d518d22 100644 --- a/.github/workflows/32bit.yml +++ b/.github/workflows/32bit.yml @@ -19,8 +19,13 @@ jobs: - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - - name: Build for 32-bit architectures - run: | - rustup target add i686-unknown-linux-gnu wasm32-unknown-unknown - cargo build --target i686-unknown-linux-gnu - cargo build --target wasm32-unknown-unknown + - name: Add targets + run: rustup target add i686-unknown-linux-gnu wasm32-unknown-unknown + + - name: Build for i686-unknown-linux-gnu + run: cargo build --target i686-unknown-linux-gnu + + - name: Build for wasm32-unknown-unknown + run: cargo build --target wasm32-unknown-unknown + env: + RUSTFLAGS: --cfg getrandom_backend="wasm_js" diff --git a/twenty-first/Cargo.toml b/twenty-first/Cargo.toml index 1aadf98e1..d1ae1688c 100644 --- a/twenty-first/Cargo.toml +++ b/twenty-first/Cargo.toml @@ -42,6 +42,7 @@ hashbrown = "0.15" hex = "0.4.3" itertools = "0.14" lazy_static = "1.5.0" +getrandom = { version = "0.3.2", features = ["wasm_js"] } num-bigint = { version = "0.4", features = ["serde"] } num-traits = "0.2" phf = { version = "0.11", features = ["macros"] } From 8b372a513c5b0c91578b62c902219818f5d76d2d Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 6 May 2025 12:22:28 +0200 Subject: [PATCH 12/19] chore: Revert CI check for 32-bit compilation Commits in question give trouble when using `twenty-first` as a dependency. Reason unclear. 1. Revert "ci: Fix 32-bit workflow" This reverts commit 4469810ac81c38546996dcd19c6645c734c34692. 2. Revert "ci: Verify build on 32-bit targets" This reverts commit 5027e0ae8013427c1ad20272c8fde2f958af3c1e. --- .github/workflows/32bit.yml | 31 ------------------------------- twenty-first/Cargo.toml | 1 - 2 files changed, 32 deletions(-) delete mode 100644 .github/workflows/32bit.yml diff --git a/.github/workflows/32bit.yml b/.github/workflows/32bit.yml deleted file mode 100644 index e4d518d22..000000000 --- a/.github/workflows/32bit.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: 32bit - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - name: Build for 32-bit targets - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Add targets - run: rustup target add i686-unknown-linux-gnu wasm32-unknown-unknown - - - name: Build for i686-unknown-linux-gnu - run: cargo build --target i686-unknown-linux-gnu - - - name: Build for wasm32-unknown-unknown - run: cargo build --target wasm32-unknown-unknown - env: - RUSTFLAGS: --cfg getrandom_backend="wasm_js" diff --git a/twenty-first/Cargo.toml b/twenty-first/Cargo.toml index d1ae1688c..1aadf98e1 100644 --- a/twenty-first/Cargo.toml +++ b/twenty-first/Cargo.toml @@ -42,7 +42,6 @@ hashbrown = "0.15" hex = "0.4.3" itertools = "0.14" lazy_static = "1.5.0" -getrandom = { version = "0.3.2", features = ["wasm_js"] } num-bigint = { version = "0.4", features = ["serde"] } num-traits = "0.2" phf = { version = "0.11", features = ["macros"] } From d9f41f9122eb5e2bb370c797c0cedd82fb87a7eb Mon Sep 17 00:00:00 2001 From: Alan Date: Thu, 8 May 2025 16:50:23 +0200 Subject: [PATCH 13/19] refactor(`MerkleTree`): Change index type back to usize --- twenty-first/src/util_types/merkle_tree.rs | 50 +++++++------------ .../util_types/mmr/mmr_membership_proof.rs | 2 +- .../src/util_types/mmr/mmr_successor_proof.rs | 4 +- .../src/util_types/mmr/shared_basic.rs | 2 +- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 8942658dc..61b545ad9 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -47,15 +47,15 @@ lazy_static! { /// 8 9 10 11 12 13 14 15 ╯ /// ``` /// -/// Type alias for [u64]. -pub type MerkleTreeNodeIndex = u64; +/// Type alias for [usize]. +pub type MerkleTreeNodeIndex = usize; /// Indexes the leafs of a Merkle tree, left to right, starting with zero and /// ending with one less than a power of two. The exponent of that power of two /// coincides with the tree's height. /// -/// Type alias for [u64]. -pub type MerkleTreeLeafIndex = u64; +/// Type alias for [usize]. +pub type MerkleTreeLeafIndex = usize; /// Counts the number of layers in the Merkle tree, not including the root. /// Equivalently, counts the number of nodes on a path from a leaf to the root, @@ -137,12 +137,6 @@ pub(crate) struct PartialMerkleTree { } impl MerkleTree { - /// When iterating over [`Self::nodes`] it pays to have an starting index - /// with the same type as the index type used by `Vec`. - /// - /// If you need to read the root, try [`root()`](Self::root) instead. - const ROOT_INDEX: usize = ROOT_INDEX as usize; - /// Build a MerkleTree with the given leafs. /// /// [`MerkleTree::par_new`] is equivalent and usually faster. @@ -154,7 +148,7 @@ impl MerkleTree { pub fn sequential_new(leafs: &[Digest]) -> Result { let mut nodes = Self::initialize_merkle_tree_nodes(leafs)?; - for i in (MerkleTree::ROOT_INDEX..leafs.len()).rev() { + for i in (ROOT_INDEX..leafs.len()).rev() { nodes[i] = Tip5::hash_pair(nodes[i * 2], nodes[i * 2 + 1]); } @@ -189,7 +183,7 @@ impl MerkleTree { // sequential let num_remaining_nodes = num_nodes_on_this_level; - for i in (MerkleTree::ROOT_INDEX..num_remaining_nodes).rev() { + for i in (ROOT_INDEX..num_remaining_nodes).rev() { nodes[i] = Tip5::hash_pair(nodes[i * 2], nodes[i * 2 + 1]); } @@ -312,7 +306,7 @@ impl MerkleTree { } pub fn root(&self) -> Digest { - self.nodes[Self::ROOT_INDEX] + self.nodes[ROOT_INDEX] } pub fn num_leafs(&self) -> MerkleTreeLeafIndex { @@ -332,16 +326,11 @@ impl MerkleTree { /// Note that nodes are 1-indexed, meaning that the root lives at index 1 /// and all the other nodes have larger indices. pub fn node(&self, index: MerkleTreeNodeIndex) -> Option { - // If `MerkleTreeNodeIndex` aka u64 cannot be converted to usize, that - // means - // (1) the current architecture has a pointer width smaller than 64 - // bits, and - // (2) the current index is larger than the pointer width. - // Therefore, Merkle trees with the number of nodes implied by the - // requested index cannot even constructed on the current architecture. - usize::try_from(index) - .ok() - .and_then(|idx| self.nodes.get(idx).copied()) + if index == 0 { + None + } else { + self.nodes.get(index).copied() + } } /// All leafs of the Merkle tree. @@ -351,7 +340,7 @@ impl MerkleTree { // usize::MAX. Since the nodes are stored in a Vec, the number of nodes // can never exceed usize::MAX. // This proof by contradiction shows that unwrapping is fine. - let num_leafs = usize::try_from(self.num_leafs()).unwrap(); + let num_leafs = self.num_leafs(); self.nodes.iter().skip(num_leafs) } @@ -480,7 +469,8 @@ impl PartialMerkleTree { } fn num_leafs(&self) -> Result { - 1u64.checked_shl(self.tree_height) + 1usize + .checked_shl(self.tree_height) .ok_or(MerkleTreeError::TreeTooHigh) } @@ -674,7 +664,7 @@ pub mod merkle_tree_test { ) -> HashMap { node_indices .iter() - .map(|&i| (i, BFieldElement::new(i))) + .map(|&i| (i, BFieldElement::new(u64::try_from(i).unwrap()))) .map(|(i, leaf)| (i, Tip5::hash_varlen(&[leaf]))) .collect() } @@ -686,7 +676,7 @@ pub mod merkle_tree_test { #[strategy(arb())] pub tree: MerkleTree, - #[strategy(vec(0..#tree.num_leafs(), 0..(#tree.num_leafs() as usize)))] + #[strategy(vec(0..#tree.num_leafs(), 0..(#tree.num_leafs())))] pub selected_indices: Vec, } @@ -866,7 +856,7 @@ pub mod merkle_tree_test { strategy( vec( 0..#test_tree.tree.num_leafs(), - 1..=(#test_tree.tree.num_leafs() as usize) + 1..=(#test_tree.tree.num_leafs()) ) ) ] @@ -989,9 +979,7 @@ pub mod merkle_tree_test { fn each_leaf_can_be_verified_individually(test_tree: MerkleTreeToTest) { let tree = test_tree.tree; for (leaf_index, &leaf) in tree.leafs().enumerate() { - let authentication_path = tree - .authentication_structure(&[leaf_index.try_into().unwrap()]) - .unwrap(); + let authentication_path = tree.authentication_structure(&[leaf_index]).unwrap(); let proof = MerkleTreeInclusionProof { tree_height: tree.height(), indexed_leafs: [(leaf_index as MerkleTreeLeafIndex, leaf)].into(), diff --git a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs index fe3805614..8421aa787 100644 --- a/twenty-first/src/util_types/mmr/mmr_membership_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_membership_proof.rs @@ -69,7 +69,7 @@ impl MmrMembershipProof { }; mt_index /= 2; } - debug_assert_eq!(merkle_tree::ROOT_INDEX, mt_index); + debug_assert_eq!(merkle_tree::ROOT_INDEX as u64, mt_index); let peak_index = usize::try_from(peak_index).expect(U32_TO_USIZE_ERR); diff --git a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs index f38e076e6..bcf776c78 100644 --- a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs @@ -61,7 +61,7 @@ impl MmrSuccessorProof { let mut old_peaks = mmra.peaks().into_iter(); let mut first_unused_new_leaf_idx = num_leafs_in_lowest_peak; - let merkle_tree_root_index = merkle_tree::ROOT_INDEX; + let merkle_tree_root_index = merkle_tree::ROOT_INDEX as u64; while merkle_tree_index > merkle_tree_root_index { let current_node_is_left_sibling = merkle_tree_index % 2 == 0; current_node = if current_node_is_left_sibling { @@ -195,7 +195,7 @@ impl MmrSuccessorProof { let mut current_node = *auth_path.next().ok_or(Error::AuthenticationPathTooShort)?; let mut merkle_tree_index = merkle_tree_index >> height_of_lowest_old_peak; - let merkle_tree_root_index = merkle_tree::ROOT_INDEX; + let merkle_tree_root_index = merkle_tree::ROOT_INDEX as u64; while merkle_tree_index > merkle_tree_root_index { let current_node_is_left_sibling = merkle_tree_index % 2 == 0; current_node = if current_node_is_left_sibling { diff --git a/twenty-first/src/util_types/mmr/shared_basic.rs b/twenty-first/src/util_types/mmr/shared_basic.rs index c94326d71..9e730cec0 100644 --- a/twenty-first/src/util_types/mmr/shared_basic.rs +++ b/twenty-first/src/util_types/mmr/shared_basic.rs @@ -111,7 +111,7 @@ pub fn calculate_new_peaks_from_leaf_mutation( leaf_index: u64, membership_proof: &MmrMembershipProof, ) -> Vec { - let merkle_tree_root_index = merkle_tree::ROOT_INDEX; + let merkle_tree_root_index = merkle_tree::ROOT_INDEX as u64; let (mut acc_mt_index, peak_index) = leaf_index_to_mt_index_and_peak_index(leaf_index, num_leafs); From 96db7f92fbd7edab9f65eedd99b27c4dc4b4aaf1 Mon Sep 17 00:00:00 2001 From: aszepieniec Date: Fri, 9 May 2025 10:58:42 +0200 Subject: [PATCH 14/19] style: Inline variable Co-authored-by: Jan Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 61b545ad9..7241d25b9 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -340,9 +340,7 @@ impl MerkleTree { // usize::MAX. Since the nodes are stored in a Vec, the number of nodes // can never exceed usize::MAX. // This proof by contradiction shows that unwrapping is fine. - let num_leafs = self.num_leafs(); - - self.nodes.iter().skip(num_leafs) + self.nodes.iter().skip(self.num_leafs()) } /// The leaf at the given [`MerkleTreeLeafIndex`], if it exists. From 4e72bfb6b1ce920d8c077dcf185df51fcb5d0f04 Mon Sep 17 00:00:00 2001 From: aszepieniec Date: Fri, 9 May 2025 10:59:01 +0200 Subject: [PATCH 15/19] style: Use `bfe!` macro Co-authored-by: Jan Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 7241d25b9..e32541c43 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -662,7 +662,7 @@ pub mod merkle_tree_test { ) -> HashMap { node_indices .iter() - .map(|&i| (i, BFieldElement::new(u64::try_from(i).unwrap()))) + .map(|&i| (i, bfe!(i))) .map(|(i, leaf)| (i, Tip5::hash_varlen(&[leaf]))) .collect() } From 4d7fc1718cdab39106900993d1155d15b6b49a21 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 9 May 2025 12:25:19 +0200 Subject: [PATCH 16/19] docs: Drop deprecated comment --- twenty-first/src/util_types/merkle_tree.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index e32541c43..385512750 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -335,11 +335,6 @@ impl MerkleTree { /// All leafs of the Merkle tree. pub fn leafs(&self) -> impl Iterator { - // This conversion can only fail if the number of leafs is larger than - // usize::MAX. This implies that the number of nodes is larger than - // usize::MAX. Since the nodes are stored in a Vec, the number of nodes - // can never exceed usize::MAX. - // This proof by contradiction shows that unwrapping is fine. self.nodes.iter().skip(self.num_leafs()) } From 62204734c3fec0b88c17708fdce985300a9bb507 Mon Sep 17 00:00:00 2001 From: aszepieniec Date: Tue, 13 May 2025 13:08:05 +0200 Subject: [PATCH 17/19] style: Admit reviewer nits Update twenty-first/src/util_types/merkle_tree.rs Co-authored-by: Jan Ferdinand Sauer Update twenty-first/src/util_types/merkle_tree.rs Co-authored-by: Jan Ferdinand Sauer Update twenty-first/src/util_types/merkle_tree.rs Co-authored-by: Jan Ferdinand Sauer Update twenty-first/src/util_types/merkle_tree.rs Co-authored-by: Jan Ferdinand Sauer Update twenty-first/src/util_types/merkle_tree.rs Co-authored-by: Jan Ferdinand Sauer Update twenty-first/src/util_types/merkle_tree.rs Co-authored-by: Jan Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 385512750..c683dbb39 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -462,7 +462,7 @@ impl PartialMerkleTree { } fn num_leafs(&self) -> Result { - 1usize + 1_usize .checked_shl(self.tree_height) .ok_or(MerkleTreeError::TreeTooHigh) } @@ -626,7 +626,7 @@ pub enum MerkleTreeError { #[error("The number of leafs must be a power of two.")] IncorrectNumberOfLeafs, - #[error("Tree height must not exceed 63.")] + #[error("Tree height implies a tree that does not fit in RAM")] TreeTooHigh, } @@ -845,14 +845,7 @@ pub mod merkle_tree_test { #[proptest(cases = 30)] fn checking_set_inclusion_of_items_not_in_set_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, - #[ - strategy( - vec( - 0..#test_tree.tree.num_leafs(), - 1..=(#test_tree.tree.num_leafs()) - ) - ) - ] + #[strategy(vec(0..#test_tree.tree.num_leafs(), 1..=(#test_tree.tree.num_leafs())))] spurious_indices: Vec, #[strategy(vec(any::(), #spurious_indices.len()))] spurious_digests: Vec, ) { @@ -911,7 +904,7 @@ pub mod merkle_tree_test { #[proptest(cases = 30)] fn requesting_inclusion_proof_for_nonexistent_leaf_fails_with_expected_error( #[strategy(arb())] tree: MerkleTree, - #[filter(#leaf_indices.iter().any(|&i| i > (#tree.num_leafs() as MerkleTreeLeafIndex)))] + #[filter(#leaf_indices.iter().any(|&i| i > #tree.num_leafs()))] leaf_indices: Vec, ) { let maybe_proof = tree.inclusion_proof_for_leaf_indices(&leaf_indices); @@ -975,7 +968,7 @@ pub mod merkle_tree_test { let authentication_path = tree.authentication_structure(&[leaf_index]).unwrap(); let proof = MerkleTreeInclusionProof { tree_height: tree.height(), - indexed_leafs: [(leaf_index as MerkleTreeLeafIndex, leaf)].into(), + indexed_leafs: [(leaf_index, leaf)].into(), authentication_structure: authentication_path, }; let verdict = proof.verify(tree.root()); @@ -1020,7 +1013,7 @@ pub mod merkle_tree_test { // // 0 2 <-- opened_leaf_indices - let node_indices = [3 as MerkleTreeNodeIndex, 8, 9, 10, 11]; + let node_indices = [3, 8, 9, 10, 11]; let mut partial_tree = PartialMerkleTree { tree_height: 3, leaf_indices: vec![0, 2], From 883dd5a28977db9aa2872f77e34025afe46f4f29 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 13 May 2025 13:15:33 +0200 Subject: [PATCH 18/19] style: Degrade formatting with `cargo fmt` --- twenty-first/src/util_types/merkle_tree.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index c683dbb39..7ea52d40e 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -904,8 +904,9 @@ pub mod merkle_tree_test { #[proptest(cases = 30)] fn requesting_inclusion_proof_for_nonexistent_leaf_fails_with_expected_error( #[strategy(arb())] tree: MerkleTree, - #[filter(#leaf_indices.iter().any(|&i| i > #tree.num_leafs()))] - leaf_indices: Vec, + #[filter(#leaf_indices.iter().any(|&i| i > #tree.num_leafs()))] leaf_indices: Vec< + MerkleTreeLeafIndex, + >, ) { let maybe_proof = tree.inclusion_proof_for_leaf_indices(&leaf_indices); let err = maybe_proof.unwrap_err(); From 6d3eb9b568556f8af6e2b65af81898e48ee772db Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 13 May 2025 13:25:35 +0200 Subject: [PATCH 19/19] style: Integrate reviewer feedback Co-authored-by: Ferdinand Sauer --- twenty-first/src/util_types/merkle_tree.rs | 8 ++++---- twenty-first/src/util_types/mmr/mmr_successor_proof.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/twenty-first/src/util_types/merkle_tree.rs b/twenty-first/src/util_types/merkle_tree.rs index 7ea52d40e..ff7dc3fbd 100644 --- a/twenty-first/src/util_types/merkle_tree.rs +++ b/twenty-first/src/util_types/merkle_tree.rs @@ -9,7 +9,6 @@ use lazy_static::lazy_static; use rayon::prelude::*; use thiserror::Error; -use crate::error::USIZE_TO_U64_ERR; use crate::prelude::*; const DEFAULT_PARALLELIZATION_CUTOFF: usize = 512; @@ -310,7 +309,7 @@ impl MerkleTree { } pub fn num_leafs(&self) -> MerkleTreeLeafIndex { - let node_count = MerkleTreeNodeIndex::try_from(self.nodes.len()).expect(USIZE_TO_U64_ERR); + let node_count = self.nodes.len(); debug_assert!(node_count.is_power_of_two()); node_count / 2 } @@ -824,8 +823,9 @@ pub mod merkle_tree_test { fn removing_leafs_from_proof_leads_to_verification_failure( #[filter(#test_tree.has_non_trivial_proof())] test_tree: MerkleTreeToTest, #[strategy(Just(#test_tree.proof().indexed_leafs.len()))] _n_leafs: usize, - #[strategy(vec(0..(#_n_leafs as MerkleTreeLeafIndex), 1..=#_n_leafs))] - leaf_indices_to_remove: Vec, + #[strategy(vec(0..(#_n_leafs), 1..=#_n_leafs))] leaf_indices_to_remove: Vec< + MerkleTreeLeafIndex, + >, ) { let mut proof = test_tree.proof(); let leafs_to_keep = proof diff --git a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs index bcf776c78..c072fb8a3 100644 --- a/twenty-first/src/util_types/mmr/mmr_successor_proof.rs +++ b/twenty-first/src/util_types/mmr/mmr_successor_proof.rs @@ -61,7 +61,8 @@ impl MmrSuccessorProof { let mut old_peaks = mmra.peaks().into_iter(); let mut first_unused_new_leaf_idx = num_leafs_in_lowest_peak; - let merkle_tree_root_index = merkle_tree::ROOT_INDEX as u64; + let merkle_tree_root_index = + u64::try_from(merkle_tree::ROOT_INDEX).expect(USIZE_TO_U64_ERR); while merkle_tree_index > merkle_tree_root_index { let current_node_is_left_sibling = merkle_tree_index % 2 == 0; current_node = if current_node_is_left_sibling {