Verify Fairness

PROVABLY FAIR

What does it mean?

The outcome of each lottery pool is calculated in a deterministic way given a random secret, the hash of a target BSC block, and an optional user-provided seed. A cryptographic hash of the random secret is shown at the beginning of each lottery pool before any stakes are made. By publishing the hash of the secret, we are verifying that the secret is not changed after stakes are made. By also incorporating the hash of a future BSC block that is unknowable at the beginning of the lottery and a user-provided seed, we are ensuring that it is not possible for us to choose the secret in such a way as to influence the outcome of the lottery.

ALGORITHM

At the beginning of each lottery, a new 32-byte secret is randomly chosen. The secret is kept private until the lottery is complete, but its hash is calculated using the BLAKE2b hash function with a digest size of 256 bits and shown to the user before any stakes are made.

When placing a stake, the user can provide an arbitrary string seed. The MD5 hash of this seed (or of an empty string if no seed is provided) will be used in the selection of prize winners. The user can also apply multipliers based on his wagered amounts and Rollbot traits that will increase the amount of RLB staked for the lottery.

After the target BSC block is mined and its hash is known, the lottery prizes are calculated and awarded to users who have placed stakes with following algorithm. First, the jackpot outcome is determined by taking the 256 bit BLAKE2b hash of the secret concatenated with the block hash and converting the first 8 bytes of that hash to a floating point number in between 0 and 1. If this number is less than the jackpot probability, the jackpot will be the first prize awarded with a prize id of 0, otherwise, no jackpot is awarded. The actual jackpot amount is determined by multiplying the total jackpot pool by the fraction calculated by dividing by the floating point jackpot outcome.

Prizes are then awarded to users by first getting a unique ordering of all stakes by sorting by their user_id and seed hash. A prize outcome is then calculated by taking the 256 bit BLAKE2b hash of the secret concatenated with the block hash and prize id and converting the first 8 bytes to a floating point number between 0 and 1. The prize outcome is multiplied by the total amount staked and the first user whose cumulative sorted stake amount exceeds this number is selected as the prize winner. This stake is then removed from consideration for any additional pool prizes, unless it is a team stake. Team stakes are only removed when the number of prizes the team has won equals the number of team members. This process is repeated for all prizes or until no stakes remain.

VERIFICATION EXAMPLE

After the outcome of a lottery pool has been revealed, the results can be verified with the following python code. It includes the calculation of the hashes for user-defined seeds for 'seed123' and an empty seed, the hash of the secret, the jackpot outcome and amount, and the selection of stakes for periodic prizes:

import hashlib
import math
import pyblake2

jackpot_fraction = 0.02
jackpot_probability = 0.1

target_block = b'2104518'
block_hash = b'000000000000000eb4e1776b5ee79fbaab9cb296540266ee42c169ef56f3f075'
secret = b'e92ee2f186b7b2cf7755d42023a329ba'

prizes = [
        {'prize_id': b'0', 'amount': 800000.0},
        {'prize_id': b'1', 'amount': 1250.0},
        {'prize_id': b'2', 'amount': 150.0}
        ]

# the staked amounts are the integer number of RLB cents before applying multipliers
stakes = [
        {
            'user_id': b'3b9c774e-f896-4ff9-b4de-6e5f2e469884',
            'seed_hash': b'9f5e07cf788b584a291e4c90fefdf7c3',
            'staked': 1500,
            'multiplier': 1.5,
            'team_users': 0
            },
        {
            'user_id': b'd52bbe44-989f-4212-acc3-83c9363650e7',
            'seed_hash': b'd41d8cd98f00b204e9800998ecf8427e',
            'staked': 100020,
            'multiplier': 3.0,
            'team_users': 5
            },
        {
            'user_id': b'c9ab9616-233e-45d5-9a67-43cc33b92aa9',
            'seed_hash': b'd41d8cd98f00b204e9800998ecf8427e',
            'staked': 95000,
            'multiplier': 1.0,
            'team_users': 0
            },
        {
            'user_id': b'8c6b8b64-6815-6084-0a3e-178401251b68',
            'seed_hash': b'd41d8cd98f00b204e9800998ecf8427e',
            'staked': 777700,
            'multiplier': 1.0,
            'team_users': 0
            }
        ]

print('seed_hash for 1st stake:', hashlib.md5(b'seed123').hexdigest())
print('seed_hash for 2nd stake:', hashlib.md5(b'').hexdigest())
print('secret_hash:', pyblake2.blake2b(secret, digest_size=32).digest().hex())


def bytes_to_uniform_number(xs: bytes) -> float:
    hash = pyblake2.blake2b(xs, digest_size=32).digest()
    return int.from_bytes(hash[:8], byteorder='little', signed=False) / float(2**64 - 1)


jackpot_outcome = bytes_to_uniform_number(secret + block_hash)
prizes[0]['amount'] = math.floor(100.0 * prizes[0]['amount'] * min(1.0, jackpot_fraction * jackpot_probability / jackpot_outcome)) / 100.0
print('Jackpot amount:', prizes[0]['amount'])
jackpot_won = jackpot_outcome < jackpot_probability
print('Jackpot won:', jackpot_won)
if not jackpot_won:
    prizes.pop(0)

for stake in stakes:
    stake['staked'] = round(100.0 * (stake['staked'] * stake['multiplier'] / 100.0))

stakes.sort(key=lambda x: (x['seed_hash'], x['user_id']))
for prize in prizes:
    if len(stakes) == 0:
        break
    total_staked = sum([x['staked'] for x in stakes])
    outcome = bytes_to_uniform_number(secret + block_hash + prize['prize_id'])
    outcome_stake = math.floor(outcome * total_staked)
    current_stake = 0
    for (i, stake) in enumerate(stakes):
        current_stake += stake['staked']
        if outcome_stake < current_stake:
            print(f"User {stake['user_id'].decode('ascii')} (is_team: {stake['team_users'] > 0}) won prize id {prize['prize_id'].decode('ascii')} for ${prize['amount']}")
            stakes.pop(i)
            break

With the given example, the output is:

seed_hash for 1st stake: 9f5e07cf788b584a291e4c90fefdf7c3
seed_hash for 2nd stake: d41d8cd98f00b204e9800998ecf8427e
secret_hash: 6806e9f8b63496fa53f494329257e90f55ff2b92454f2dcc172babac1e09feb8
Jackpot amount: 36460.13
Jackpot won: True
User 8c6b8b64-6815-6084-0a3e-178401251b68 (is_team: False) won prize id 0 for $36460.13
User d52bbe44-989f-4212-acc3-83c9363650e7 (is_team: True) won prize id 1 for $1250.0
User c9ab9616-233e-45d5-9a67-43cc33b92aa9 (is_team: False) won prize id 2 for $150.0

CURRENT SECRET HASH

b19d1b674f99a31bbde325292af9404c5376e0f1f32c37e07abfa8929c96b4a1

Last updated