Skip to content

Commit 2894059

Browse files
authoredJun 27, 2022
Support memory arrays in MerkleTree multiproof (#3493)
1 parent 7473872 commit 2894059

File tree

3 files changed

+109
-8
lines changed

3 files changed

+109
-8
lines changed
 

‎contracts/mocks/MerkleProofWrapper.sol

+21-4
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,36 @@ contract MerkleProofWrapper {
3030
}
3131

3232
function multiProofVerify(
33+
bytes32[] memory proofs,
34+
bool[] memory proofFlag,
35+
bytes32 root,
36+
bytes32[] memory leaves
37+
) public pure returns (bool) {
38+
return MerkleProof.multiProofVerify(proofs, proofFlag, root, leaves);
39+
}
40+
41+
function multiProofVerifyCalldata(
3342
bytes32[] calldata proofs,
3443
bool[] calldata proofFlag,
3544
bytes32 root,
36-
bytes32[] calldata leaves
45+
bytes32[] memory leaves
3746
) public pure returns (bool) {
38-
return MerkleProof.multiProofVerify(proofs, proofFlag, root, leaves);
47+
return MerkleProof.multiProofVerifyCalldata(proofs, proofFlag, root, leaves);
3948
}
4049

4150
function processMultiProof(
51+
bytes32[] memory proofs,
52+
bool[] memory proofFlag,
53+
bytes32[] memory leaves
54+
) public pure returns (bytes32) {
55+
return MerkleProof.processMultiProof(proofs, proofFlag, leaves);
56+
}
57+
58+
function processMultiProofCalldata(
4259
bytes32[] calldata proofs,
4360
bool[] calldata proofFlag,
44-
bytes32[] calldata leaves
61+
bytes32[] memory leaves
4562
) public pure returns (bytes32) {
46-
return MerkleProof.processMultiProof(proofs, proofFlag, leaves);
63+
return MerkleProof.processMultiProofCalldata(proofs, proofFlag, leaves);
4764
}
4865
}

‎contracts/utils/cryptography/MerkleProof.sol

