Skip to content

Commit b70f8b2

Browse files
authored
Merge pull request #77 from kleros/feat/merkle-library
fix: sha256 leaves to avoid 2nd pre-image attack
2 parents 8e737d3 + 04e4f30 commit b70f8b2

File tree

4 files changed

+16
-14
lines changed

4 files changed

+16
-14
lines changed

contracts/src/bridge/merkle/MerkleProof.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ contract MerkleProof {
3939
bytes memory data,
4040
bytes32 merkleRoot
4141
) public pure returns (bool) {
42-
return validateProof(proof, keccak256(data), merkleRoot);
42+
return validateProof(proof, sha256(data), merkleRoot);
4343
}
4444

4545
/** @dev Calculates merkle root from proof.

contracts/src/bridge/merkle/MerkleTreeHistory.sol

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,32 +40,34 @@ contract MerkleTreeHistory {
4040
* @param data The data to insert in the merkle tree.
4141
*/
4242
function append(bytes memory data) public {
43-
bytes32 leaf = keccak256(data);
44-
count += 1;
45-
uint256 size = count;
43+
// Differentiate leaves from interior nodes with different
44+
// hash functions to prevent 2nd order pre-image attack.
45+
// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/
46+
bytes32 leaf = sha256(data);
47+
uint256 size = count + 1;
48+
count = size;
4649
uint256 hashBitField = (size ^ (size - 1)) & size;
47-
48-
for (uint256 height = 0; height < 64; height++) {
49-
if ((hashBitField & 1) == 1) {
50-
branch[height] = leaf;
51-
return;
52-
}
50+
uint256 height;
51+
while ((hashBitField & 1) == 0) {
5352
bytes32 node = branch[height];
54-
// effecient hash
5553
if (node > leaf)
5654
assembly {
55+
// effecient hash
5756
mstore(0x00, leaf)
5857
mstore(0x20, node)
5958
leaf := keccak256(0x00, 0x40)
6059
}
6160
else
6261
assembly {
62+
// effecient hash
6363
mstore(0x00, node)
6464
mstore(0x20, leaf)
6565
leaf := keccak256(0x00, 0x40)
6666
}
6767
hashBitField /= 2;
68+
height = height + 1;
6869
}
70+
branch[height] = leaf;
6971
}
7072

7173
/** @dev Saves the merkle root state in history and resets.
@@ -98,8 +100,8 @@ contract MerkleTreeHistory {
98100
uint256 height = 0;
99101
bool isFirstHash = true;
100102
while (size > 0) {
101-
// avoid redundant calculation
102103
if ((size & 1) == 1) {
104+
// avoid redundant calculation
103105
if (isFirstHash) {
104106
node = branch[height];
105107
isFirstHash = false;

contracts/test/bridge/merkle/MerkleTree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class MerkleTree {
3131
* @return node The `sha3` (A.K.A. `keccak256`) hash of `first, ...params` as a 32-byte hex string.
3232
*/
3333
public static makeLeafNode(data: string): string {
34-
const result = ethers.utils.keccak256(data);
34+
const result = ethers.utils.sha256(data);
3535

3636
if (!result) {
3737
throw new Error("Leaf node must not be empty");

contracts/test/bridge/merkle/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe("Merkle", function () {
6161
});
6262
it("Should correctly verify all nodes in the tree", async () => {
6363
for (var message of data) {
64-
const leaf = ethers.utils.keccak256(message);
64+
const leaf = ethers.utils.sha256(message);
6565
proof = mt.getHexProof(leaf);
6666
const validation = await merkleProof.validateProof(proof, message,rootOnChain);
6767
expect(validation).equal(true);

0 commit comments

Comments
 (0)