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 multiProofVerify #3276

Merged
merged 24 commits into from May 25, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,7 @@
* `ERC20Wrapper`: the `decimals()` function now tries to fetch the value from the underlying token instance. If that calls revert, then the default value is used. ([#3259](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3259))
* `Governor`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by governors. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230))
* `TimelockController`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by timelocks. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230))
* `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276))

### Breaking changes

Expand Down
17 changes: 17 additions & 0 deletions contracts/mocks/MerkleProofWrapper.sol
Expand Up @@ -16,4 +16,21 @@ contract MerkleProofWrapper {
function processProof(bytes32[] memory proof, bytes32 leaf) public pure returns (bytes32) {
return MerkleProof.processProof(proof, leaf);
}

function multiProofVerify(
bytes32 root,
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
) public pure returns (bool) {
return MerkleProof.multiProofVerify(root, leafs, proofs, proofFlag);
}

function processMultiProof(
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
) public pure returns (bytes32) {
return MerkleProof.processMultiProof(leafs, proofs, proofFlag);
}
}
69 changes: 61 additions & 8 deletions contracts/utils/cryptography/MerkleProof.sol
Expand Up @@ -43,18 +43,71 @@ library MerkleProof {
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = _efficientHash(computedHash, proofElement);
} else {
// Hash(current element of the proof + current computed hash)
computedHash = _efficientHash(proofElement, computedHash);
}
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}

/**
* @dev Returns true if a `leafs` can be proved to be a part of a Merkle tree
* defined by `root`. For this, `proofs` for each leaf must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Then
* 'proofFlag' designates the nodes needed for the multi proof.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32 root,
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
) internal pure returns (bool) {
return processMultiProof(leafs, proofs, proofFlag) == root;
}

/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using the multi proof as `proofFlag`. A multi proof is
* valid if the final hash matches the root of the tree.
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leafs` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leafsLen = leafs.length;
uint256 totalHashes = proofFlag.length;
bytes32[] memory hashes = new bytes32[](totalHashes);
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proofs` array.
for (uint256 i = 0; i < totalHashes; i++) {
hashes[i] = _hashPair(
leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++],
proofFlag[i] ? leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++] : proofs[proofPos++]
);
}

Amxx marked this conversation as resolved.
Show resolved Hide resolved
return hashes[totalHashes - 1];
}

function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
colinh80 marked this conversation as resolved.
Show resolved Hide resolved
}

function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
Expand Down
6 changes: 5 additions & 1 deletion docs/modules/ROOT/pages/utilities.adoc
Expand Up @@ -26,7 +26,11 @@ WARNING: Getting signature verification right is not trivial: make sure you full

=== Verifying Merkle Proofs

xref:api:cryptography.adoc#MerkleProof[`MerkleProof`] provides xref:api:cryptography.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[`verify`], which can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree].
xref:api:cryptography.adoc#MerkleProof[`MerkleProof`] provides:

* xref:api:cryptography.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree].

* xref:api:cryptography.adoc#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---[`multiProofVerify`] - can prove multiple values are part of a Merkle tree.

[[introspection]]
== Introspection
Expand Down
27 changes: 27 additions & 0 deletions test/utils/cryptography/MerkleProof.test.js
Expand Up @@ -62,4 +62,31 @@ contract('MerkleProof', function (accounts) {
expect(await this.merkleProof.verify(badProof, root, leaf)).to.equal(false);
});
});

describe('multiProofVerify', function () {
it('returns true for a valid Merkle multi proof', async function () {
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare);
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();
const proofLeaves = ['b', 'f', 'd'].map(keccak256).sort(Buffer.compare);
const proof = merkleTree.getMultiProof(proofLeaves);
const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);

expect(await this.merkleProof.multiProofVerify(root, proofLeaves, proof, proofFlags)).to.equal(true);
});

it('returns false for an invalid Merkle multi proof', async function () {
const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare);
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();
const badProofLeaves = ['g', 'h', 'i'].map(keccak256).sort(Buffer.compare);
const badMerkleTree = new MerkleTree(badProofLeaves);
const badProof = badMerkleTree.getMultiProof(badProofLeaves);
const badProofFlags = badMerkleTree.getProofFlags(badProofLeaves, badProof);

expect(await this.merkleProof.multiProofVerify(root, badProofLeaves, badProof, badProofFlags)).to.equal(false);
});
});
});