MWAN MOBILE

×
mwan_logo
blog-banner

Using Merkle Trees for Smart Contracts

Miscellaneous 19-Oct-2022

A secure and easy way to implement an NFT whitelist.

Merkle proofs are often used in NFT contracts that feature some sort of whitelist. I am not a huge fan of it since it requires off-chain code as well. But even with the off-chain code, it is a very good way to prevent bots or non-whitelisted addresses from minting tokens.

So what are Merkle proofs, Merkle trees, what does the off-chain code do, and what does the on-chain code do? Let’s check that out in this article.

What is a Merkle tree?

Merkle trees, like the name suggests, are a tree-like structure in which every node is labeled with a hash of some data. The bottom-most nodes are called leaves.

There are as many leaves as the number of data that need to be hashed. The nodes between the leaves and the root are determined by the hash of the concatenated hashes of the nodes below it. It sounds like a lot, but let’s look at the image below.

A Merkle tree. The image is taken from here.

Now that we roughly know what a Merkle tree is we can get on to implementing it.

Serverside code

Our off-chain program will simply hash a few fake Ethereum addresses in such a way that we can check their validity in the smart contract we’re about to write. I will be writing a simple NodeJS program that will log the relevant hashes and I’ll copy-paste it into the smart contract’s function call using Remix.

Let’s get started by initializing the project and installing the right packages we need for the Merkle tree implementation.

npm init -y
npm i merkletreejs
npm i keccak256

With this, we can easily create a Merkle tree. I’ve made a small list of 7 Ethereum addresses that I picked randomly. Then with keccak256 , I make a new list called leaves that has the keccak256 hash of each address.

// 'Serverside' code
let address = addresses[0]
let hashedAddress = keccak256(address)
let proof = merkleTree.getHexProof(hashedAddress)
console.log(proof)

With that list, we can create a new MerkleTree with the leaves and keccak256 function as parameters.

With this, we now have a printer Merkle tree and its root hash in our console when you run node index.js .

Merkle Proof

Now that we have our root hash, our tree, and the leaves, we can verify if a given hash is part of the tree, and therefore valid. In a real-world application, you’d get the user’s address from the front-end. We simply get one from our list.

Next, we hash the address with keccak256 and retrieve the proof using getHexProof() . This proof is then sent alongside the transaction to the smart contract.

// 'Serverside' code
let address = addresses[0]
let hashedAddress = keccak256(address)
let proof = merkleTree.getHexProof(hashedAddress)
console.log(proof)

We can also verify it within the backend already if we really want to. It isn’t useful for the whitelist application, but it’s good to know how it works anyway since we’ll do something similar in the smart contract.

// Check proof
let v = merkleTree.verify(proof, hashedAddress, rootHash)
console.log(v) // returns true

Now let’s boot up Remix and create a smart contract that does this for us.

Smart Contract

The smart contract will simply do the same as the final code snippet in the last paragraph. It will verify the proof with the leaf and the root hash using a simple function from another library.


// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract MerkleTest {
    // Our rootHash
    bytes32 public root = 0x11d251a3c7c541a8a68635af1ed366692175fbdc9f3b07da18af66c111f85800; 

    function checkValidity(bytes32[] calldata _merkleProof) public view returns (bool){
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_merkleProof, root, leaf), "Incorrect proof");
        return true; // Or you can mint tokens here
    }

}

We use OpenZeppelin’s contract to help with verifying the Merkle proof. In a production environment, be sure to also have a function for setting the root, and disabling the check for a valid proof altogether if your backend somehow fails.

Conclusion

That’s essentially it. We’ve talked about how to create a Merkle tree, how to create a proof for a leaf, and how to verify the proof for its validity. With this, you can create a whitelist smart contract by simply using the public addresses instead of a random list like I did.

Source: Medium