+63-3
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,26 @@ library MerkleProof {
8181
* _Available since v4.7._
8282
*/
8383
function multiProofVerify(
84+
bytes32[] memory proof,
85+
bool[] memory proofFlags,
86+
bytes32 root,
87+
bytes32[] memory leaves
88+
) internal pure returns (bool) {
89+
return processMultiProof(proof, proofFlags, leaves) == root;
90+
}
91+
92+
/**
93+
* @dev Calldata version of {multiProofVerify}
94+
*
95+
* _Available since v4.7._
96+
*/
97+
function multiProofVerifyCalldata(
8498
bytes32[] calldata proof,
8599
bool[] calldata proofFlags,
86100
bytes32 root,
87-
bytes32[] calldata leaves
101+
bytes32[] memory leaves
88102
) internal pure returns (bool) {
89-
return processMultiProof(proof, proofFlags, leaves) == root;
103+
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
90104
}
91105

92106
/**
@@ -97,9 +111,55 @@ library MerkleProof {
97111
* _Available since v4.7._
98112
*/
99113
function processMultiProof(
114+
bytes32[] memory proof,
115+
bool[] memory proofFlags,
116+
bytes32[] memory leaves
117+
) internal pure returns (bytes32 merkleRoot) {
118+
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
119+
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
120+
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
121+
// the merkle tree.
122+
uint256 leavesLen = leaves.length;
123+
uint256 totalHashes = proofFlags.length;
124+
125+
// Check proof validity.
126+
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
127+
128+
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
129+
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
130+
bytes32[] memory hashes = new bytes32[](totalHashes);
131+
uint256 leafPos = 0;
132+
uint256 hashPos = 0;
133+
uint256 proofPos = 0;
134+
// At each step, we compute the next hash using two values:
135+
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
136+
// get the next hash.
137+
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
138+
// `proof` array.
139+
for (uint256 i = 0; i < totalHashes; i++) {
140+
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
141+
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
142+
hashes[i] = _hashPair(a, b);
143+
}
144+
145+
if (totalHashes > 0) {
146+
return hashes[totalHashes - 1];
147+
} else if (leavesLen > 0) {
148+
return leaves[0];
149+
} else {
150+
return proof[0];
151+
}
152+
}
153+
154+
/**
155+
* @dev Calldata version of {processMultiProof}
156+
*
157+
* _Available since v4.7._
158+
*/
159+
function processMultiProofCalldata(
100160
bytes32[] calldata proof,
101161
bool[] calldata proofFlags,
102-
bytes32[] calldata leaves
162+
bytes32[] memory leaves
103163
) internal pure returns (bytes32 merkleRoot) {
104164
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
105165
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the

‎test/utils/cryptography/MerkleProof.test.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ contract('MerkleProof', function (accounts) {
7979
const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);
8080

8181
expect(await this.merkleProof.multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true);
82+
expect(await this.merkleProof.multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true);
8283
});
8384

8485
it('returns false for an invalid Merkle multi proof', async function () {
@@ -91,7 +92,10 @@ contract('MerkleProof', function (accounts) {
9192
const badProof = badMerkleTree.getMultiProof(badProofLeaves);
9293
const badProofFlags = badMerkleTree.getProofFlags(badProofLeaves, badProof);
9394

94-
expect(await this.merkleProof.multiProofVerify(badProof, badProofFlags, root, badProofLeaves)).to.equal(false);
95+
expect(await this.merkleProof.multiProofVerify(badProof, badProofFlags, root, badProofLeaves))
96+
.to.equal(false);
97+
expect(await this.merkleProof.multiProofVerifyCalldata(badProof, badProofFlags, root, badProofLeaves))
98+
.to.equal(false);
9599
});
96100

97101
it('revert with invalid multi proof #1', async function () {
@@ -111,6 +115,15 @@ contract('MerkleProof', function (accounts) {
111115
),
112116
'MerkleProof: invalid multiproof',
113117
);
118+
await expectRevert(
119+
this.merkleProof.multiProofVerifyCalldata(
120+
[ leaves[1], fill, merkleTree.layers[1][1] ],
121+
[ false, false, false ],
122+
root,
123+
[ leaves[0], badLeaf ], // A, E
124+
),
125+
'MerkleProof: invalid multiproof',
126+
);
114127
});
115128

116129
it('revert with invalid multi proof #2', async function () {
@@ -130,6 +143,15 @@ contract('MerkleProof', function (accounts) {
130143
),
131144
'reverted with panic code 0x32',
132145
);
146+
await expectRevert(
147+
this.merkleProof.multiProofVerifyCalldata(
148+
[ leaves[1], fill, merkleTree.layers[1][1] ],
149+
[ false, false, false, false ],
150+
root,
151+
[ badLeaf, leaves[0] ], // A, E
152+
),
153+
'reverted with panic code 0x32',
154+
);
133155
});
134156

135157
it('limit case: works for tree containing a single leaf', async function () {
@@ -142,6 +164,7 @@ contract('MerkleProof', function (accounts) {
142164
const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);
143165

144166
expect(await this.merkleProof.multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true);
167+
expect(await this.merkleProof.multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true);
145168
});
146169

147170
it('limit case: can prove empty leaves', async function () {
@@ -150,6 +173,7 @@ contract('MerkleProof', function (accounts) {
150173

151174
const root = merkleTree.getRoot();
152175
expect(await this.merkleProof.multiProofVerify([ root ], [], root, [])).to.equal(true);
176+
expect(await this.merkleProof.multiProofVerifyCalldata([ root ], [], root, [])).to.equal(true);
153177
});
154178
});
155179
});

0 commit comments

Comments
 (0)
Please sign in to comment.