Приветствую читатели! В данном посте я поверхностно расскажу о доказуемой честности в казино, а так-же о Proof Of Liabilities.
О опыте
Не так давно я начал интересоваться работой гэмблинг рынка, а именно казино. У многих людей ошибочное мнение о работе онлайн-казино. Многие представляют себе казино как сервис, где людей нагло обманывают, все игры подкручивают и выиграть там нереально, но в этой статье я хочу рассказать подробнее о возможностях как обычный игрок может проверить свою ставку или средства на честность.
Доказуемая честность
Данное понятие подразумевает в себе, что казино никак не может повлиять на исход игры и до момента совершения ставки исход детерминирован.
Данная схема содержит в себе четыре части:
Server Seed
Client Seed
Nonce
Cursor
Давайте подробнее о каждой части
Server Seed - случайное число, которое сгенерировало казино. Игрок перед совершением ставки видит лишь хэш данного числа.
Client Seed - случайное число, которое генерирует юзер. Он может его менять в любой момент до совершения ставки.
Nonce - число, которое увеличивается с каждой ставкой. При сбросе server seed`a как правило становится 0.
Cursor - число, нужное если исход игры требует больше чисел, например для слотов. Если игра требует 1 число, то мы используем значение 0 в качестве курсора, если исход игры требует больше 8 чисел, то мы можем увеличить курсор на единицу.
Собрав все нужные нам данные мы можем получить 256 битное число, для этого используют HMAC_SHA256(server_seed + client_seed + nonce + cursor)
Получив 32 байта, мы можем их конвертировать в 8 чисел с плавающей запятой.
Имплементация данного алгоритма:
func GenerateFloats(bytes []byte) []float64 {
var result []float64
for i := 0; i < len(bytes) / 4; i++ {
var res float64
for j := 0; j < 4; j++ {
divider := math.Pow(256, float64(j+1))
partialResult := float64(bytes[(i*4)+j]) / divider
res += partialResult
}
result = append(result, res)
}
return result
}
Таким образом мы можем далее это конвертировать в результат для какой-либо игры, допустим вытащить карту:
// Index of 0 to 51 : ♦2 to ♣A
const CARDS = [
♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4,
♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6,
♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9,
♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J,
♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A,
♥A, ♠A, ♣A
];
// Game event translation
const card = CARDS[Math.floor(float * 52)];
Proof Of Liabilities
Данный алгоритм может применяться не только в гэмблинге, а так-же в централизованных криптобиржах(CEX), и других сервисах, принимающих депозиты.
За пример я взял данную имплементацию на JS, и расскажу о ней поверхностно.
Представим биткоин казино, которое принимает депозиты на горячий кошелек, далее данные биткоины уходят на холодный кошелек казино. В итоге мы получаем кошелек с балансом 100 биткоинов, но юзер пополнивший 5 биткоинов не может быть уверен, что его средства хранятся на данном холодном кошельке и что казино в любой момент может выплатить ему назад его 5 биткоинов. Вдруг сумма всех хранимых средств меньше суммы балансов игроков?
На помощь нам приходит алгоритм PoL. Данный алгоритм строится с помощью Merkle tree и казино или биржа может раз в 24 часа генерировать данное дерево заново и показывать root узел всем пользователям.
{"root":{"sum":"87.19198928","hash":"2f88e5473199aaacb6f2f60b8d93a11862426902aeb31c0198c8f6c4156329dd"},"currency":"BTC","timestamp":1670972401975}
О реализации
В данном примере я расскажу о имплементации с репозитория выше, но данных можно вносить больше.
Листья в данном дереве будут юзеры, а внутренние узлы - промежуточный баланс юзеров ниже.
Информация о листе(юзере)
Name - имя юзера, его id
Nonce - рандомное число, сгенерированное оператором, доступно только юзеру. Нужно чтоб его сосед не смог узнать name.
Sum - баланс юзера
Hash - SHA256(name + '|' + sum + '|' + nonce)
Промежуточный узел
Sum - баланс промежуточного узла (сумма левого и правого потомка)
Hash - SHA256(sum + '|' + left_hash + '|' + right_hash)
function NodeCombiner (left_child, right_child) {
var n = {};
n.sum = left_child.sum + right_child.sum;
n.hash = sha256(string(n.sum) + '|' + left_child.hash + '|' + right_child.hash);
return n;
}
Итоговый узел(root node)
Содержит как и внутренний узел Sum и Hash
Поиграться с данным алгоритмом можно тут
Плюс данного алгоритма в том, что не нужно кол-во юзеров кратным 2^n.
Юзер может проверить находится ли его средства в общей массе. Для этого ему достаточно предоставить свои данные и запросить у оператора промежуточные узлы дерева.
В завершение
Хочу сказать, если бы данные алгоритмы популяризовали в массы, то мы бы могли избежать множества скама с стороны казино и централизованных бирж(например крах FTX).