From 73908da7810cae5ee44516516bf5d02c832439d1 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 31 Aug 2021 12:12:44 +0200 Subject: [PATCH 01/11] Add a MerkleProof.processProof utility function --- contracts/utils/cryptography/MerkleProof.sol | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index b3b4871de6c..a6dc190e834 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -23,6 +23,23 @@ library MerkleProof { bytes32 root, bytes32 leaf ) internal pure returns (bool) { + // Check if the computed hash (root) is equal to the provided root + return processProof(proof, leaf) == root; + } + + /** + * @dev Returns the rebuild hash of node obtained by traversing a Merklee + * tree up from `leaf` using `proof`. If `proof` proof is valid, in the send + * that it contains the hashes of the sibling on the branch from the leaf to + * the root, then this will return the hash of the root of the tree. In + * processing the proof we assume the pairs of leafs & pre-images are sorted. + * + * _Available since v4.4._ + */ + function processProof( + bytes32[] memory proof, + bytes32 leaf + ) internal pure returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { @@ -37,7 +54,6 @@ library MerkleProof { } } - // Check if the computed hash (root) is equal to the provided root - return computedHash == root; + return computedHash; } } From 5b5d651c5d2824a09fb3c826a4c4ebd646cb057d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 31 Aug 2021 12:15:40 +0200 Subject: [PATCH 02/11] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99972ed62f4..8ca95cd1ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + + * `MerkleProof`: add a processProof function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) + ## 4.3.1 * `TimelockController`: Add additional isOperationReady check. From 77352ef990add2c26e44281f6c2d574c59e709a0 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 1 Sep 2021 14:30:16 +0200 Subject: [PATCH 03/11] fix lint --- contracts/utils/cryptography/MerkleProof.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index a6dc190e834..869b79efee0 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -36,10 +36,7 @@ library MerkleProof { * * _Available since v4.4._ */ - function processProof( - bytes32[] memory proof, - bytes32 leaf - ) internal pure returns (bytes32) { + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { From 0938323add728c0f9fc95a8861b03a4ae48e9b4d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 9 Sep 2021 13:54:59 +0200 Subject: [PATCH 04/11] return index when processingProof --- contracts/mocks/MerkleProofWrapper.sol | 7 +++++++ contracts/utils/cryptography/MerkleProof.sol | 14 ++++++++------ test/utils/cryptography/MerkleProof.test.js | 19 ++++++++++++++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/contracts/mocks/MerkleProofWrapper.sol b/contracts/mocks/MerkleProofWrapper.sol index 6189eb4aef7..3215efdb752 100644 --- a/contracts/mocks/MerkleProofWrapper.sol +++ b/contracts/mocks/MerkleProofWrapper.sol @@ -12,4 +12,11 @@ contract MerkleProofWrapper { ) public pure returns (bool) { return MerkleProof.verify(proof, root, leaf); } + + function processProof( + bytes32[] memory proof, + bytes32 leaf + ) public pure returns (bytes32, uint256) { + return MerkleProof.processProof(proof, leaf); + } } diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 869b79efee0..d809c228f95 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -24,7 +24,8 @@ library MerkleProof { bytes32 leaf ) internal pure returns (bool) { // Check if the computed hash (root) is equal to the provided root - return processProof(proof, leaf) == root; + (bytes32 computedHash,) = processProof(proof, leaf); + return computedHash == root; } /** @@ -36,21 +37,22 @@ library MerkleProof { * * _Available since v4.4._ */ - function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32, uint256) { bytes32 computedHash = leaf; - + uint256 index = 0; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; - + index <<= 1; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); + index |= 1; + // index |= 1 << (proof.length - i - 1); // option 2: better ? } } - - return computedHash; + return (computedHash, index); } } diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index cb7be8642ac..df4617164f4 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -3,7 +3,7 @@ require('@openzeppelin/test-helpers'); const { MerkleTree } = require('merkletreejs'); const keccak256 = require('keccak256'); -const { expect } = require('chai'); +const { expect, assert } = require('chai'); const MerkleProofWrapper = artifacts.require('MerkleProofWrapper'); @@ -56,4 +56,21 @@ contract('MerkleProof', function (accounts) { expect(await this.merkleProof.verify(badProof, root, leaf)).to.equal(false); }); }); + + describe('processProof', function () { + it('create unique indices', async function () { + const elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''); + const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true }); + + const root = merkleTree.getHexRoot(); + + const results = await Promise.all(elements + .map(element => keccak256(element)) + .map(leaf => this.merkleProof.processProof(merkleTree.getHexProof(leaf), leaf)) + ); + + assert(results.map(result => result[0]).every(value => value === root), 'processProof rebuilt invalid root'); + assert(results.map(result => result[1].toNumber()).every((value, i, values) => values.indexOf(value) === i), 'processProof rebuilt indices duplicate'); + }); + }); }); From 318df029991ef4135d37e075733ed86fb6e3c43c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 9 Sep 2021 14:53:14 +0200 Subject: [PATCH 05/11] fix lint --- contracts/mocks/MerkleProofWrapper.sol | 5 +---- contracts/utils/cryptography/MerkleProof.sol | 2 +- test/utils/cryptography/MerkleProof.test.js | 11 +++++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/mocks/MerkleProofWrapper.sol b/contracts/mocks/MerkleProofWrapper.sol index 3215efdb752..b8d036372fe 100644 --- a/contracts/mocks/MerkleProofWrapper.sol +++ b/contracts/mocks/MerkleProofWrapper.sol @@ -13,10 +13,7 @@ contract MerkleProofWrapper { return MerkleProof.verify(proof, root, leaf); } - function processProof( - bytes32[] memory proof, - bytes32 leaf - ) public pure returns (bytes32, uint256) { + function processProof(bytes32[] memory proof, bytes32 leaf) public pure returns (bytes32, uint256) { return MerkleProof.processProof(proof, leaf); } } diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index d809c228f95..9ffd1cceba5 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -24,7 +24,7 @@ library MerkleProof { bytes32 leaf ) internal pure returns (bool) { // Check if the computed hash (root) is equal to the provided root - (bytes32 computedHash,) = processProof(proof, leaf); + (bytes32 computedHash, ) = processProof(proof, leaf); return computedHash == root; } diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index df4617164f4..ea1878cc8b5 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -3,7 +3,7 @@ require('@openzeppelin/test-helpers'); const { MerkleTree } = require('merkletreejs'); const keccak256 = require('keccak256'); -const { expect, assert } = require('chai'); +const { expect } = require('chai'); const MerkleProofWrapper = artifacts.require('MerkleProofWrapper'); @@ -66,11 +66,14 @@ contract('MerkleProof', function (accounts) { const results = await Promise.all(elements .map(element => keccak256(element)) - .map(leaf => this.merkleProof.processProof(merkleTree.getHexProof(leaf), leaf)) + .map(leaf => this.merkleProof.processProof(merkleTree.getHexProof(leaf), leaf)), ); - assert(results.map(result => result[0]).every(value => value === root), 'processProof rebuilt invalid root'); - assert(results.map(result => result[1].toNumber()).every((value, i, values) => values.indexOf(value) === i), 'processProof rebuilt indices duplicate'); + // All reconstructed roots are correct + expect(results.map(result => result[0])).to.have.members(Array(elements.length).fill(root)); + + // Indices are unique + expect(results.map(result => result[1].toNumber().every((index, i, indices) => indices.indexOf(i) === index))); }); }); }); From 4122df92be57a23b54cb0b4a189a296600ae48c1 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 9 Sep 2021 18:01:02 +0200 Subject: [PATCH 06/11] fix test --- test/utils/cryptography/MerkleProof.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index ea1878cc8b5..92e013c55cb 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -73,7 +73,7 @@ contract('MerkleProof', function (accounts) { expect(results.map(result => result[0])).to.have.members(Array(elements.length).fill(root)); // Indices are unique - expect(results.map(result => result[1].toNumber().every((index, i, indices) => indices.indexOf(i) === index))); + expect(results.map(result => result[1].toNumber()).every((index, i, indices) => indices.indexOf(i) === index)); }); }); }); From 1b43b3758869a74afe6856c042d1cc2b87550ce6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 11 Sep 2021 00:20:32 +0200 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Francisco Giordano --- CHANGELOG.md | 2 +- contracts/utils/cryptography/MerkleProof.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ca95cd1ca6..7c1dfa92c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased - * `MerkleProof`: add a processProof function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) + * `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) ## 4.3.1 diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 9ffd1cceba5..7aedcf3dfb5 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -29,9 +29,9 @@ library MerkleProof { } /** - * @dev Returns the rebuild hash of node obtained by traversing a Merklee - * tree up from `leaf` using `proof`. If `proof` proof is valid, in the send - * that it contains the hashes of the sibling on the branch from the leaf to + * @dev Returns the rebuilt hash of nodes obtained by traversing a Merklee + * tree up from `leaf` using `proof`. If `proof` is valid, in the sense + * that it contains the hashes of the siblings on the branch from the leaf to * the root, then this will return the hash of the root of the tree. In * processing the proof we assume the pairs of leafs & pre-images are sorted. * @@ -42,7 +42,7 @@ library MerkleProof { uint256 index = 0; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; - index <<= 1; + index *= 2; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); From e0689a4585dd75847da8b6a4015327c5b94314ea Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 12 Oct 2021 14:25:17 +0200 Subject: [PATCH 08/11] improve documentation --- CHANGELOG.md | 2 +- contracts/utils/cryptography/MerkleProof.sol | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1dfa92c54..d9a3d5f94e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased - * `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) + * `MerkleProof`: add a `processProof` function that returns the rebuilt root hash, and a unique leaf indentifier, given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) ## 4.3.1 diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 7aedcf3dfb5..539243ed17f 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -29,11 +29,16 @@ library MerkleProof { } /** - * @dev Returns the rebuilt hash of nodes obtained by traversing a Merklee - * tree up from `leaf` using `proof`. If `proof` is valid, in the sense - * that it contains the hashes of the siblings on the branch from the leaf to - * the root, then this will return the hash of the root of the tree. In - * processing the proof we assume the pairs of leafs & pre-images are sorted. + * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up + * from `leaf` using `proof`, and an index uniquelly identifying the leaf + * location in the tree. A `proof` is valid if and only if the rebuilt hash + * matches the root of the tree. When processing the proof, the pairs of + * leafs & pre-images are assumed to be sorted. + * The produced index is unique in the sens that processing two valid proofs + * will return the same indices if and only if the leaf at the same location + * in the tree. This helps distinguishing two leaves that have the same + * bytes32 identifier but are present in different locations in the merkle + * tree. * * _Available since v4.4._ */ @@ -42,7 +47,7 @@ library MerkleProof { uint256 index = 0; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; - index *= 2; + index <<= 1; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); @@ -50,7 +55,6 @@ library MerkleProof { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); index |= 1; - // index |= 1 << (proof.length - i - 1); // option 2: better ? } } return (computedHash, index); From 40ae7cc53f4a8296c6f8494f465b56581ccc5162 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 13 Oct 2021 09:30:47 +0200 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Francisco Giordano --- contracts/utils/cryptography/MerkleProof.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 539243ed17f..01cf9ebdc55 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -30,12 +30,12 @@ library MerkleProof { /** * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up - * from `leaf` using `proof`, and an index uniquelly identifying the leaf + * from `leaf` using `proof`, and an index uniquely identifying the leaf * location in the tree. A `proof` is valid if and only if the rebuilt hash * matches the root of the tree. When processing the proof, the pairs of * leafs & pre-images are assumed to be sorted. - * The produced index is unique in the sens that processing two valid proofs - * will return the same indices if and only if the leaf at the same location + * The produced index is unique in the sense that processing two valid proofs + * will return the same indices if and only if the leaf is at the same location * in the tree. This helps distinguishing two leaves that have the same * bytes32 identifier but are present in different locations in the merkle * tree. From 3fe8e4f040f1f631a7aa46c2f71aac812c9a1f37 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 13 Oct 2021 17:30:40 +0200 Subject: [PATCH 10/11] remove index - see discussion in the PR --- contracts/mocks/MerkleProofWrapper.sol | 2 +- contracts/utils/cryptography/MerkleProof.sol | 23 +++++--------------- test/utils/cryptography/MerkleProof.test.js | 20 ----------------- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/contracts/mocks/MerkleProofWrapper.sol b/contracts/mocks/MerkleProofWrapper.sol index b8d036372fe..1e188df36ba 100644 --- a/contracts/mocks/MerkleProofWrapper.sol +++ b/contracts/mocks/MerkleProofWrapper.sol @@ -13,7 +13,7 @@ contract MerkleProofWrapper { return MerkleProof.verify(proof, root, leaf); } - function processProof(bytes32[] memory proof, bytes32 leaf) public pure returns (bytes32, uint256) { + function processProof(bytes32[] memory proof, bytes32 leaf) public pure returns (bytes32) { return MerkleProof.processProof(proof, leaf); } } diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 539243ed17f..caf2dbe31db 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -23,40 +23,29 @@ library MerkleProof { bytes32 root, bytes32 leaf ) internal pure returns (bool) { - // Check if the computed hash (root) is equal to the provided root - (bytes32 computedHash, ) = processProof(proof, leaf); - return computedHash == root; + return processProof(proof, leaf) == root; } /** * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up - * from `leaf` using `proof`, and an index uniquelly identifying the leaf - * location in the tree. A `proof` is valid if and only if the rebuilt hash - * matches the root of the tree. When processing the proof, the pairs of - * leafs & pre-images are assumed to be sorted. - * The produced index is unique in the sens that processing two valid proofs - * will return the same indices if and only if the leaf at the same location - * in the tree. This helps distinguishing two leaves that have the same - * bytes32 identifier but are present in different locations in the merkle - * tree. + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. * * _Available since v4.4._ */ - function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32, uint256) { + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; - uint256 index = 0; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; - index <<= 1; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); - index |= 1; } } - return (computedHash, index); + return computedHash; } } diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index 92e013c55cb..cb7be8642ac 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -56,24 +56,4 @@ contract('MerkleProof', function (accounts) { expect(await this.merkleProof.verify(badProof, root, leaf)).to.equal(false); }); }); - - describe('processProof', function () { - it('create unique indices', async function () { - const elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''); - const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true }); - - const root = merkleTree.getHexRoot(); - - const results = await Promise.all(elements - .map(element => keccak256(element)) - .map(leaf => this.merkleProof.processProof(merkleTree.getHexProof(leaf), leaf)), - ); - - // All reconstructed roots are correct - expect(results.map(result => result[0])).to.have.members(Array(elements.length).fill(root)); - - // Indices are unique - expect(results.map(result => result[1].toNumber()).every((index, i, indices) => indices.indexOf(i) === index)); - }); - }); }); From c4a049e330749e792c460b64e9c79ad2ac36fef4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 13 Oct 2021 21:31:05 +0200 Subject: [PATCH 11/11] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3470b3e5b02..9ac6332d9ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * `Governor`: shift vote start and end by one block to better match Compound's GovernorBravo and prevent voting at the Governor level if the voting snapshot is not ready. ([#2892](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2892)) * `PaymentSplitter`: now supports ERC20 assets in addition to Ether. ([#2858](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2858)) * `ECDSA`: add a variant of `toEthSignedMessageHash` for arbitrary length message hashing. ([#2865](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2865)) - * `MerkleProof`: add a `processProof` function that returns the rebuilt root hash, and a unique leaf indentifier, given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) + * `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) ## 4.3.2 (2021-09-14)