Skip to content

Commit

Permalink
Add a MerkleTree builder (#3617)
Browse files Browse the repository at this point in the history
Co-authored-by: Ernesto García <ernestognw@gmail.com>
  • Loading branch information
Amxx and ernestognw committed Mar 7, 2024
1 parent e831429 commit 92ff025
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changeset/odd-files-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Hashes`: A library with commonly used hash functions.
5 changes: 5 additions & 0 deletions .changeset/warm-sheep-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash.
24 changes: 24 additions & 0 deletions contracts/mocks/ArraysMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ contract Uint256ArraysMock {
function _reverse(uint256 a, uint256 b) private pure returns (bool) {
return a > b;
}

function unsafeSetLength(uint256 newLength) external {
_array.unsafeSetLength(newLength);
}

function length() external view returns (uint256) {
return _array.length;
}
}

contract AddressArraysMock {
Expand All @@ -74,6 +82,14 @@ contract AddressArraysMock {
function _reverse(address a, address b) private pure returns (bool) {
return uint160(a) > uint160(b);
}

function unsafeSetLength(uint256 newLength) external {
_array.unsafeSetLength(newLength);
}

function length() external view returns (uint256) {
return _array.length;
}
}

contract Bytes32ArraysMock {
Expand All @@ -100,4 +116,12 @@ contract Bytes32ArraysMock {
function _reverse(bytes32 a, bytes32 b) private pure returns (bool) {
return uint256(a) > uint256(b);
}

function unsafeSetLength(uint256 newLength) external {
_array.unsafeSetLength(newLength);
}

function length() external view returns (uint256) {
return _array.length;
}
}
43 changes: 43 additions & 0 deletions contracts/mocks/MerkleTreeMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {MerkleTree} from "../utils/structs/MerkleTree.sol";

contract MerkleTreeMock {
using MerkleTree for MerkleTree.Bytes32PushTree;

MerkleTree.Bytes32PushTree private _tree;

event LeafInserted(bytes32 leaf, uint256 index, bytes32 root);

function setup(uint8 _depth, bytes32 _zero) public {
_tree.setup(_depth, _zero);
}

function push(bytes32 leaf) public {
(uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf);
emit LeafInserted(leaf, leafIndex, currentRoot);
}

function root() public view returns (bytes32) {
return _tree.root();
}

function depth() public view returns (uint256) {
return _tree.depth();
}

// internal state
function nextLeafIndex() public view returns (uint256) {
return _tree._nextLeafIndex;
}

function sides(uint256 i) public view returns (bytes32) {
return _tree._sides[i];
}

function zeros(uint256 i) public view returns (bytes32) {
return _tree._zeros[i];
}
}
33 changes: 33 additions & 0 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,37 @@ library Arrays {
res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
}
}

/**
* @dev Helper to set the length of an dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(address[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}

/**
* @dev Helper to set the length of an dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}

/**
* @dev Helper to set the length of an dynamic array. Directly writing to `.length` is forbidden.
*
* WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
*/
function unsafeSetLength(uint256[] storage array, uint256 len) internal {
assembly {
sstore(array.slot, len)
}
}
}
10 changes: 8 additions & 2 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {SafeCast}: Checked downcasting functions to avoid silent truncation.
* {ECDSA}, {MessageHashUtils}: Libraries for interacting with ECDSA signatures.
* {SignatureChecker}: A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts.
* {Hashes}: Commonly used hash functions.
* {MerkleProof}: Functions for verifying https://en.wikipedia.org/wiki/Merkle_tree[Merkle Tree] proofs.
* {EIP712}: Contract with functions to allow processing signed typed structure data according to https://eips.ethereum.org/EIPS/eip-712[EIP-712].
* {ReentrancyGuard}: A modifier that can prevent reentrancy during certain functions.
Expand All @@ -20,6 +21,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {EnumerableSet}: Like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc.
* {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be removed added or remove from both sides. Useful for FIFO and LIFO structures.
* {Checkpoints}: A data structure to store values mapped to an strictly increasing key. Can be used for storing and accessing values over time.
* {MerkleTree}: A library with https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] data structures and helper functions.
* {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly.
* {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type.
* {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`].
Expand Down Expand Up @@ -48,13 +50,15 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable

{{ECDSA}}

{{EIP712}}

{{MessageHashUtils}}

{{SignatureChecker}}

{{MerkleProof}}
{{Hashes}}

{{EIP712}}
{{MerkleProof}}

== Security

Expand Down Expand Up @@ -88,6 +92,8 @@ Ethereum contracts have no native concept of an interface, so applications must

{{Checkpoints}}

{{MerkleTree}}

== Libraries

{{Create2}}
Expand Down
29 changes: 29 additions & 0 deletions contracts/utils/cryptography/Hashes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @dev Library of standard hash functions.
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(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)
}
}
}
29 changes: 6 additions & 23 deletions contracts/utils/cryptography/MerkleProof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

pragma solidity ^0.8.20;

import {Hashes} from "./Hashes.sol";

/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
Expand Down Expand Up @@ -49,7 +51,7 @@ library MerkleProof {
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
Expand All @@ -60,7 +62,7 @@ library MerkleProof {
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
Expand Down Expand Up @@ -138,7 +140,7 @@ library MerkleProof {
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
hashes[i] = Hashes.commutativeKeccak256(a, b);
}

if (totalHashes > 0) {
Expand Down Expand Up @@ -194,7 +196,7 @@ library MerkleProof {
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
hashes[i] = Hashes.commutativeKeccak256(a, b);
}

if (totalHashes > 0) {
Expand All @@ -210,23 +212,4 @@ library MerkleProof {
return proof[0];
}
}

/**
* @dev Sorts the pair (a, b) and hashes the result.
*/
function _hashPair(bytes32 a, bytes32 b) private 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)
}
}
}

0 comments on commit 92ff025

Please sign in to comment.