Программирование
July 23, 2022

Древо Меркла. Что это и как его используют в смарт контрактах. Объясняю понятным языком.

Итак, раз вы зашли на эту статью, то наверняка интересуетесь что же такое Древо Меркла. Думаю что каждый кто хоть раз в жизни минтил через контракт, видел в функции вайтлиста такую штуку как _merkleProof или proof. Эти переменные могут называться по-разному, но смысл обычно один и тот же. В статье мы разберем как работает эта система, построим свое двоичное древо и напишем смарт контракт с проверкой есть ли кошелек в вл на Solidity!

Да кто такой этот ваш Древо Меркла

Пример дерева

Начнем с определений. Скорее всего часть из них вы сейчас не поймете, так что возвращайтесь к ним по ходу прочтения.

◉ Вся структура данных которую мы рассматриваем называется древом.

◉ Каждый элемент в древе называется листком, а несколько элементов - листьями, по аналогии с реальными деревьями.

◉ Путь от верхнего элемента до нужного нам листка называется веткой.

◉ Все Древо Меркла - это 32 байтная строка, называемая рутом.

◉ Меркель пруф - это путь из 32 байтного массива хэшей от рута до проверяемого кошелька (его хэша).

Сами листья не могут являтся числом или массивом. Каждый лист - это хэш, чаще всего keccak256 помещаемых нами данных.

Шифр - это обратимая функция, а хэш - необратимая.

То есть, например, если мы знаем, что строка зашифрована шифром Цезаря под ключом 2 (то есть изменение вперед на 2 буквы) то мы можем ее расшифровать.

А если мы знаем, что строка захэширована, например, алгоритмом Keccak256, то, зная хэш, мы не сможем ее расхэшовать и узнать, что это была за строка (точнее можем, но только если мы будем перебирать все возможные слова, хэшировать каждое и сравнивать с искомым хэшем).

Дети древа меркла выглядят так, как вы видите сверху на картинке (сори за пейнт).

Это все понятно, но зачем его использовать, если можно все данные хранить в массиве/списке?

Вот мы и пришли к вопросу, зачем Древо Меркла используют в смарт контрактах.

◉ Древо Меркла позволяет значительно облегчить нагрузку, так как при проверке, есть ли нужный хэш в древе, нужно пройти всего лишь по одной ветке, и нет необходимости перепроверять абсолютно все ветки.

Если бы мы хранили в смарте массив из адресов, то тогда потребовалось гораздо больше газа, ведь все адреса хранились бы в блокчейне. Помимо этого, для проверки, есть ли кошелек в WL, потребовалось бы проходиться по всему массиву, что съедало бы больше газа при минте

А так получается, что в блокчейне сохраняется только одна строчка из 32 байтов - рут хэш, нужный как раз таки для проверок, есть ли кошель в вайтлисте.

Второе преимущество - это безопасность.

◉ Во первых, поскольку Древо состоит из хэшей, не получится узнать чужие кошельки в WL (т.к. по хэшу нельзя получить исходную строку).

◉ Во вторых, по рут хэшу нельзя восстановить список хэшей влов, а соответственно меркель пруф можно получить только на сайте, и никто не сможет заботить контракт.

◉ Из минусов (как же без них) можно отметить то, что не получится сминтить через контракт. Да, выше это было также в плюсах, но не у всех проектов есть сайт для минта, особенно у деген фришек.

◉ Также не получится удалить определенный кошелек (его хэш) из Древа, его придется пересобирать без удаляемых кошельков, и менять рут хэш отдельной функцией в контракте.

Кодинг

Для начала напишем простенький скрипт на Node.js для сбора Древа. Подробно останавливаться на установке я не буду, все есть в интернете, да и если вы уж решили создавать свое Древо Меркла, то, скорее всего, вы видите код не первый раз в своей жизни. Но я все же оставлю ссылку на установку Node.js.

Итак, открываем вашу IDE (если кому интересно, я использую коммерческую версию WebStorm) и устанавливаем две библиотеки, которые нам понадобятся. Первая нужна, чтобы превратить строку в keccak256, а вторая - чтобы создать древо.

$ npm i keccak256 merkletreejs

Бум! Все установилось и теперь начинаем кодить.

Для начала импортируем две библиотеки которые мы установили выше:

const { MerkleTree } = require("merkletreejs");
const keccak256 = require('keccak256');

Теперь создадим массив из кошельков которые у нас в WL:

const addresses = ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"];

Помимо этого нам потребуется небольшая стрелочная функция, которая будет конвертировать значения в нужный нам результат.

const buf2hex = x => "0x" + x.toString("hex");

Далее нам нужно получить из всех этих адресов кошельков список их хэшей, сделать это элегантно мы можем через метод map в JS, которая пройдется по каждому кошельку и вернет значение стрелочной функции в новый массив.

const leaves = addresses.map(address => keccak256(address));

Затем наконец-то создаем наше Древо:

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

И получаем нужный нам рут хэш, который мы далее сохраним в смарт контракте:

const merkleRootString = buf2hex(tree.getRoot());

Теперь реализуем получение пруфа для кошелька:

const leaf = keccak256("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4");
const proof = tree.getProof(leaf);
const proofString = "[" + proof.map(x => `"${buf2hex(x.data)}"`) + "]"

proofString это есть нужный нам пруф, который мы можем ввести в контракт и все пройдет.

Напишем простую реализацию смарт-контракта на Solidity, проверяющего есть ли кошель в WL.

Для начала инициализируем контракт и импортируем нужную нам библиотеку для работы с Древом Меркла.

pragma solidity ^0.8.4;
 
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

Создаем сам контракт, изначально задаем меркель рут, и добавляем функцию для проверки, которая вернет true если кошель в WL, и false если нет.

contract SimpleMerkleTree {
  bytes32 merkleRoot = 0xeeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097; 
  function checkIsAddressInWl(bytes32[] memory _merkleProof) public view returns(bool) {
    return MerkleProof.verify(_merkleProof, merkleRoot, keccak256(abi.encodePacked(msg.sender)));
  }  
 }

Надеюсь вам понравилась статья и вы узнали для себя что-то новое. Лучшей благодарностью будем подписочка на TG канал и телетайп.

Created with ❤️ by https://t.me/crypto_satana || https://t.me/scissor_eth