
Серия: Zcash from First Principles . Статья 3 . Хеширование и коммитменты Для кого: новички. Мы опираемся на Статью 1 (конечные поля) и Статью 2 (эллиптические кривые), но интуитивное понимание здесь самодостаточно. Что вы поймёте: чёткое понимание хеш-функций, того, что на самом деле означают "hiding" и "binding", и как Zcash строит коммитменты note, лежащие в основе каждого приватного платежа.
В Статье 0 мы описали «магический запечатанный конверт»: нечто, что можно прикрепить к публичной доске и тем самым доказать существование конверта, скрывая его содержимое, и что потом уже невозможно подменить. Мы пообещали объяснить, как такое вообще возможно. Именно этому и посвящена данная статья. Нам понадобятся два ингредиента: хеш-функции и коммитменты.
Представьте, что вы предсказываете исход выборов и хотите доказать постфактум, что сделали этот прогноз заранее. Вы не можете просто объявить свой прогноз (это влияет на людей или вызывает обвинения, будто вы его изменили). Но и держать его полностью в секрете нельзя (тогда вы позже ничего не сможете доказать).
Вам нужен способ сейчас публично зафиксировать значение так, чтобы:
Этот механизм «зафиксировать сейчас, раскрыть позже, без возможности солгать» называется коммитментом, и в Zcash он используется повсюду. Значение и владелец note фиксируются в коммитменте в момент создания note. Чтобы построить коммитменты, нам сначала нужен их главный рабочий инструмент: хеш-функция.
Хеш-функция берёт любые данные — одну букву или целую библиотеку — и сжимает их в короткую строку фиксированного размера, называемую digest или hash. Думайте о ней как об отпечатке пальца для данных.

У хорошего криптографического отпечатка есть четыре свойства. Воспринимайте их интуитивно, а не как уравнения:
| Свойство | Простыми словами | Почему это важно |
|---|---|---|
| Детерминированность | Один и тот же вход всегда даёт один и тот же отпечаток | Вы можете перепроверить отпечаток в любой момент |
| Быстрота в прямом направлении | Вычислить отпечаток быстро | Это практично для повсеместного использования |
| Односторонность (стойкость к прообразу) | Имея отпечаток, нельзя найти вход, который его породил | Это скрывает исходные данные |
| Стойкость к коллизиям | Нельзя найти два разных входа с одним и тем же отпечатком | Никто не сможет подделать совпадение |
И есть ещё одно поведение, из-за которого такие отпечатки кажутся почти магическими:
Измените входные данные хоть на малейшую величину — и отпечаток изменится полностью, без какого-либо сходства со старым. Вот два реальных отпечатка SHA-256 для сообщений, различающихся всего одним символом:
H("Pay Bob 5 ZEC") = 6e2dc1a954c70cc865f18ea8cb70b7b56eeaf6ca42b380824a55d65dc342f34b
H("Pay Bob 6 ZEC") = 76abc346d8d3053f76a9ae18b617af71f02729a73ec6a51732d2d94934e4217f
Из 64 шестнадцатеричных символов 59 различаются. Один символ на входе — и на выходе совершенно не связанный с ним отпечаток. Именно поэтому нельзя постепенно подталкивать вход к целевому отпечатку: здесь нет никакого сигнала «теплее / холоднее», за которым можно было бы следовать.
Вот соблазнительная, но неверная идея: чтобы сделать коммитмент к секретному значению v, просто опубликовать его отпечаток H(v).
Это хорошо вас привязывает (вы не сможете позже заявить другое v, потому что для этого понадобилась бы коллизия). Но это не скрывает значение. Если множество возможных значений невелико, атакующий просто вычисляет отпечатки всех кандидатов и сравнивает. Делаете коммитмент к «да» или «нет»? Он хеширует оба варианта и мгновенно узнаёт, какой вы выбрали. Детерминированность, которая только что была нашим другом, теперь выдаёт секрет.
Исправление выражается одним словом: случайность.
Коммитмент — это отпечаток вашего значения, смешанный со свежим случайным числом:
commitment = H(v, r)гдеr— секретное случайное значение «ослепления» (blinding).
Теперь одно и то же v каждый раз даёт по-разному выглядящий коммитмент, потому что r разное. Два свойства, которые нам были нужны, наконец выполняются одновременно:

Чтобы позже открыть (раскрыть) коммитмент, вы публикуете v и r; любой может заново вычислить H(v, r) и проверить, что оно совпадает. Вы зафиксировали значение. Это и есть тот самый магический запечатанный конверт из Статьи 0, воплощённый в реальность.
Два вывода, которые стоит запомнить навсегда: binding возникает благодаря стойкости хеша к коллизиям; hiding обеспечивается случайным фактором ослепления
r.
Есть два распространённых рецепта, и Zcash использует оба.
| Коммитмент на основе хеша | Коммитмент Pedersen (из Статьи 2) | |
|---|---|---|
| Рецепт | H(v, r) | v.G + r.H (точки на кривой) |
| Сокрытие обеспечивается | случайным r | случайным r |
| Связывание обеспечивается | стойкостью к коллизиям | эллиптическокривой ловушкой (ECDLP) |
| Особая сила | просто и быстро | коммитменты складываются (гомоморфны) |
Последняя строка показывает, почему коммитменты Pedersen так важны в Zcash. Поскольку commit(v_1) + commit(v_2) является корректным commit(v_1 + v_2), протокол может позже доказать, что входящие деньги равны исходящим, складывая коммитменты, и всё это без раскрытия ни одной суммы. Мы сохраним этот факт для Статьи 6.
Вот идея, которую большинство вводных материалов упускают, и это как раз тот момент, где стоит подчеркнуть встречу «математики и инженерии».
SHA-256 — превосходный отпечаток для повседневных вычислений. Но Zcash не просто вычисляет хеши; ему нужно доказывать внутри zero-knowledge proof, что хеш был вычислен корректно (Статья 5 объясняет почему). И тут есть проблема: zero-knowledge proof работает на языке арифметики конечных полей (Статья 1), тогда как SHA-256 построен из побитовых операций (сдвиги, AND, XOR). Выразить все эти побитовые действия через арифметику полей чрезвычайно дорого, из-за чего доказательства становятся огромными и медленными.
Поэтому криптографы Zcash разработали хеш-функции, чья внутренняя структура уже выражена через арифметику полей, что делает их дешёвыми для доказательства:

