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 3 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
24 changes: 24 additions & 0 deletions contracts/mocks/MerkleMultiProofWrapper.sol
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/cryptography/MerkleProof.sol";

contract MerkleMultiProofWrapper {
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);
}
}
51 changes: 51 additions & 0 deletions contracts/utils/cryptography/MerkleProof.sol
Expand Up @@ -62,4 +62,55 @@ library MerkleProof {
value := keccak256(0x00, 0x40)
}
}

/**
* @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.
*/

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.
*/

function processMultiProof(
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
) internal pure returns (bytes32 merkleRoot) {
uint256 leafsLen = leafs.length;
uint256 totalHashes = proofFlag.length;
bytes32[] memory hashes = new bytes32[](totalHashes);
uint leafPos = 0;
uint hashPos = 0;
uint proofPos = 0;
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++]
);
}

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
}

}



33 changes: 32 additions & 1 deletion test/utils/cryptography/MerkleProof.test.js
Expand Up @@ -6,10 +6,12 @@ const keccak256 = require('keccak256');
const { expect } = require('chai');

const MerkleProofWrapper = artifacts.require('MerkleProofWrapper');
const MerkleMultiProofWrapper = artifacts.require('MerkleMultiProofWrapper');

contract('MerkleProof', function (accounts) {
beforeEach(async function () {
this.merkleProof = await MerkleProofWrapper.new();
this.merkleMultiProof = await MerkleMultiProofWrapper.new();
});

describe('verify', function () {
Expand Down Expand Up @@ -62,4 +64,33 @@ 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.merkleMultiProof.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.merkleMultiProof.multiProofVerify(root, badProofLeaves, badProof, badProofFlags)).to.equal(false);

});
});
});