見出し画像

MerkleTreeを利用して、ホワイトリスト付きNFTコントラクトのガス代を大幅に圧縮する

昨今のNFTプロジェクトを運営する中で、ホワイトリスト機能はほとんど必須となっています。ホワイトリストとは指定されたwalletのみ購入できる機能で、これをもとにセール期間まで盛り上げていきます。

ですが、イーサリアムチェーンでデプロイする場合、ホワイトリストをコントラクトにいれるためには多額のガス代が必要になってきます。たとえば、Love Addicted Girls では、ホワイトリストを入れるために2ETH (1ETH x 2回) のガス代がかかっています。

昨今、ホワイトリストのガス代を削減するため、MerkleTreeという仕組みをコントラクトに導入することが一般的です。MerkleTreeはOpenZeppelin 公式にも機能として導入されているため、正しく実装すれば安全な技術です。

MerkleTreeとは

マークルツリーの構造

マークルツリーは名前のように木のような構造であり、ツリー上のすべてのノードは、暗号化ハッシュ関数の結果である値で表されます。ハッシュ関数は一方向です。つまり、入力から出力を生成するのは簡単ですが、出力から入力を決定することは(計算上)不可能です。(暗号学の本に面白いものもありますよ)マークルツリーには3種類のノードがあり、次のとおりです。

  1. LeafNode—このノードはツリーの最下部にあり、その値は、指定されたハッシュ関数に従って元のデータがハッシュされた結果です。ツリーには、元のデータと同じ数のリーフノードがあります。たとえば、7つのデータをハッシュする必要がある場合、7つのLeafNodeがあります。

  2. ParentNode—ParentNodeは、ツリー全体のサイズに応じてツリーのさまざまなレベルに配置されますが、常にリーフノードの上に配置されます。ParentNodeは、最小で1つのノード、最大で2つのノードのみを形成します。ParentNodeの値は、通常は左から右に開始して、その下のノードの連結ハッシュのハッシュによって決定されます。入力内容が異なれば常に異なるハッシュが生成されるため、ハッシュが連結される順序は重要です。ParentNodeは、ツリーのサイズに応じて他のParentNodeを形成できることに価値があります。

  3. RootNode—RootNodeはツリーの最上部にあり、その下にある2つのParentNodeの連結されたハッシュのハッシュから派生します。これも左から右に始まります。マークルツリーにはRootNodeが1つしかなく、RootNodeはRootHashを持っています。

この説明だけではわかりづらいと思うので、上記の図を見て覚えてください。

マークルツリーを使うと何がいいのか

MerkleTreeを使うと、予め獲得したホワイトリストをもとにして計算することにより、非常に短い形で安全にホワイトリストの情報をコントラクトに保存することが可能です。通常、MerkleTreeはRootHashと呼ばれる32バイトの情報のみコントラクトに保存することで、数千個のホワイトリストの検証を済ませることが可能です。これにより、(LAGの例であった)2ETHのホワイトリスト格納ガス代から、わずか数ドルまでガス代を減らすことが可能です!

実装方法

今回、私が作っているNFTテンプレート「NFTBoil」には、マークルツリーを利用したプログラムを載せているため、そちらのプログラムを利用してテストすることが可能になっています。

Minting バックエンドの生成

最初に実行したいのは、LeafNodeを計算することです。NFTBoilの例をもとに確認してください。各LeafNodeは何らかの形式のハッシュデータである必要があるため、この例では、keccak256ライブラリを使用してホワイトリストのすべてのアドレスをハッシュします(図2)。後でSolidityスマートコントラクトで使用されるため、大体keccak256のハッシュアルゴリズムを使用します。

frontend/functions/merkletree.ts

ホワイトリストのすべてのアドレスをハッシュしてLeafNodeを取得したら、MerkleTreeオブジェクトを作成してください。これを行うには、merkletreejsライブラリを使用し、new MerkleTree()関数を呼び出して、LeafNodeを最初の引数として渡し、ハッシュアルゴリズムを2番目の引数として渡し、{ sortPairs: true }オプションを最後の引数として渡します。

その後、getRoot()を利用することにより、RootHashを取得できます。このRootHashの情報は、後程スマートコントラクトにRootHashを入れるときに使うので、メモしておいてください。getHexRoot() のデータを利用します。

MerkleTreeがなぜ元の情報を使わずにホワイトリストの検証をしているのか、こちらの動画が詳しいです。英語ですが興味ある方はご確認ください。

ウェブサイトの実装

通常のmintサイトだとコントラクトと静的ページだけでmintingサイトを作成することが可能ですが、MerkleTreeを利用した場合、それら+でMerkleTreeを計算するバックエンドプログラムを入れて、Webサイトから呼び出す実装にしないといけません。

今回、NFTBoilではWebサイトからMerkleTreeを呼び出すコードもあわせて記述されています。また、netlifyを利用することにより、開発者は意識せずにnetlify functionから検証プログラムを呼び出すことが可能です。

Webサイトから検証プログラムをAPIで呼び出す例

コントラクトへのMerkleTreeの実装

さて、このWebプログラムにて実際のMerkleTreeの検証を行いますが、この検証だけでは、直コンによりいともたやすくNFTを抜かれてしまいます。そのため、MerkleRootをコントラクトに入れることにより、コントラクトでも検証を行いましょう。

最初に、この検証を行うために、OpenZeppelin MerkleProof.sol contract のimportを行います。(OpenZeppelinにすでに用意されているのは安心ですね)MerkleProof.verify()がこれでcontractの中でも使えるようになります。
MerkleRootを格納する変数を用意し、そこに検証したMerkleRootを保存できるようにします。

この図はMerkleRootの例であり、実稼働用コントラクトではありません

また、ホワイトリストセールのmint関数にて、MerkleTreeの検証用コードを実装します。まずは、 msg.sender のアドレスをもとに、 keccak256を利用して leafNode を作成します。通常、byte32 の配列で leafNodeを読み込みますが、それを encode する必要があります。

そしてコントラクト内で生成したleafNodeをもとに、MerkleProof.verify を利用して検証を行います。この msg.sender からの一連の検証は、悪意をもったユーザーが変更できないよう完全に一意であることを確認してください。(アドレスを外部から受け付ける仕組みにすると、いともたやすく貫通してしまいます)

最後に

ここまで読んでくれてありがとうございます!
これから日本のNFTプロジェクトがますます発展することを祈っています。私のTwitterをフォローしてください。 (@HayattiQ) ジェネレーティブNFT運営のための相談があればTwitterのDMまで!

この記事が気に入ったらサポートをしてみませんか?