Именно это инженерное требование — «это должно быть дёшево доказывать» — объясняет, почему Zcash изобрёл и принял специальные хеш-функции вместо того, чтобы везде использовать SHA-256.
Zcash использовал разные хеши в разных версиях своей конструкции, и каждый выбирался под конкретную задачу:
| Дизайн | Используемые хеши | Где |
|---|---|---|
| Sprout (самый ранний) | SHA-256 | Коммитменты note и дерево |
| Sapling | Pedersen hashes, плюс BLAKE2 | Pedersen для коммитментов note и дерева Merkle; BLAKE2 для вывода ключей и nullifiers |
| Orchard (текущий) | Sinsemilla, плюс Poseidon | Sinsemilla для коммитментов note и дерева Merkle; Poseidon для nullifier, всё это спроектировано для арифметических схем |
Имена, которые стоит узнавать, — это Pedersen и Sinsemilla (хеши в стиле коммитментов, построенные из точек на кривой, поэтому они наследуют суперсилу «складываемости» и дёшево доказываются) и Poseidon (хеш на арифметике полей, специально созданный для zero-knowledge circuits). Когда в Статье 0 говорилось, что содержимое note запечатывается в коммитмент, именно этот механизм и выполняет запечатывание.
Так что открытый вопрос из Статьи 0 — «как запечатанный конверт может скрывать своё содержимое и при этом быть невозможным для подделки?» — теперь закрыт: сокрытие обеспечивается случайным фактором ослепления, а связывание — стойкостью к коллизиям или ловушкой эллиптической кривой.
Мы упростили изложение ради ясности. Реальные схемы коммитментов точно задают, как кодируются v и r и какие генераторы используются; у свойств "hiding" и "binding" есть разные варианты (совершенное и вычислительное) с точными определениями безопасности; и мы не показали внутреннее устройство Pedersen, Sinsemilla или Poseidon. Ничто из этого не меняет интуицию: коммитмент — это отпечаток плюс случайность, который сейчас скрывает, а навсегда связывает. Детали вернутся — с явными пометками — когда они понадобятся в статье о протоколе.
H(v) связывает, но не скрывает. Добавление случайного фактора ослепления, H(v, r), это исправляет: hiding за счёт r, binding за счёт стойкости к коллизиям.| Термин | Значение простыми словами |
|---|---|
| Hash function | Сжимает любые данные в короткий отпечаток фиксированного размера (digest) |
| Digest | Выходной отпечаток хеш-функции |
| Preimage resistance | Нельзя обратить digest обратно в его вход (односторонность) |
| Collision resistance | Нельзя найти два входа с одинаковым digest |
| Avalanche effect | Крошечное изменение входа полностью меняет digest |
| Commitment | Зафиксировать значение сейчас, раскрыть позже, без возможности солгать |
Blinding factor (r) | Новое случайное число, которое делает коммитмент скрывающим |
| ZK-friendly hash | Хеш, построенный на арифметике полей, поэтому его дёшево доказывать |
Почему бы просто не зашифровать значение вместо того, чтобы делать к нему коммитмент? Шифрование — это про секретность, которую потом можно расшифровать. Коммитмент — это про binding: гарантию того, что позже вы не сможете изменить свой ответ. Это разные задачи.
Если коммитменты скрывают значение, как тогда вообще кто-то проверяет соблюдение правил? Для этого и нужны zero-knowledge proofs (Статья 5): они доказывают, что скрытое значение подчиняется правилам, не раскрывая его.
Раз Zcash местами избегает SHA-256, значит ли это, что SHA-256 сломан? Нет. С SHA-256 всё в порядке, и Zcash по-прежнему его использует. Просто его дорого доказывать внутри схемы, поэтому для этой конкретной задачи и существуют ZK-friendly хеши.
Откуда берётся случайное r и кто его хранит?
Оно генерируется заново при создании note и известно владельцу note. Это часть того, что делает каждую note уникальной и приватной.
Вы делаете коммитмент к своему прогнозу на выборы как H(v, r) и публикуете его. Друг настаивает, что для простоты вам стоит публиковать просто H(v). В одном предложении: почему это плохая идея, если возможны только два исхода? (Ответ ниже.)
Если возможны только два исхода, ваш друг может просто сам вычислить H("win") и H("lose") и сравнить их с опубликованным вами digest, мгновенно узнав ваш прогноз. Голый хеш связывает, но не скрывает; именно случайное r останавливает такую атаку перебором и сравнением.
Статья 4 . Деревья Merkle: теперь у нас накапливаются миллионы коммитментов. Статья 4 показывает, как Zcash организует их в единое дерево, чей маленький корневой отпечаток представляет всю историю целиком, и как можно доказать, что ваша note находится в этом дереве, не раскрывая какая именно. Это и есть настоящая форма «публичной доски» из Статьи 0.
Часть серии Zcash from First Principles для ZecHub. Лицензия CC BY-SA 4.0.