コラム

MerkleProofによるAllowList実装について

今回は、Merkle Proofについて、実際に操作しながら説明していきたいと思います。

私の勉強も兼ねて書いた記事ですので、間違い等あれば色々教えてくださいね

Merkle Proofの概要

Merkle Proofは、仕様によって実装の仕方や注意点が異なります。

今回は、OpenZeppelinのMerkle Proof (v4.7.0) を対象とします。

このMerkle Proofをサクッと説明すると以下の通りです。

  • 二つのハッシュ値を比較します。
  • 値が大きい方を0番目(0x00)に、値が小さい方を16番目(0x40)に格納します。
  • 連結した値(0番目から32番目を対象)をハッシュ化します。
  • ①〜③を繰り返して、MerkleRootを演算します。
  • 事前に保存しておいたMerkleRootと比較して、正しいかどうかを判断するというものです。

NFTプロジェクトでは、見慣れないコードが混じってるかと思います。

詳細が気になった方は、ぜひopcodeassemblyで検索してみて下さいね。

ガス最適化には欠かせないプログラミングになります😉

Merkle Proofを支えているハッシュ関数について

ハッシュ関数は、与えた値(りんご)からハッシュ値(1357)を計算する関数です。

ハッシュ値(1357)から与えた値(りんご)を逆算することは非常に難しいとされています。

  • りんご →  1357
  • 1357  →  りんご:これは非常に困難

また異なる値(りんご、みかん)から同じハッシュ値(1357、1357)になることはありえます。

しかし、(1357と)同じハッシュ値になる値(みかん)を算出することも非常に困難であるとされています。

  • りんご → 1357 みかん → 1357 :これはありうる。
  • りんごや1357を使って「みかん」を導出することは非常に困難

Merkle Proofは、時間をかければ突破することが可能と考えた方が良いです。

Merkle ProofによるAllowListの実装について

では、本題に行きたいと思います。

今回はNFTプロジェクトのAllowlistを題材に説明していきたいと思います。

チェックするデータとしては、アドレスとそのアドレスに許可された枚数になります。

※multi Merkle Proofの説明ではありません。

なお、Merkle Proofが分かりやすいように、体験できるページを作りました。

これを操作しながら読み進めてもらえると分かりやすいかと思います。

https://eggdragons.com/dapps/merkleproof

さらっと作ったので、ちょっとだけ挙動がおかしいときがあります。

今回作成するMerkleTree全体は、こちらになります。

Leaves:AllowList

検証したいデータ(Data Block)からMerkleTreeを作っていきます。

今回のケースでは、アドレスと枚数をData Blockに格納していきます。

今回のテストサイトでは、最低3つ以上のデータブロックを入力してください。

ランダムボタンを押すと、自動的にランダムで生成されたアドレスと枚数が入力されます。

準備ができたら、『Set Allowlist』ボタンを押してください。

個々のDataBlockの値をハッシュ化して、Leafを作成します。LeafをひとまとめにしたものがLeavesになります。

参考ですが、ここで使用しているコードはこちらになります。(TypeScriptになります)

import { ethers } from "ethers";

const leaf = ethers.utils.solidityKeccak256(["address", "uint256"],[address, quantity]));

Tree:from leaves

Leaves(DataBlock)の情報を元に、Treeが出来上がります。

ここで使用しているコードはこちらになります。

import { MerkleTree } from "merkletreejs";
import keccak256 from "keccak256";

const tree = new MerkleTree(leaves, keccak256, { sort: true });

OpenZeppelinのMerkle Proofを使う場合は、必ずsortが必要になります。

Proofs:from tree and leaves

出来上がったTreeと個々のLeafを使って、proofを作っていきます。

const proof = tree.getHexProof(leaf)

Root:Save to Contract(from leaves)

MerkleTreeのtop hashがrootになります。

このrootをブロックチェーン上に保存します。

const root = tree.getHexRoot();

Argument:Send from dapps to contract

ここでは、実際にdappsからブロックチェーンにおくる変数を入力して、Merkle Proofをテストすることができます。

Argumentは、Merkle Proofに必要な変数を入力する場所になっています。

Setボタンを押すことで、最初に入力したDataBlockに紐づいたデータが自動で入力されます。

Check At here

先ほど入力したArgumentの情報に基づき、MerkleProofして結果を表示させています。

例えば、SetAをクリックすると、Check:At hereがOKになると思います。

その状態で、例えば、quantityを変化させると、Check:At hereがNGになるかと思います。

タイトルがおかしいのは、ご愛嬌ということで・・・

Check At Contract

ここでは、実際にブロックチェーンを通してテストすることができます。

Check Contractを押すと、ここで作ったrootとArgumentの値がコントラクトに送られます。

その後、OpenzepplinのMerkle Proofで検証された結果が返ってきます。

read contractになっているのでガス代はかかりません

Jump to etherscan

Jump to etherscanのボタンを押してもらうと、実際に使用しているコントラクトをEtherScanで見ることが出来ます。

またNFTプロジェクトに採用する際のサンプル記載してます。

プロジェクトに合わせて必ず書き換えるとともに、必ずテストしてくださいね

※全てpublic functionになっているので、要注意です。もし必要であれば自由にrootを変換してみてくださいね!

※ERC721Aでは、mint function内部にReentrancy guardに変わるものが含まれています。

注意事項??

このMerkleTreeでは、必ず対称にする必要があります。

すなわちLeavesは、必ず2の乗数にする必要があります。

※矛盾することを書きますが、非対称のMerkleTreeはあります。というか、ブロックチェーンは非対称だったはず・・・

DataBlockの情報を3つだけ入れてみてください。

DataBlockの情報を3つしか入れてない場合、一部データが欠落していると思います。

hash1-1が無いので、hash1-0とhash1とが一致することになります。当然、proofデータの一部が欠損します。

これは、冒頭にちょろっと述べていたMerkleTreeの算出方法が関係しています。

実装する際は、必ずTreeが対称になるように注意してくださいね!

説明するのは、やっぱり難しい。難しいと感じるということは、理解が足りていない証拠だなぁって改めて思いました。勉強しよう!

合わせてチェックしてね!

-コラム