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

Add a MerkleTree builder #3617

Merged
merged 54 commits into from
Mar 7, 2024
Merged

Conversation

Amxx
Copy link
Collaborator

@Amxx Amxx commented Aug 14, 2022

Structure inspired by Tornardo Cash's commitment tree.

  • Initialize a complete merkle tree of given depth, with all leaves having the same "zero" value.
  • Insert leaves one at a time, recomputing the merkle root in the process
  • Keep a "limited" (rotating) history of past merkle roots.

Fixes: #4758

PR Checklist

@Amxx Amxx changed the title Add a (complete) MerkleTree structure Add a MerkleTree structure Aug 16, 2022
@Amxx
Copy link
Collaborator Author

Amxx commented Aug 19, 2022

Just realized that if someone could be creative and commit merle roots of subtrees that use the same hashings function. That would allow verification of the leafs deep inside the subtrees

@frangio frangio changed the title Add a MerkleTree structure Add a MerkleTree builder Sep 2, 2022
Copy link

changeset-bot bot commented Dec 13, 2023

🦋 Changeset detected

Latest commit: eca9085

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
openzeppelin-solidity Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Amxx Amxx requested a review from ernestognw January 28, 2024 12:56
Comment on lines 9 to 26
/**
* @dev Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*/
function stdPairHash(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}

/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

What do you think of?

Suggested change
/**
* @dev Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*/
function stdPairHash(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
/**
* @dev Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*/
function stdPairKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Going further, what about sortedPairKeccak256 instead stdPairKeccak256 ?

Copy link
Member

Choose a reason for hiding this comment

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

I shared some extra thoughts here. I wasn't convinced of either so I'm open to chat about this again if we can agree on a better API for future additions.

contracts/utils/cryptography/Hashes.sol Outdated Show resolved Hide resolved
contracts/utils/cryptography/Hashes.sol Outdated Show resolved Hide resolved
contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
ernestognw
ernestognw previously approved these changes Feb 21, 2024
contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
* @dev Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*/
function stdPairHash(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
Copy link
Member

Choose a reason for hiding this comment

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

Just realized this function is commutative. Should the tree work with non-commutative functions?

I guess it should, so I'd feel better if we add tests with a custom non-commutative keccak256 function. It may require updating the javascript library for testing.

If the tree expects only commutative functions we should be explicit about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We should support non commutative hashes, so yes, we should test that.

Copy link
Member

Choose a reason for hiding this comment

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

Right, we should do this after the javascript library is released.

contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
@ernestognw
Copy link
Member

Took the freedom to update the documentation. I left some comments and I think this still needs some work but it's looking really good in my opinion.

@ernestognw
Copy link
Member

I gave it a thought and I started to see why you wanted to call the function stdPairKeccak256 like that in the Hashes library considering the context of the Javascript library (assuming it's for standard). I'd like to discuss the naming again.

Right now I'm not convinced of sortedPairKeccak256 either. The reason is that I think Pair is redundant. I feel we should agree on a naming convention for future additions to the Hashes library, and the distinction between sorting a pair or a single value should come from the function signature.

For example, for a sortedKeccak256, it'd be:

function sortedKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
    return a < b ? _efficientKeccak256(a, b) : _efficientKeccak256(b, a);
}

function sortedKeccak256(bytes32[] memory ptr) {
  ...
}

I'd say users would need to decide three things: the algorithm, the sorting (for multiproofs), and some particularity of the algorithm. In this case, it's just keccak256 with sorting, so it's fine if we call it sortedKeccak256 but I have a couple of questions:

  • Would it make sense an inverse sorted hashing function at all? For example, we could have sortedKeccak256(bytes32 a, bytes32 b, bool asc)
  • Furthermore, would it make sense to have a function with signature keccak256(bytes32, bytes32, Sorting) assuming keccak256 is not creating a conflict? In this case, Sorting would be either None, Asc, or Desc.

So far I'm feeling the best naming convention we can use would be using the name of the algorithm and that's it, but then I'd reconsider calling it stdHash to avoid the naming conflict.

I know the naming convention and variants would make it difficult for a user to decide what to use but we can wrap the common use cases:

function stdHash(bytes32 a, bytes32 b) {} // Default to sorted for multiproofs
function stdHash(bytes32 a, bytes32 b, Sorting sorting) {}
function poseidon(bytes32 a) {}
function poseidon(bytes32 a, bytes32 b) {} // Default to sorted for multiproofs
functon posiedon(bytes32 a, bytes32 b, Sorting sorting) {}

The main issue I see with this is the gas overhead of each hashing operation since it'd be evaluating the sorting each time, perhaps unnecessarily, but I think this is cleaner.

Sorry for bringing it up again, just want to make sure we release an API that behaves well over time, he. What do you think @Amxx?

Copy link
Member

@ernestognw ernestognw left a comment

Choose a reason for hiding this comment

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

Almost there!

contracts/utils/structs/MerkleTree.sol Outdated Show resolved Hide resolved
* @dev Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*/
function stdPairHash(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
Copy link
Member

Choose a reason for hiding this comment

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

Right, we should do this after the javascript library is released.

test/utils/structs/MerkleTree.test.js Outdated Show resolved Hide resolved
ernestognw and others added 2 commits March 4, 2024 18:57
Co-authored-by: Ernesto García <ernestognw@gmail.com>
Copy link
Member

@ernestognw ernestognw left a comment

Choose a reason for hiding this comment

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

LGTM, pending to discuss whether to keep the root stored or not

@Amxx Amxx merged commit 92ff025 into OpenZeppelin:master Mar 7, 2024
20 checks passed
@Amxx Amxx deleted the structure/merkletree branch March 7, 2024 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a new incremental on-chain merkle tree library folllowing the idea of Ethereum 2.0 deposit contract.
2 participants