
Серия: Zcash from First Principles . Статья 4 . Деревья Меркла Для кого: новички. Мы опираемся на Статью 3 (хеширование и commitments). Если вы понимаете, что такое отпечаток и commitment, вы готовы. Что вы получите: интуитивно понятное и корректное представление о деревьях Меркла, о том, как доказать принадлежность, не раскрывая, какой именно элемент вы имеете в виду, и как именно это становится деревом note commitments в Zcash.
Статья 0 описывала «публичную доску», на которой хранится каждая когда-либо созданная note и которая только растёт. Теперь вы, вероятно, уже догадываетесь, что на неё прикрепляется: commitments (Статья 3), запечатанные конверты. Но настоящая доска содержала бы сотни миллионов таких элементов. Как сеть это хранит, проверяет и позволяет вам доказать, что ваш конверт есть на доске, не указывая на него? Ответом служит одна из самых элегантных структур в информатике: дерево Меркла.
Как только у вас появляется гигантский публичный список commitments, сразу возникают две проблемы.
Проблема первая: целостность в большом масштабе. Если в списке 300 миллионов записей, как кому-либо убедиться, что ни одна из них не была тайно изменена? Проверять заново 300 миллионов элементов при каждом взгляде — безнадёжная затея.
Проблема вторая: приватное доказательство принадлежности. Чтобы потратить note (Статья 0), вы должны доказать, что ваш commitment действительно находится на доске. Но если вы укажете на него («это запись номер 4,201,337!»), вы тут же деанонимизируете себя. Вам нужно доказать: «мой конверт находится где-то на этой доске» — не раскрывая, какой именно.
Дерево Меркла решает обе задачи сразу. Оно сжимает весь список в один-единственный отпечаток и позволяет доказать принадлежность с помощью маленького доказательства, скрывающего позицию.
Представьте сетку турнира на выбывание, только вместо игроков дальше проходят отпечатки.

Самое важное свойство напрямую вытекает из лавинного эффекта (Статья 3):
Корень — это отпечаток всего, что находится под ним. Измените любой лист, хотя бы на один бит, — и его отпечаток изменится, затем изменится его родитель, затем тот родитель, и так до самого верха. Корень изменится. Значит, одно небольшое значение корня удостоверяет целостность всего списка. Это решает первую проблему.
Давайте построим показанное выше дерево с четырьмя листьями, используя реальные SHA-256-отпечатки для листьев A, B, C, D (дайджесты сокращены для удобства чтения):
hA = 559aead08264... hB = df7e70e50215...
hC = 6b23c0d5f35d... hD = 3f39d5c348e5...
hAB = H(hA , hB) = 63956f0ce48e...
hCD = H(hC , hD) = 98a2fbfddbc7...
ROOT = H(hAB , hCD) = 1b3faa3fcc5e...
Здесь всё сводится к одному: «захешировать объект, затем захешировать пары хешей». Ничего более экзотического, чем в Статье 3, просто организованного в виде дерева.
Теперь ко второй проблеме. Допустим, вы хотите доказать, что лист C находится в дереве, человеку, который знает только корень. Вы не передаёте ему всё дерево. Вы передаёте только отпечатки, нужные для подъёма от C к корню; это называется путём аутентификации (или доказательством Меркла):
Чтобы доказать, что
Cнаходится в дереве, предоставьте:
- его соседний элемент
hD, и- его «дядю»
hAB.
Проверяющий, зная только корень, пересчитывает подъём:
step 1: H(hC , hD) = hCD (объединяем C с его соседним элементом)
step 2: H(hAB , hCD) = ROOT? (объединяем с «дядей»)
Если посчитать это на практике, получится 1b3faa3fcc5e..., что совпадает с корнем. Лист доказанно находится в дереве.

