Для обеспечения прозрачности и доказуемой честности мы сгенерировали цепочку из 10 миллионов хэшей SHA256 для каждой мультиплеерной игры, начиная с серверного секрета, результат которого 10 миллионов раз повторно подавался обратно в SHA256.
SHA256 конечного хэша для Platform: eeb4d340bfaed702601b9a08faba2f8088c4810c3e3a883293accaf25053b883
SHA256 конечного хэша для Crash: c46f0705f6ba4df891ce44accda8f89f5a6fa8b987eb7c7b445280cf6cabfbc6
SHA256 конечного хэша для Battle: 63cb1eb64ec4eb4de02f6bad85e42365d08f37497a1b7e907bd0b9183f27e1b3
Публикуя это здесь, мы исключаем возможность выбора альтернативной цепочки SHA256. Теперь игровой сервер проходит через данную цепочку хэшей в обратном порядке, используя данные значения для расчета результатов игры доказуемо честным образом.
1. Секретный начальный хэш: Каждый тип мультиплеерной игры имеет свой собственный приватный начальный хэш (известный только серверу), формирующий корень уникальной хэш-цепочки.
2. Генерация хэш-цепочки: Из начального секретного хэша мы генерируем цепочку из 10 000 000 хэшей путем многократного применения SHA256.
func HashSeed(seed string) string {
hash := sha256.Sum256([]byte(seed))
return hex.EncodeToString(hash[:])
}
func CalculateHashChain(firstHash string) []string {
hashes := make([]string, 10_000_000)
for i := int64(0); i <= 10_000_000; i++ {
firstHash = HashSeed(firstHash)
hashes[i] = firstHash
}
return hashes
}
3. Обратное потребление: Мы раскрываем публике конечный хэш в цепочке. Каждый игровой раунд использует хэши в обратном порядке: Раунд 1 использует хэш[10_000_000] Раунд 2 использует хэш[9_999_999] и так далее…
4. Данная конструкция делает невозможным подделку будущих хэшей, позволяя при этом любому проверить все предыдущие хэши.
Непредсказуемость: Будущие игровые результаты не могут быть известны или подвержены влиянию, даже со стороны сервера.
Проверяемость: Игроки могут проверить целостность любого предыдущего раунда, используя только публично раскрытый конечный хэш.
Изоляция игр: Каждый тип игры имеет свою собственную цепочку, предотвращая манипуляции между играми.
Для обеспечения доказуемой честности в одиночных играх наша платформа использует систему, основанную на трех основных компонентах для генерации результирующего сида:
1. Клиентский сид: Это сид, контролируемый пользователем. Он автоматически генерируется при регистрации пользователя, но пользователь может изменить его в любое время. Изменение клиентского сида инициирует генерацию нового серверного сида.
2. Серверный сид: Он генерируется сервером с использованием внутренних параметров, которые не раскрываются пользователю. Он уникально привязан к текущему клиентскому сиду. Для каждого клиентского сида существует соответствующий серверный сид. Когда пользователь меняет клиентский сид, предыдущий серверный сид раскрывается для целей верификации.
3. Nonce: Уникальное число, основанное на временной метке ставки (в миллисекундах). Оно обеспечивает то, что даже повторяющиеся действия дают разные результаты.
Данные три значения объединяются и хэшируются с использованием HMAC-SHA256 для генерации конечного игрового сида, который затем используется для определения результата игры.
func GenerateUserGameSeed(userSeed string, userServerSeed string, nonce int64) (string, error) {
data := fmt.Sprintf("%s:%s:%d", userServerSeed, userSeed, nonce)
mac := hmac.New(sha256.New, []byte(data))
gameSeed := hex.EncodeToString(mac.Sum(nil))
return gameSeed, nil
}
Сервер предоставляет хэшированную версию текущего серверного сида до совершения какой-либо ставки.
После того как пользователь изменяет свой клиентский сид, ранее использовавшийся серверный сид раскрывается.
Это позволяет пользователям проверить, что все результаты, сгенерированные с предыдущим серверным сидом, были последовательными и честными.