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 19 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 @@ -10,6 +10,7 @@
* `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338))
* `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416))
* `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245))
* `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276))

## 4.6.0 (2022-04-26)

Expand Down
23 changes: 19 additions & 4 deletions contracts/utils/cryptography/MerkleProof.sol
Expand Up @@ -77,17 +77,32 @@ library MerkleProof {
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 proofsLen = proofs.length;
uint256 totalHashes = proofFlag.length;

// Check proof validity.
require(leafsLen + proofsLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

// 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".
bytes32[] memory hashes = new bytes32[](totalHashes);
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(
proofFlag[i] ? leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++] : proofs[proofPos++],
leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++]
);
bytes32 a = leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlag[i] ? leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++] : proofs[proofPos++];
hashes[i] = _hashPair(a, b);
}

Amxx marked this conversation as resolved.
Show resolved Hide resolved
return hashes[totalHashes - 1];
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
39 changes: 39 additions & 0 deletions test/utils/cryptography/MerkleProof.test.js
@@ -1,5 +1,6 @@
require('@openzeppelin/test-helpers');

const { expectRevert } = require('@openzeppelin/test-helpers');
const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');

Expand Down Expand Up @@ -88,5 +89,43 @@ contract('MerkleProof', function (accounts) {

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

it('revert with invalid multi proof #1', async function () {
const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
const badLeave = keccak256('e');
Amxx marked this conversation as resolved.
Show resolved Hide resolved
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();

await expectRevert(
this.merkleProof.multiProofVerify(
root,
[ leaves[0], badLeave ], // A, E
[ leaves[1], fill, merkleTree.layers[1][1] ],
[ false, false, false ],
),
'MerkleProof: invalid multiproof',
);
});

it('revert with invalid multi proof #2', async function () {
const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
const badLeave = keccak256('e');
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();

await expectRevert(
this.merkleProof.multiProofVerify(
root,
[ badLeave, leaves[0] ], // A, E
[ leaves[1], fill, merkleTree.layers[1][1] ],
[ false, false, false, false ],
),
'reverted with panic code 0x32',
);
});
});
});