Здесь есть две причины, почему это так мощно:
n листьев нужно передать всего около log_2(n) хешей. Для миллиарда листьев это примерно 30 хешей, а не миллиард. Доказательство почти не растёт, даже когда само дерево разрастается до огромных размеров.Теперь мы можем точно сформулировать, чем на самом деле является «публичная доска» из Статьи 0:
Дерево note commitments — это дерево Меркла, у которого листьями являются note commitments. Каждый раз, когда где-либо в мире создаётся note, её commitment добавляется как следующий лист, а корень обновляется.
Несколько конкретных деталей:
2^(32) (более четырёх миллиардов) notes.
Дерево доказывает, что note существует. Но само по себе оно не мешает вам потратить одну и ту же note дважды. За это отвечает множество nullifier из Статьи 0: отдельная коллекция «токенов аннулирования». Когда вы тратите note, вы публикуете её nullifier, и сеть отклоняет любой nullifier, который уже встречала раньше.
Таким образом, эти две публичные структуры выполняют взаимодополняющие роли, и именно их раздельность разрывает связь между рождением note и её смертью:
| Структура | На какой вопрос отвечает | Когда обновляется |
|---|---|---|
| Дерево note commitments | «Существует ли эта note?» | Note создаётся (commitment добавляется) |
| Множество nullifier | «Была ли эта note уже потрачена?» | Note тратится (nullifier публикуется) |
Как обычно, здесь есть упрощения. Настоящие инкрементальные деревья Меркла отслеживают «пограничные» узлы, чтобы обновлять корень без полной перестройки; сеть хранит окно недавних anchor, а не только самый последний, чтобы новые блоки не ломали кошельки; а пустые листья используют заранее определённое значение-заполнитель. Мы также рисовали аккуратные бинарные деревья с красивыми степенями двойки. Ничто из этого не меняет интуицию: листья из commitments, попарно хешируемые до одного корня, и короткие доказательства принадлежности. К точной бухгалтерии мы вернёмся в статье о протоколе.
| Термин | Значение простыми словами |
|---|---|
| Merkle tree | Дерево хешей; листья — это отпечатки данных, а родители хешируют своих потомков |
| Leaf | Нижний узел; в Zcash — один note commitment |
| Merkle root | Единственный верхний отпечаток, суммирующий всё дерево |
| Authentication path / Merkle proof | Соседние хеши, необходимые, чтобы доказать, что лист находится в дереве |
| Incremental Merkle tree | Merkle tree только с добавлением (листья лишь добавляются) |
| Anchor | Корень Меркла, на который ссылается трата как на «состояние дерева, относительно которого я доказываю» |
| Nullifier set | Отдельная коллекция маркеров потраченных notes, которая блокирует двойные траты |
Почему дерево, а не просто длинный список хешей? Плоский список заставил бы вас раскрывать или обрабатывать каждую запись, чтобы доказать принадлежность. Дерево даёт доказательства логарифмического размера и один корень для проверки целостности.
Нуждается ли проверяющий во всём дереве? Нет. Проверяющему нужен только корень плюс ваш короткий путь аутентификации. В этом и состоит вся идея.
Почему именно глубина 32? Она ограничивает дерево примерно четырьмя миллиардами notes, что даёт большой запас, при этом сохраняя доказательство принадлежности (и его стоимость внутри схемы) фиксированного и управляемого размера.
Если корень меняется с каждой новой note, как старые доказательства остаются действительными? Сеть хранит окно недавних корней (anchor), поэтому доказательство, сделанное относительно немного более старого anchor, всё ещё проверяется. В статье о протоколе это будет сформулировано точно.
В нашем дереве с 4 листьями предположим, что злоумышленник тайно заменяет лист C на другое значение, но оставляет опубликованный корень неизменным. Что у него пойдёт не так и почему он не может незаметно это исправить? (Ответ ниже.)
Изменение C меняет hC (лавинный эффект), что меняет hCD = H(hC, hD), что меняет ROOT = H(hAB, hCD). Поэтому пересчитанный корень больше не совпадает с опубликованным корнем, и подмена обнаруживается. Чтобы «незаметно это исправить», злоумышленнику пришлось бы найти другое значение C, которое даёт тот же самый hC, а это коллизия хеша, что практически неосуществимо согласно Статье 3. Целостность сохраняется.
Статья 5 . Доказательства с нулевым разглашением: кульминация. Теперь у нас есть notes, commitments и дерево, и мы постоянно говорим: «доказано с нулевым разглашением». Статья 5 наконец объяснит, как можно доказать истинность утверждения — что ваша note находится в дереве, что ваш nullifier корректен, что баланс денег сходится, — не раскрывая ничего из этого.
Часть серии Zcash from First Principles для ZecHub. Лицензия CC BY-SA 4.0.