Игральные кости on-chain звучат просто: две стороны вносят секретные сиды, комбинируют их, и результат определяет победителя. Но «простота» рушится в тот момент, когда одна из сторон может узнать результат до внесения денег. Это и есть избирательная отмена — возможность отказаться от ставки, которую вот-вот проиграешь. В этой статье объясняется, как схема коммит-раскрытие (commit-reveal) и инвариант доминирования по ставке комиссии (fee-rate dominance) в мемпуле UTXO Kaspa закрывают эту поверхность атаки, используя путь повышения безопасности протокола костей Kaspa Forge (V1 → V2 → V3) в качестве конкретного примера.
Arena Dice находится в активной разработке; её постоянный продукционный сегмент отключён на время проверки. Ничто из нижеследующего не является приглашением к игре.
Что означает избирательная отмена для игральных костей on-chain
Каждый честный протокол костей on-chain требует трех свойств:
1. Ни одна из сторон не знает исход до внесения средств. 2. После внесения средств ни одна из сторон не может молча отказаться от проигрышного результата. 3. Механизм работает в публичном, враждебном мемпуле — без секвенсоров, координаторов или приватных каналов.
Избирательная отмена нарушает свойство 2. Если дом может вычислить бросок в момент ставки (до подтверждения), он отменяет проигрышные комнаты. Если игрок может задержать раскрытие, чтобы узнать ход дома, он отменяет неблагоприятные ставки. Обе формы являются избирательной отменой — они различаются по таймингу и тому, у кого есть информационное преимущество.
В системе на основе UTXO, такой как Kaspa, есть дополнительный нюанс: как только транзакция попадает в мемпул, любой может прочитать её сырые байты. Секрет, передаваемый в одной транзакции, — это секрет, опубликованный бесплатно.
Схема коммит-раскрытие, шаг за шагом
Коммит-раскрытие — это классическое решение, и оно естественно ложится на модель транзакций Kaspa. Протокол выполняется в три ончейн-транзакции.
Фаза 1 — Создание комнаты (дом)
Дом публикует комнату, содержащую коммит к своему секретному сиду:
house_commit = BLAKE3("KASPAFORGE_DICE_HOUSE_SEED_V1"
‖ game_id[32] ‖ house_seed[32])
Этот хеш компилируется в скрипт ковенанта. Сам house_seed на этом этапе не появляется on-chain. Поскольку BLAKE3 является PRF, просмотр house_commit не дает игроку никакой информации о броске — дом зафиксировал обязательство, но исход остается неизвестным всем.
Фаза 2 — JOIN (игрок)
Игрок отправляет транзакцию DICE_JOIN со своим собственным коммитом:
player_commit = BLAKE3("KASPAFORGE_DICE_PLAYER_SEED_V1"
‖ game_id[32] ‖ player_seed[32])
Оба сида зафиксированы; ни один не раскрыт. Никто не может вычислить бросок. Значение game_id в преобразовании каждого коммита предотвращает межкомнатные повторы: коммит, сделанный за одним столом, не может быть интерпретирован за другим.
Каждый сид — это 32 байта от CSPRNG в клиенте. Предсказуемые сиды — нули, повторно используемые ключи кошелька, что-либо, выводимое из публичной информации, — отвергаются. Тип DicePlayerSecret не реализует Clone или Debug; сид существует только в памяти вкладки до момента раскрытия.
Фаза 3 — PLAYER_REVEAL → HOUSE_REVEAL (расчет)
Как только JOIN игрока подтвержден, клиент автоматически создает и подписывает транзакцию PLAYER_REVEAL, которая вставляет player_seed в область состояния ковенанта. Взаимодействия с пользователем нет — у игрока нет решения, потому что он тоже не знает исхода.
Когда оба сида доступны on-chain, бросок вычисляется внутри ковенанта:
roll_u16 = LE_u16( BLAKE3("KASPAFORGE_DICE_ROLL_V1"
‖ game_id ‖ house_seed ‖ player_seed)[0..2] )
win ⟺ roll_u16 < win_threshold
Двухбайтовый диапазон (0..65535) делит пространство дайджеста BLAKE3 равномерно — без отбраковки, без смещения модуля. Пороги используют целочисленное округление вниз, так что преимущество дома не превышает заявленное значение.
Затем дом публикует HOUSE_REVEAL_WIN или HOUSE_REVEAL_LOSE, который выплачивает выигрыш в соответствии с фиксированным в ковенанте значением win_payout или удерживает банк.
Как сломалась V1: сид оказался в неправильной транзакции
Оригинальная Dice V1 помещала сид игрока внутрь транзакции JOIN — в открытом виде, читаемом из мемпула. Дом, зная свой собственный house_seed, мог вычислить бросок в момент появления JOIN, еще до подтверждения транзакции. Если исход был неблагоприятным, дом мог отменить комнату через ROOM_TIMEOUT или просто не обрабатывать ставку.
Это было не теоретическим: ROOM_TIMEOUT существовал с момента создания комнаты, предоставляя дому предварительно подписанный путь возврата в любой момент. Избирательная отмена была поведением по умолчанию.
Исправление было структурным: перенести сид игрока в отдельную транзакцию PLAYER_REVEAL. Но это создало новую поверхность — у игрока теперь есть окно между JOIN и PLAYER_REVEAL, где он знает свой собственный сид, но еще не опубликовал его.
Окно отмены игрока и безоперационные тайм-ауты
После подтверждения JOIN Game UTXO имеет два допустимых пути расходования в окне раскрытия:
PLAYER_REVEAL— подписанный игроком, вставляетplayer_seed, продолжает игру.PLAYER_REVEAL_TIMEOUT— безоперационный (любой может опубликовать), возвращаетstakeигроку иroom_valueдому.
Если игрок закрывает браузер или решает не раскрывать сид, капитал дома заблокирован до истечения тайм-аута. Окно тайм-аута сохраняется коротким (несколько минут), чтобы ограничить эту экспозицию.
Более тонкий вопрос: что если подписанная игроком транзакция PLAYER_REVEAL попадает в мемпул в тот же момент, когда становится валидным тайм-аут? После player_reveal_daa (дедлайна) обе транзакции становятся расходуемыми из одного и того же UTXO. Та, что выиграет аукцион замены в мемпуле, будет подтверждена. Если выиграет тайм-аут, сид игрока — уже находящийся в мемпуле — публикуется бесплатно, без расчета по игре.
Это мигрированная, но не устраненная избирательная отмена: V1 позволяла дому отменять в момент JOIN; V2 позволяет мемпулу арбитражировать между отменой и раскрытием после дедлайна.
Доминирование по ставке комиссии: выигрыш аукциона в мемпуле
Мемпул Kaspa выбирает транзакции по ставке комиссии (feerate) — комиссия за грамм массы транзакции. Когда две транзакции тратят один и тот же UTXO, транзакция с более высокой ставкой заменяет другую. Это ценовой пол для любой конкуренции честности против отмены.
Инвариант — это одно неравенство для каждой конкурирующей пары:
honest.fee_cap / honest.mass > rival.fee_cap / rival.mass
Применительно к трем соответствующим парам в конечном автомате (FSM) костей:
| Пара | Ставка честной транзакции | Ставка конкурирующей | Запас |
|---|---|---|---|
PLAYER_REVEAL vs PLAYER_REVEAL_TIMEOUT | 573.7 | 481.6 | 1.19× |
HOUSE_REVEAL_WIN vs HOUSE_REVEAL_TIMEOUT | 2 840.9 | 621.5 | 4.57× |
HOUSE_REVEAL_LOSE vs HOUSE_REVEAL_TIMEOUT | 2 927.4 | 621.5 | 4.71× |
*Измерено на золотом профиле, множитель 2×, базовая комиссия 100 сомпи/грамм.*
Ключевой дизайнерский ход: лимиты комиссии намеренно неравны. PLAYER_REVEAL имеет лимит 0.02 KAS; PLAYER_REVEAL_TIMEOUT имеет 0.016 KAS. Ветка тайм-аута структурно легче (нет сида, нет подписи игрока), поэтому даже с более низким абсолютным лимитом ей нужна ставка на 19.1% ниже, чтобы сравниться с веткой раскрытия. Ветка раскрытия выигрывает аукцион.
Со стороны дома запас шире: ветки раскрытия несут 0.1 KAS против 0.02 KAS у ветки тайм-аута — доминирование по ставке в 4.5–4.7 раза.
Ранняя версия (V2) сравнивала абсолютные суммы лимитов: «безоперационные ветки имеют более жесткие лимиты». Это было верно, но неактуально — мемпул сравнивает ставки комиссии, а не абсолютные комиссии. Ветка с пятикратно большим лимитом, но восьмикратно большей массой, проиграет аукцион. V3 заменила сравнение сумм инвариантом ставки, реализованным как защита генератора (комнаты, его нарушающие, не могут быть созданы) и проверенным измерительным зондированием по всем опубликованным ставкам и обоим режимам сомножителя.
Дополнительное укрепление
Инвариант ставки комиссии необходим, но недостаточен. Несколько других решений закрывают оставшиеся пробелы:
Обеспечение соблюдения дедлайна. После player_reveal_daa функция клиента build_dice_player_reveal отказывается собирать раскрытие. Публикация раскрытия в окне тайм-аута опубликовала бы сид на UTXO с двумя активными путями расходования — именно тот сценарий отмены, который предотвращает инвариант. Правильное действие — позволить ветке тайм-аута вернуть ставку и начать заново.
Размер резерва. Дом блокирует резерв (3 KAS в измеренной конфигурации) в каждый Game UTXO. До V3 ветка выигрыша была самой тяжелой в протоколе — привязана к хранилищу с запасом 2.54 раза. Увеличение резерва с 0.5 до 3 KAS снизило массу хранения на 92% и запас до 19 раз. Узкое место сместилось на ветку тайм-аута, что и требуется.
Нет доказательств с нулевым разглашением, нет деревьев Меркла, нет доверенной настройки. BLAKE3 как PRF, 32-байтные сиды CSPRNG и двухбайтовый равномерный диапазон дают проверяемую честность без какой-либо системы доказательств. Вики Kaspa документирует, что выбор транзакций и расчет массы — это решения на стороне кошелька с обеспечением on-chain — протокол костей опирается на ту же модель: пути скрипта ковенанта, фиксированные массы и конкуренция по ставке комиссии.
Как Kaspa Forge это применяет
Трехтранзакционная структура протокола костей — JOIN → PLAYER_REVEAL → HOUSE_REVEAL — это вторая игра в семействе Арены Kaspa Forge. Arena Blackjack (живая бета на мейннете) использует связанную, но более сложную последовательность с пятью шаблонами и пятью состояниями игры, потому что в блэкджеке есть несколько решений игрока. Кости упрощаются до двух состояний и двух шаблонов, причем один шаблон несет обе фазы раскрытия.
Инвариант ставки комиссии, разрешения на тайм-аут и измеренные таблицы масс входят в генератор, который создает скрипты ковенантов для каждой комнаты. Если конфигурация нарушает инвариант при какой-либо опубликованной ставке, генератор отказывается её создавать — жесткая защита, а не руководство.
Для других продуктов Kaspa Forge, использующих ковенанты — Kaspa Safe для хранилищ с тайм-локом, Escrow для P2P-сделок, Deposit для обеспечения — базовый принцип переносится напрямую: пути скрипта должны иметь четкий, измеримый приоритет по ставке комиссии, чтобы предполагаемое расходование всегда выигрывало аукцион в мемпуле у любой безоперационной альтернативы.
Arena Blackjack — это сегодня живая, играбельная игра Арены — попробуйте её в Деске. Arena Dice находится в активной разработке; её постоянный продукционный сегмент отключён до завершения независимой аудиторской проверки. Описанные здесь механики ковенантов являются частью этой продолжающейся инженерной работы.
Компромиссы и текущие границы
Ни один механизм не бесплатен. Схема коммит-раскрытие плюс дизайн ставки комиссии несут явные издержки:
Отмена игрока дешевая, но не бесплатная. Если игрок никогда не раскрывает свой сид, PLAYER_REVEAL_TIMEOUT возвращает его ставку — он теряет только сетевую комиссию транзакции JOIN. Капитал дома заблокирован на время окна тайм-аута. Это известное, принятое ограничение: добавление штрафного залога потребовало бы арифметики внутри скрипта ковенанта. Текущий дизайн намеренно избегает умножения и деления on-chain.
Подписанное, до-дедлайновое раскрытие, попадающее в мемпул после дедлайна, не может быть отозвано. Клиент прекращает сборку раскрытий после player_reveal_daa, но раскрытие, подписанное ранее и задержанное сетевым распространением, остается валидным. Это ограничение уровня контроллера, а не уровня консенсуса. Мера смягчения топологическая: клиент рассылает раскрытия напрямую публичным нодам, а не через единый шлюз.
Опоздавший JOIN после истечения срока комнаты. В виртуальной машине ковенантов Kaspa нет опкода «не старше». Транзакция JOIN, прибывшая после дедлайна комнаты, конкурирует с ROOM_TIMEOUT в мемпуле. Дом не может избирательно отменить — player_commit непрозрачен — но гонка существует. Лечение операционное: лобби прекращает продажу мест после дедлайна, а дом оперативно возвращает истекшие комнаты.
Кости не в работе. Уровень консенсуса (V3) получил независимый GO-вердикт по итогам третьего раунда проверки. Продукционная инфраструктура — подписанты, сборщики раскрытий дома, демон — строится и тестируется на мейннете с внутренними кошельками. Но публичных столов, пользовательского интерфейса и сервиса для игроков не существует. Постоянный продукционный сегмент отключён до проверки. Arena Blackjack остается играбельной игрой Арены.
Путь от «инженерного результата» до «первого стола» требует независимой аудиторской проверки продукционного слоя, прямой публичной рассылки раскрытий игроков и политики оператора для жизненного цикла комнат. Это инженерные шаги, а не исследовательские вопросы — но они еще не завершены.
FAQ
Что такое избирательная отмена в протоколе костей?
Избирательная отмена — это возможность отменить ставку после того, как стало известно, что исход будет неблагоприятным. Если любая из сторон может вычислить результат броска до внесения средств, она может отказаться от каждой проигрышной позиции.
Может ли игрок отменить ставку бесплатно?
После подтверждения JOIN единственный способ отмены — не раскрывать свой сид. Ветка PLAYER_REVEAL_TIMEOUT возвращает ставку игроку, но передаёт room_value дому. Игрок теряет только комиссию за транзакцию JOIN.
Что мешает дому избирательно отменять ставки?
Коммит-хеш дома фиксируется в комнате до присоединения любого игрока. Ветки раскрытия дома имеют лимит комиссии 0.1 KAS, который превосходит ветку тайм-аута по ставке в 4.5–4.7 раза. У дома нет ветки для тихого ухода.
Почему мемпул Kaspa важен для честности игры?
Kaspa выбирает транзакции по ставке комиссии — комиссия за грамм массы. Когда две транзакции тратят один и тот же UTXO, транзакция с более высокой ставкой выигрывает в аукционе замены. Это базовый примитив, гарантирующий, что честные переходы игры побеждают ветки тайм-аута.
Запущена ли Arena Dice в работу?
Нет. Arena Dice находится в разработке; её постоянный продукционный сегмент отключён на время проверки. Arena Blackjack — это живая, играбельная игра Арены.
Держите KAS там, где кражу можно отменить
Ковенант-сейф в мейннете Kaspa: ваши ключи, ваши правила, наш инструмент. Ончейн — бесплатно, навсегда.
Создать сейф
