Skip to content

Instantly share code, notes, and snippets.

@4Kaylum
Last active March 4, 2022 02:16
Show Gist options
  • Save 4Kaylum/6bbb3dcd9ebb0545ba8e692391d5fedd to your computer and use it in GitHub Desktop.
Save 4Kaylum/6bbb3dcd9ebb0545ba8e692391d5fedd to your computer and use it in GitHub Desktop.
Implementation of the Primedice provably fair algorithm (https://dicesites.com/primedice/verifier)
import random
import secrets
import hashlib
import hmac
class ProvablyFair(random.Random):
"""An object that represents a provably fair algorithm"""
def __init__(self, client_seed:str=None, *, server_seed:str=None):
self.valid = True
self.nonce = -1
self.client_seed = client_seed if client_seed else secrets.token_hex(10)
if not server_seed:
data = self.generate_server_seed()
else:
data = self._hash_server_seed(server_seed)
self._server_seed, self.server_seed_hash = data
self.last_rolled_data = None
@classmethod
def verify_roll(cls, server_seed, server_seed_hash, client_seed, nonce, roll):
"""Verifies that a roll passed successfully
Returns True if all was well, otherwise raising AssertionError"""
instance = cls(client_seed, server_seed=server_seed)
instance_roll = instance.random(nonce=nonce)
instance_data = instance.last_rolled_data
assert instance._server_seed == server_seed
assert instance.server_seed_hash == server_seed_hash
assert instance_data['nonce'] == nonce
assert instance_data['roll'] == roll
return True
@staticmethod
def generate_server_seed():
"""Generates a new server seed to use for future rolls"""
server_seed = secrets.token_hex(20)
server_seed_hash_object = hashlib.sha256(server_seed.encode())
server_seed_hash = server_seed_hash_object.hexdigest()
return server_seed, server_seed_hash
@staticmethod
def _hash_server_seed(server_seed):
"""Hashes and stores a given server seed"""
server_seed_hash_object = hashlib.sha256(server_seed.encode())
server_seed_hash = server_seed_hash_object.hexdigest()
return server_seed, server_seed_hash
def invalidate(self):
"""Marks this instance as invalid and returns the server seed to you"""
self.valid = False
return self._server_seed
def random(self, *, nonce=None):
"""Generates a provably random number given a client seed and a
pre-generated server seed hash"""
# Make sure it's still valid
if not self.valid:
raise Exception('This server seed is no longer valid.') # TODO provide custom exception
# Get a nonce to use
if nonce:
self.nonce = nonce
else:
self.nonce += 1
# HMAC it up
msg_str = '{}-{}'.format(self.client_seed, self.nonce)
key = self._server_seed.encode()
msg = msg_str.encode()
dig = hmac.new(key, msg=msg, digestmod=hashlib.sha512)
# Get the number
full_number = dig.hexdigest()
counter = 0
while True:
number_str = full_number[counter:counter+5]
number = int(number_str, 16)
if number > 999_999:
counter += 5
else:
break
# Return results
roll = (number % (10**4)) / 100
self.last_rolled_data = {
'roll': roll,
'nonce': self.nonce,
'server_seed_hash': self.server_seed_hash,
'client_seed': self.client_seed
}
return roll / 100
def seed(self, *args, **kwds):
"""Stub method. Not used for provably fair systems, since the seed is passed on initialization"""
return None
def getstate(self):
return self.nonce, self.client_seed, self._server_seed
def setstate(self, state):
self.nonce, self.client_seed, self._server_seed = state
def _randbelow(self, n):
return int(self.random() * n)
@fadedmax
Copy link

sick

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment