Автор: Павел Найданов 🕵️♂️
Опр! Commitment scheme - криптографический алгоритм, который позволяет зафиксировать значение (утверждение), скрывая его от других, с возможностью раскрыть его позже.
Подобное решение работает таким образом, что с момента фиксации изменить значение не представляется возможным.
Концепт commitment scheme был впервые формализован Gilles Brassard, David Chaum и Claude Crépeau в 1988 году как часть zero-knowledge протоколов для NP задач. Однако, подобный концепт уже использовался ранее.
Важно! То, о чем я буду говорить, можно встретить под названием commit-reveal схема. В нашем контексте я буду использовать оба термина.
Целый ряд блокчейнов, в том числе и Ethereum, являются публичными. Это означает, что любые данные, попавшие внутрь такого блокчейна, доступны для чтения любым участником. Подобное "преимущество" усложняет работу с приватными данными, там, где это действительно необходимо. Классический пример - игра "Камень, ножницы и бумага". Игра требует сохранять выбор игрока в секрете, пока не будет сделан выбор соперником. Иначе игрок может сделать выбор основываясь на выборе соперника (читая выбор соперника из публичного блокчейна).
В подобных случаях помогает использование commitment scheme для временного обеспечения приватности данных.
Процесс состоит из трех действий: хеширование значения, которое необходимо скрыть, отправка хешированного значения на смарт-контракт, отправка первоначального (раскрытого) значения на смарт-контракт для подтверждения первоначального значения.
Однако все эти три действия, обычно, укладываются в два этапа:
- Commit phase. Создается хеш секретного значения и отправляется на смарт-контракт.
- Reveal phase. Раскрытие секретного значения. Базовое значения (значение, которое было захешировано) в открытом виде отправляется на смарт-контракт. Смарт-контракт самостоятельно хеширует полученное значение и проверяет на соответствие со значением присланным на этапе commit phase. Если хеш-значения совпадут, значит раскрытие секретного значения прошло успешно.
В реализации смарт-контрактов на solidity, для хеширования, используется функция keccak256()
.
Важно! В большинстве случаев commit и reveal этапы четко ограничены по времени.
После того, как все присланные значения раскрываются, смарт-контракт готов выполнять любую заложенную в него бизнес-логику: подсчет голосов, определение победителя лотереи и так далее.
-
Генерация случайных чисел. Монополистом в этой области все больше и больше становятся оракулы. Например, chainlink VRF. Однако commit-reveal схема может выступать, как альтернативный подход для обеспечения надежности и случайности генерируемых значений. Подобная схема может выглядеть так.
-
Голосование. Для обеспечения анонимности и предотвращения мошенничества при голосовании. Это позволяет сохранить конфиденциальность на этапе голосования, а раскрывать голоса только в момент подсчета.
-
Первоначальное сокрытие nft изображения. Далеко не всегда используется в этой схеме хеширование, но подход целом похож, подменить метаданные до востребования или наступления фазы раскрытия. Подмененные метаданные возвращают информацию не разглашающие данные nft.
-
Игры, лотереи, аукционы. Прекрасно подходит для предметной области, где скрытая ставка (предсказание, предположение) является ключевым фактором. Применение commit-reveal схемы для сбора скрытой информации с последующим ее раскрытием, сохраняет непредвзятость в определение победителей.
-
Любые конфиденциальные процессы. Commit-reveal схема может применяться в различных контекстах, где важна конфиденциальность данных и предотвращение манипуляций с публичными данными.
Простой смарт-контракт ниже демонстрирует commit-reveal схему. Порядок взаимодействия со смарт-контрактом:
-
Сгенерировать хеш для текстовой строки
getHash(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, "Be happy!")
В результате будет получен хеш
0x9e986e680a9a04cea824e4f0bdfa67a872878f2485628a12d056a4bff0528bb6
. -
Отправить полученный хеш на смарт-контракт.
commit(0x9e986e680a9a04cea824e4f0bdfa67a872878f2485628a12d056a4bff0528bb6)
-
Раскрыть значение текстовой строки
reveal("Be happy!")
.
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.21;
/**
* @title Commit-reveal schema
* @notice Смарт-контракт реализует процесс сохранения хешированного значения строки с последующим раскрытием
* @dev Для хеширования значения использовать публичную функцию getHash()
*/
contract CommitReveal {
mapping (address account => bytes32 hash) private _hashes;
event Committed(address indexed account, bytes32 hash);
event Revealed(address indexed account, string value);
error HashAlreadyCommitted();
error NotCommitted();
error ValueNotMatchHash();
/**
* @notice Сохраняет приватную тестовую строку
* @param hash Хеш приватной строки
* @dev Для получения hash использовать публичную функцию getHash()
*/
function commit(bytes32 hash) external {
if (hasHash(msg.sender)) {
// Запрещаем повторно сохранять хеш
revert HashAlreadyCommitted();
}
// Сохраняем хеш
_hashes[msg.sender] = hash;
emit Committed(msg.sender, hash);
}
/**
* @notice Раскрывает приватную строку пользователя
* @param value Раскрытая строка
*/
function reveal(string memory value) external returns (string memory) {
if (!hasHash(msg.sender)) {
// Отменяем транзакцию, если хешированное значение не было добавлено
revert NotCommitted();
}
// Получаем хешированное значение
bytes32 hash = getHash(msg.sender, value);
if (hash != _hashes[msg.sender]) {
// Отменяем транзакцию, если хешированное значение не совпадает с сохраненным хешированным значением на стадии commit
revert ValueNotMatchHash();
}
// Удаляем сохраненное хешированное значение
// Это позволит пользователю повторно вызвать commit() функцию
delete _hashes[msg.sender];
// Отправляем событие, тем самым раскрывая и подтверждая правильность раскрытия
emit Revealed(msg.sender, value);
return value;
}
/**
* @notice Проверяет наличие сохраненного хешированного значения
* @param account Адрес аккаунта
* @return True, если хешированное значение было добавлено, иначе - false
*/
function hasHash(address account) public view returns (bool) {
if (_hashes[account] != bytes32(0)) {
return true;
}
return false;
}
/**
* @notice Получает хеш значение
* @param account Адрес аккаунта
* @param value Секретное значение, которое необходимо захешировать
*/
function getHash(address account, string memory value) public pure returns (bytes32) {
return keccak256(abi.encodePacked(account, value));
}
}
Commitment schema - это простой и мощный механизм, который позволяет построить приложение, основываясь на доверии.
Применение зависит от конкретных потребностей и требований проекта или приложения. Эта схема помогает обеспечить безопасное и доверенное раскрытие данных в контексте децентрализованных систем.
- Commitment scheme - wikipedia
- Статья про commit-reveal и пример игры "Камень, ножницы и бумага".
- Слепой аукцион из документации solidity
- Интересный проект randao. Реализует DAO по генерации рандомного значения и поставке его в сеть. Подобный подход нашел применения с некоторыми доработками в работе некоторых блокчейнов (Tezos, Ethereum)