Честная ончейн-игра в кости должна решить задачу, которая звучит просто, но на деле таковой не является: две стороны, не доверяющие друг другу, должны совместно сгенерировать случайный результат, зафиксировать свои входные данные до того, как увидят чужие, и произвести расчёт выплаты — всё это без передачи хранения третьей стороне. Ковенант костей Kaspa решает эту задачу с помощью семи путей расходования, организованных в конечный автомат (FSM), охватывающий три ончейн-транзакции. Каждый путь — это ветвь ковенантного скрипта, условие, при котором UTXO может быть потрачен, — и вместе они обеспечивают полный жизненный цикл одной ставки.
В этой статье описаны все семь путей, объяснены криптографические обязательства, обеспечивающие честность игры, и показано, как структура ставок комиссии делает честное поведение доминирующим в мемпуле. Arena Dice находится в разработке; постоянная продакшн-ветка отключена до завершения ревью. Далее речь идёт о протокольной инженерии, а не об анонсе продукта.
Проблема: обязательство без видимости
При подбрасывании монеты между двумя незнакомцами каждая сторона обладает секретным случайным сидом. Честный протокол — это коммит-раскрытие: обе стороны фиксируют данные, публикуя хеши, затем обе раскрывают их, и результат вычисляется на основе двух раскрытых значений. Если бы любая сторона могла увидеть сид другой до фиксации, она могла бы участвовать только тогда, когда выигрывает.
В Kaspa «обязательство» — это UTXO, заблокированный ковенантным скриптом, который проверяет хеш BLAKE3 против значения, предоставленного при расходовании. Вики Kaspa описывает модель транзакций и UTXO, делающую это возможным. Ковенант костей расширяет базовую блокировку хешированием с разделением доменов: каждый хеш включает версионированную доменную строку, поэтому обязательство на сид никогда не может совпасть с обязательством на идентификатор комнаты или результат броска.
Ковенант — условие расходования UTXO, которое проверяет данные транзакции расходования, а не только подписи. В Kaspa ковенанты активируются хардфорком Toccata и используют опкоды вроде OpBlake3 для проверки хешей против зафиксированных значений непосредственно в скрипте.
Семь ветвей
FSM имеет две UTXO-фазы — Комната и Игра — с двумя состояниями Игры. Их покрывают два шаблона скриптов: один для Комнаты, один для Игры. Шаблон Игры содержит оба состояния T0 и T1; когда PLAYER_REVEAL расходует монету T0, он пересобирает наследника как тот же скрипт с новой областью данных в начале (раскрытый сид). Семь ветвей связывают состояния:
Room
├── DICE_JOIN player sig → Game T0 (carries player_commit)
└── ROOM_TIMEOUT permissionless → house
Game · T0_AWAITING_PLAYER_REVEAL
├── PLAYER_REVEAL player sig → Game T1 (seed in state)
└── PLAYER_REVEAL_TIMEOUT permissionless → stake to player, room_value to house
Game · T1_AWAITING_HOUSE_REVEAL
├── HOUSE_REVEAL_WIN house sig roll < T → player: win_payout
├── HOUSE_REVEAL_LOSE house sig roll ≥ T → house: game_value − fee
└── HOUSE_REVEAL_TIMEOUT permissionless → player: entire Game UTXO
«Без разрешения» означает, что любой может отправить транзакцию — подпись ни одной из сторон не требуется. Эти ветви тайм-аутов существуют, чтобы средства не оказались заблокированы навсегда, если одна из сторон исчезнет.
Никаких ZK-доказательств, деревьев Меркла, доказателей или залогов не задействовано. Три транзакции на ставку, две подписи от игрока, одна от дома.
Жизненный цикл из трёх транзакций
Транзакция 1 — JOIN. Игрок подписывает транзакцию с ковенантной защитой, которая блокирует его ставку в Game UTXO в состоянии T0. Транзакция несёт player_commit — хеш BLAKE3 32-байтового сида игрока:
player_commit = BLAKE3(KASPAFORGE_DICE_PLAYER_SEED_V1
‖ game_id ‖ player_seed)
Сам сид не попадает в блокчейн. Обязательство дома (house_commit) было уже зафиксировано в байтах Комнаты до того, как игрок сел за стол. На этом этапе ни одна сторона не может вычислить результат броска — BLAKE3 является PRF, и каждое обязательство скрывает соответствующий сид.
Транзакция 2 — PLAYER_REVEAL. Клиент игрока автоматически подписывает и отправляет эту транзакцию после подтверждения JOIN. Раскрытый player_seed записывается в состояние Игры. Этот шаг автоматизирован по замыслу: у игрока нет осмысленного выбора (он не знает результата), а необходимость ручного клика превратила бы игру в один клик в игру в два клика с таймером между ними.
Если браузер игрока закроется до раскрытия, сид будет утерян — это сделано намеренно, а не по недосмотру. Восстанавливаемый сид — это сид, который дом тоже мог бы получить. После дедлайна player_reveal_daa любой может отправить PLAYER_REVEAL_TIMEOUT, вернув игроку точную сумму ставки и передав стоимость комнаты дому. Игрок потеряет только комиссию за исходную транзакцию JOIN.
Транзакция 3 — HOUSE_REVEAL. Дом раскрывает house_seed. Ковенант вычисляет результат броска в блокчейне:
roll_u16 = LE_u16( BLAKE3(KASPAFORGE_DICE_ROLL_V1
‖ game_id ‖ house_seed ‖ player_seed)[0..2] )
win ⟺ roll_u16 < win_threshold
Если результат ниже порога, ковенант переходит к HOUSE_REVEAL_WIN и выплачивает игроку win_payout. В противном случае HOUSE_REVEAL_LOSE выплачивает дому game_value − fee. Если дом пропускает свой дедлайн, HOUSE_REVEAL_TIMEOUT (без разрешения) возвращает весь Game UTXO игроку.
Криптографический механизм
Разделение доменов. Шесть версионированных доменных строк гарантируют, что хеши, построенные из одних и тех же входных данных для разных целей, никогда не совпадут:
KASPAFORGE_DICE_ROOM_V1— идентификатор комнаты и домен защиты от повторного использованияKASPAFORGE_DICE_HOUSE_SEED_V1— обязательство домаKASPAFORGE_DICE_PLAYER_SEED_V1— обязательство игрока (добавлено в V2)KASPAFORGE_DICE_ROLL_V1— вычисление броскаKASPAFORGE_DICE_RULES_V1— условия ставки в виде одного хешаKASPAFORGE_DICE_STATE_V1— дайджест компоновки области состояния
Тест проверяет попарную различность со всеми доменами Blackjack, включая проверку коллизий префиксов — поскольку эти строки конкатенируются, а не разделяются по длине, одна доменная строка, являющаяся префиксом другой, означала бы реальную коллизию.
Идентификатор игры. game_id выводится из якорной транзакции Комнаты, не позволяя двум комнатам разделять идентичность и допуская повторное использование обязательств между столами.
Диапазон — степень двойки. Бросок читает два байта дайджеста BLAKE3, давая равномерный диапазон 0–65535. Это сделано намеренно: 2^16 делит хеш-пространство нацело, поэтому ни одно значение не встречается чаще других. Использование mod 10000 внесло бы смещение 16,67% на малых остатках (поскольку 65536 = 6 × 10000 + 5536, остатки ниже 5536 встречаются семь раз за период, а остальные — шесть). Измеренная равномерность проходит χ²-тесты на 10⁷ бросках по двум независимым лентам сидов.
Обработка знакового бита. Kaspa читает элементы стека как знаковые числа в формате little-endian. Необработанный двухбайтовый фрагмент со старшим байтом ≥ 0x80 прочитался бы как отрицательное число, что сделало бы половину всех бросков выигрышными при любом положительном пороге. Ковенант добавляет нулевой байт (substr(0,2) ‖ 0x00) для получения трёхбайтового неотрицательного целого числа. При включённом covenants_enabled узел не требует минимальной кодировки, поэтому избыточный третий байт принимается.
Генерация сида. Сид игрока создаётся клиентским крейтом (DicePlayerSecret::mint()) с использованием CSPRNG браузера. У типа нет Clone или Debug — это обеспечивается системой типов, а не анализом исходного кода, — потому что копируемый сид — это сид, который может утечь. Конструктор from_entropy существует для кошельков с аппаратными источниками ГСЧ, но качество энтропии лежит на вызывающем коде. В коде прямо указано ограничение: *он не может сертифицировать энтропию, и ничто не может*.
Доминирование по ставке комиссии: почему честные действия побеждают
Мемпул Kaspa выбирает транзакции по ставке комиссии — комиссия за грамм массы, а не просто комиссия. Каждое честное действие (раскрытие, расчёт) конкурирует с альтернативой без разрешения (тайм-аут) на том же UTXO. Если бы тайм-аут имел более высокую ставку комиссии, он выиграл бы аукцион замены, и честная транзакция никогда не подтвердилась бы.
Дизайн V3 обеспечивает инвариант доминирования по ставке комиссии: для каждой пары «честное действие vs. конкурент без разрешения на той же монете» честное действие имеет строго более высокую ставку комиссии. Измерено на эталонном профиле при ставке 2×:
| Пара | Ставка честного действия | Конкурент | Запас |
|---|---|---|---|
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× |
Наименьший запас во всём контуре — 3,21× (PLAYER_REVEAL_TIMEOUT при более высоких ставках). Предыдущий дизайн сравнивал *суммы* комиссий, но мемпул сравнивает *ставки* комиссий. Ветвь с пятикратно большим лимитом комиссии всё равно может проиграть аукцион, если она весит в восемь раз больше. V3 измеряет ставки, а не суммы, и генератор отклоняет любую комнату, нарушающую инвариант.
Измеренные затраты
При базовой сетевой ставке комиссии 100 сомпи/грамм полная стоимость ставки составляет:
Win path: JOIN + PLAYER_REVEAL + WIN = 11 656 g → 0.011656 KAS
Lose path: JOIN + PLAYER_REVEAL + LOSE = 11 552 g → 0.011552 KAS
Expected at 2× (p = 0.489990): 0.011603 KAS
Это примерно вдвое дешевле V2. Экономия достигнута за счёт увеличения резерва дома (0,5 → 3 KAS), что сократило массу хранения ветви выигрышного расчёта с 24 267 до 1 831 граммов. Дом оплачивает это замороженным капиталом, а не игрок.
Лестница определяет предустановленные пары множитель/порог при преимуществе дома 2%:
| Множитель | Порог | Вероятность выигрыша | Реализованное преимущество |
|---|---|---|---|
| 2× | 32 112 | 0,489990 | 2,0019% |
| 5× | 12 845 | 0,195999 | 2,0004% |
| 10× | 6 422 | 0,097992 | 2,0080% |
Формула порога использует деление с округлением вниз, что слегка смещает реализованную маржу в пользу дома — с ограничением MAX_EDGE_OVERSHOOT_PPM = 500.
Как это используется в Kaspa Forge
Ковенантный FSM костей является частью семейства протоколов Arena на Kaspa Forge — той же инфраструктуры, на которой работает Arena Blackjack, публичная карточная игра на бете основной сети. Продукты Arena следуют общему принципу: средства находятся в ончейн-UTXO Комнаты/Игры, игроки подписывают локально через Desk, а расчёт обеспечивается ковенантными скриптами, а не балансами платформы. Принцип некастодиального хранения распространяется на всё семейство — Kaspa Safe использует ковенантные хранилища с выводами по тайм-локу, Escrow блокирует средства для P2P-сделок, Deposit удерживает залог в блокчейне.
Для Dice конкретно уровень консенсуса (V3) прошёл три независимых раунда проверки. Фазы контроллера P1 (построители расчётов дома) и P2 (демон с сидами для каждой ставки) построены и протестированы на мейннете с собственными кошельками команды — шесть комнат, пять ставок, шесть из семи путей проверены. Фаза P3 (комнаты, интерфейс, политика оператора) и боевое развёртывание не существуют.
Arena Blackjack — действующий продукт Arena на мейннете Kaspa, верифицируемая карточная игра, в которой средства хранятся в ончейн-UTXO Комнаты/Игры, а доказательства можно проверить независимо. Arena Dice использует ту же ковенантную инфраструктуру и основы протокола Arena, но его постоянная продакшн-ветка остаётся отключённой до завершения ревью. Следите за инженерными обновлениями в блоге Kaspa Forge или исследуйте действующую игру через Desk.
Компромиссы и текущие ограничения
Три транзакции на ставку. Это дороже, чем дизайн с двумя транзакциями. Третья транзакция существует, потому что исходная двухтранзакционная версия (V1) содержала критический недостаток: сид игрока передавался в JOIN в открытом виде, позволяя дому вычислить результат до подтверждения — и избирательно отменять невыгодные ставки. Разделение на коммит и раскрытие — это исправление, и оно стоит ещё одного ончейн-раунда.
Игрок должен оставаться в сети для автоматического раскрытия. Клиент повторно отправляет подписанную PLAYER_REVEAL до дедлайна. Если вкладка закрывается, сид утрачен и активируется путь тайм-аута. Это намеренное решение: восстанавливаемый сид — это скомпрометированный сид. Окно тайм-аута устанавливается коротким, чтобы закрытая вкладка не блокировала капитал дома надолго.
Поздний JOIN после дедлайна комнаты. В Kaspa нет опкода «не старее чем» — только «не моложе чем». JOIN, поступивший после дедлайна комнаты, конкурирует с ROOM_TIMEOUT на том же UTXO. Дом не может избирательно отменять (на этом этапе он не может отличить выигрышные ставки от проигрышных), но операционная мера в том, что лобби не должно продавать места после дедлайна, а дом должен своевременно возвращать просроченные комнаты.
Заморозка капитала. Дом резервирует (multiplier − 1) × stake в каждой комнате на весь срок. При 10× и ставке 50 KAS это 450 KAS заблокировано на столе. Это цена того, чтобы ветвь выигрышного расчёта была достаточно лёгкой для доминирования над конкурентом-тайм-аутом в аукционе мемпула.
Не боевое, не публичное. Постоянная продакшн-ветка отключена до завершения ревью. Нет интерфейса, нет публичных столов, ARENA_DICE_ARMED=0. Код консенсуса, контроллер и криптографические домены построены и проверены — но граница продукта чёткая: это инженерия, а не сервис.
Продолжить изучение
протокол Arena и руководство по проверке
Следующий шаг: посмотреть действующие столы Arena
FAQ
Что такое ковенант костей Kaspa?
Набор ончейн-ковенантных скриптов, обеспечивающих честную игру в кости между игроком и домом без необходимости доверять друг другу или третьей стороне. Семь путей расходования в трёх транзакциях, с обязательствами на BLAKE3, гарантирующими, что ни одна сторона не может предсказать результат до фиксации.
Почему для игры в кости нужны три ончейн-транзакции?
Два секретных сида требуют двух транзакций раскрытия плюс начальный JOIN. JOIN блокирует ставку хеш-обязательством. PLAYER_REVEAL раскрывает сид игрока. HOUSE_REVEAL раскрывает сид дома и производит расчёт. Такое разделение не позволяет ни одной стороне увидеть результат до фиксации.
Что произойдёт, если игрок закроет браузер после ставки?
Сид игрока хранится только в памяти вкладки и будет утерян. После дедлайна раскрытия любой может отправить PLAYER_REVEAL_TIMEOUT, вернув ставку игроку и передав стоимость комнаты дому. Игрок потеряет только комиссию за исходную транзакцию JOIN.
Как генерируется случайность без доверенной третьей стороны?
Обе стороны фиксируют 32-байтовые сиды от CSPRNG через хеши BLAKE3 до того, как любой из них будет раскрыт. Результат броска — первые два байта BLAKE3(DICE_ROLL_V1 ‖ game_id ‖ house_seed ‖ player_seed), прочитанные как little-endian u16. Диапазон 0–65535 является степенью двойки, поэтому распределение абсолютно равномерное.
Можно ли играть в Arena Dice?
Нет. Arena Dice находится в разработке; постоянная продакшн-ветка отключена до завершения ревью. Уровень консенсуса прошёл третий независимый раунд проверки, но пользовательского интерфейса, публичных столов и боевого развёртывания нет. Arena Blackjack — действующий продукт Arena на сегодняшний день.
Что мешает дому отменять невыгодные ставки?
V2 заменил открытый сид в JOIN на обязательство на основе BLAKE3. В момент JOIN ни одна сторона не может вычислить результат броска — дом не может отличить выигрышные ставки от проигрышных. После раскрытия сида игроком у дома есть дедлайн; его пропуск запускает HOUSE_REVEAL_TIMEOUT, возвращая весь Game UTXO игроку.
Держите KAS там, где кражу можно отменить
Ковенант-сейф в мейннете Kaspa: ваши ключи, ваши правила, наш инструмент. Ончейн — бесплатно, навсегда.
Создать сейф
