Skip to content
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

BaseFold: Extract Merkle tree inner into a new struct. #560

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

yczhangsjtu
Copy link
Collaborator

@yczhangsjtu yczhangsjtu commented Nov 6, 2024

Extract small PR from #294

Currently the inner (without the leafs) of the Merkle tree is represented by a Vec<Vec<Digest>>. Sometimes (for optimization reasons, e.g., when the leaf values should stay in the original variable and we don't want to clone it into the Merkle tree) this inner part needs to be accessed and stored separately from the leaves. So we encapsulate it in a struct MerkleTreeDigests for more readability, implementing common methods on them, and allow enforcing particular constraints over the Vec<Vec<Digest>>.

This refactor provides new APIs for MerkleTreeDigests and make the corresponding MerkleTree APIs a thin wrapper around the APIs for MerkleTreeDigests. There are some new APIs and the behavior of existing APIs stay the same.

@yczhangsjtu yczhangsjtu changed the title Extract Merkle tree inner into a new struct. BaseFold: Extract Merkle tree inner into a new struct. Nov 6, 2024
Copy link
Collaborator

@matthiasgoergens matthiasgoergens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see comments.

Btw, you don't need to give the types everywhere, Rust can figure much of that out on its own. Here's an example of two places, but there are more:

diff --git a/mpcs/src/basefold/commit_phase.rs b/mpcs/src/basefold/commit_phase.rs
index 992745bb..5df3aa19 100644
--- a/mpcs/src/basefold/commit_phase.rs
+++ b/mpcs/src/basefold/commit_phase.rs
@@ -98,8 +98,7 @@ where
         );
 
         if i > 0 {
-            let running_tree =
-                MerkleTree::<E>::new(running_tree_inner, FieldType::Ext(running_oracle));
+            let running_tree = MerkleTree::new(running_tree_inner, FieldType::Ext(running_oracle));
             trees.push(running_tree);
         }
 
@@ -114,7 +113,7 @@ where
             // Then the oracle will be used to fold to the next oracle in the next
             // round. After that, this oracle is free to be moved to build the
             // complete Merkle tree.
-            running_tree_inner = MerkleTreeDigests::<E>::from_leaves_ext(&new_running_oracle);
+            running_tree_inner = MerkleTreeDigests::from_leaves_ext(&new_running_oracle);
             let running_root = running_tree_inner.root();
             write_digest_to_transcript(&running_root, transcript);
             roots.push(running_root.clone());
diff --git a/mpcs/src/util/merkle_tree.rs b/mpcs/src/util/merkle_tree.rs
index 53568af8..81af42bd 100644
--- a/mpcs/src/util/merkle_tree.rs
+++ b/mpcs/src/util/merkle_tree.rs
@@ -83,9 +83,7 @@ where
                 .iter()
                 .take(self.height() - 1)
                 .enumerate()
-                .map(|(index, layer)| {
-                    Digest::<E::BaseField>(layer[(leaf_group_index >> index) ^ 1].clone().0)
-                })
+                .map(|(index, layer)| Digest(layer[(leaf_group_index >> index) ^ 1].clone().0))
                 .collect(),
         )
     }

(Sometimes adding extra redundant type information helps readability, but here it just seems to add verbosity.)

mpcs/src/util/merkle_tree.rs Outdated Show resolved Hide resolved
pub fn root_from_inner(inner: &[Vec<Digest<E::BaseField>>]) -> Digest<E::BaseField> {
inner.last().unwrap()[0].clone()
pub fn root_ref(&self) -> &Digest<E::BaseField> {
&self.inner.last().unwrap()[0]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we require that inner not be empty. I suggest we encode that in the type, instead of going with Vec which is happy being empty.

Copy link
Collaborator Author

@yczhangsjtu yczhangsjtu Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be better to let the compiler help us enforce such constraints. But there are more to constrain than this, e.g., the size of the vectors should be 1, 2, 4, 8, and so on. I don't know how to encode all these in the type. Currently, it's just relying on inner being private, and is never changed once the Merkle tree is built, and all the unwrap() are concealed.

mpcs/src/util/merkle_tree.rs Show resolved Hide resolved
where
E::BaseField: Serialize + DeserializeOwned,
{
pub fn compute_inner(leaves: &FieldType<E>) -> Vec<Vec<Digest<E::BaseField>>> {
merkelize::<E>(&[leaves])
pub fn from_leaves(leaves: &FieldType<E>) -> Self {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider implementing From instances instead? (Not completely sure.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, either. Computing the Merkle tree is an expensive procedure, so we probably want it to be always invoked explicitly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust never invokes from implicitly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not in the language. I just have the impression (which may be wrong) that .into() gives people the hint that this is a cheap type conversion, invoking some wrapper functions, without any expensive stuff.

mpcs/src/util/merkle_tree.rs Outdated Show resolved Hide resolved
@matthiasgoergens
Copy link
Collaborator

matthiasgoergens commented Nov 6, 2024

Could you please add in the PR description why we are doing this?

What do we want to accomplish that gets easier or simpler with this refactoring? Oh, and is this supposed to be a pure refactoring, or does it change any behaviour?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants