Кости, в которых не нужно верить нам на слово
До начала игры мы фиксируем секретное число и публикуем его отпечаток. По окончании публикуем само число. Между этими двумя моментами каждый бросок уже предопределён — а после любой может пересчитать их все и убедиться, что ничего не сдвинулось.
Открыть журналТо, что не исправит даже хороший генератор случайных чисел
Наши кости берутся из криптографического генератора операционной системы — того же источника, что защищает вход в банк, а не из ярлыка, которым пользуется большинство игр. Это решает вопрос о качестве случайности. И ничего не решает в том вопросе, который на самом деле задают игроки.
Честно выпавшая кость и намеренно выбранная выглядят потом одинаково. И то и другое — просто число в журнале.
Значит, полезный вопрос не «хорош ли ваш генератор?», а «могу ли я проверить?». Всё, что ниже, существует ради того, чтобы ответ был «да» — без просьбы поверить нам хоть в чём-то.
Запечатано до, вскрыто после
Четыре шага. Два из них вы можете выполнить сами — и именно эти два поймали бы нас.
- 1
Запечатать
До первого броска сервер берёт секретное 32-байтовое число — серверное зерно — и публикует его отпечаток SHA-256 обоим игрокам. Отпечаток не выдаёт ничего, но зерно уже зафиксировано: любое другое дало бы другой отпечаток.
commit0df0916cb0…2904e706 - 2
Смешать
Ваше приложение генерирует собственное случайное зерно и отправляет его, когда вы садитесь за стол. Зёрна обоих игроков входят в каждый бросок, поэтому мы не можем заранее подобрать зерно с удобными нам бросками — в момент фиксации мы не знаем, что вы добавите.
clientSeeda26460084a0ef89b:d4f28bac6efd1b6e - 3
Вывести
Каждая кость считывается из HMAC-SHA256 от зёрен и номера броска. Ничего не хранится и ничего не выбирается: одни и те же три входа всегда дают одну и ту же кость, и никакие другие её не дают.
die(n) = (HMAC-SHA256(serverSeed, "clientSeed:n:round")[0] < 252) % 6 + 1 - 4
Вскрыть
По окончании игры мы публикуем серверное зерно. Возьмите его хеш, сравните с отпечатком, выданным до первого броска, затем пересчитайте каждую кость. Сервер, выбравший хотя бы один бросок, провалит одну из этих двух проверок.
serverSeed5cde3365be…7b6b2bd5
Значения выше — настоящее доказательство игры №148, завершённой игры, которую может посмотреть любой. Введите этот номер в поле ниже и убедитесь сами.
Проверить игру — здесь и сейчас
Введите номер игры. Страница получит её доказательство, затем заново возьмёт хеш зерна и пересчитает каждую кость средствами криптографии вашего собственного браузера.
Или вставьте три значения сами
Раскрытое зерно совпадает с отпечатком
Кости воспроизведены
Под этим номером доказательства нет. Игры нумеруются в порядке завершения, и опубликованное зерно есть только у завершённой игры.
Не удалось получить доказательство. Это проблема соединения, а не проваленная проверка — ничего не опровергнуто.
Это не номер игры, либо вставленные значения — не два 64-символьных хеша и клиентское зерно.
Считаем…
Почему ни одна грань не вероятнее даже чуть-чуть
Байт — число от 0 до 255, то есть 256 вариантов. 256 не кратно шести: это 6 × 42 и ещё 4 в остатке. Возьмите остаток от деления байта на шесть — и четыре грани тихо получат 43 шанса из 256, а две другие 42. Смещение крошечное, но на миллионе бросков это уже настоящее преимущество. Поэтому эти четыре значения отбрасываются.
252 = 6 × 42. Каждой грани достаётся ровно одинаковое число байтов, значит каждая — ровно одна шестая.
Где доказательство заканчивается
Два вида костей на этом сайте цепочкой не покрыты. Они названы здесь, а не опущены: утверждение о честности с тихой дырой стоит меньше, чем отсутствие утверждения.
- TelegramКости, брошенные в чате ботаЕсли бросить собственной анимацией костей Telegram, это число приходит от Telegram, а не от нас; никакое наше зерно его не воспроизводит. Опубликованное доказательство перечисляет ровно эти позиции, а журнал ставит там знак Telegram вместо грани. Для полностью проверяемой игры бросайте в приложении.
- ТренировкаИгры против компьютераОфлайновая тренировочная игра бросает кости на вашем устройстве, без зерна и без доказательства. Нет соперника и нет ставки — нечего доказывать и нечем рисковать.
Всё остальное — онлайн 1v1, комнаты с друзьями и турниры, бесплатные или на ставку — работает на описанной выше цепочке.
Та же проверка, без этой страницы
Двенадцать строк и ни одной библиотеки. Вставьте их в консоль любого браузера вместе с тремя значениями игры — и получите ровно то же, что говорит эта страница. Ради этого метод и публикуется, а не только результат.
const enc = new TextEncoder();
const hex = (b) => [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, '0')).join('');
// 1. the seed we revealed must hash to the commitment you were given first
const commitOk = hex(await crypto.subtle.digest('SHA-256', enc.encode(serverSeed))) === commit;
// 2. every die, re-derived from that seed
const key = await crypto.subtle.importKey(
'raw', enc.encode(serverSeed), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
async function die(n) {
for (let round = 0; round < 16; round++) {
const mac = new Uint8Array(
await crypto.subtle.sign('HMAC', key, enc.encode(`${clientSeed}:${n}:${round}`)));
for (const b of mac) if (b < 252) return (b % 6) + 1;
}
}Каждое доказательство открыто по адресу /fair/<id> — без ключа, без аккаунта, без имён игроков и без сумм. Только кости.
Вопросы, которые задают на самом деле
Я проиграл четыре игры подряд. Кости против меня?+
Не могут быть. Каждая кость завершённой игры была предопределена до её начала зерном, зафиксированным заранее, и все их можно пересчитать выше. Серии кажутся адресными, но таковыми не являются: с двумя костями соперник обыгрывает вас четыре игры подряд куда чаще, чем принято думать.
Может ли сервер выбрать бросок в решающий момент?+
Ему пришлось бы сломать одну из двух проверок. Либо он публикует зерно, хеш которого не совпадает с отпечатком, выданным до игры, — это видно каждому, — либо публикует настоящее, и тогда кости, которые оно порождает, и есть те, которыми вы играли.
А если моё приложение не отправило своё зерно?+
Вместо него генерируется случайное, чтобы цепочка всегда была корректной. Это ослабляет только ваш собственный вклад; фиксация и все проверки выше остаются в силе.
Влияет ли раскрытое зерно на мою следующую игру?+
Нет. Для каждой игры, включая матч-реванш, берётся новое зерно и публикуется новый отпечаток. Опубликованное зерно больше никогда не определяет броски.
Почему на некоторых костях знак Telegram?+
Они брошены анимацией костей Telegram в чате бота, то есть пришли от Telegram, и никакое наше зерно их не воспроизводит. Они помечены, а не скрыты, и число, которое наша цепочка дала бы на этой позиции, намеренно не показано — это не та кость, которую кто-то бросал.