⚡️ Lightning Lotto

Provably fair lotto-style lottery

Draw

Each round has a predetermined close height, a predetermined draw height and a predetermined lock height. They are normally six blocks apart from each other. When the Bitcoin blockchain reaches the close height, the lottery will close and no more tickets will be sold. At draw height, the winning sequence will be generated using a combination of a secret seed (random seed, see below) and the block hash of the block at that height. Until lock height is reached, if there would be any reorganization of the blockchain, the winning sequence will simply be regenerated. At lock height, the winning sequence is locked-in and final, is made public here together with random seed, and winnings can be claimed.

Random seed

Each round has a predetermined random seed generated by me (random seed). It is kept secret until draw time, but the sha256 hash of it (seed hash) is publicly available and is included on each ticket, as a guarantee it has not changed.

Tickets

In the interest of decentralization and Don't trust. Verify, the tickets sold are self-contained and not stored here. The validity is guaranteed by a signature that is created by LND and can be checked against my node's public key. (The public key is made public together with the other parameters for the current round on the front-page). Tickets without a valid signature can not be used to claim winnings. Each ticket is unique (has a unique ticket-ID) and can only be used once.

Details

The generation of the winning sequence works like this: The random seed (a string of random bytes) is concatenated with the block hash for the block at height draw height in binary format and the result it hashed with sha256. The hash is converted to hex format and the rightmost 4 characters of that string that falls within the range '0' – '9' (ASCII 48 – 57) will be the winning sequence. Any other characters are ignored. In the unlikely case that there would not be enough such characters, we increase draw height by one and start over. The height of the block we actually used will be called actual draw height and will in practice always be the same as draw height. In case it is not, lock height will also be adjusted so the separation stays the same.

Python code

Generate winning sequence:

sequence_length = 4
actual_draw_height = draw_height

if isinstance(random_seed, str):
    random_seed = bytes.fromhex(random_seed)

while True:
    block_hash = bytes.fromhex(blockhash_for_block(actual_draw_height))

    sha256 = hashlib.sha256()
    sha256.update(random_seed)
    sha256.update(block_hash)

    winning_value = sha256.digest()

    sequence = []
    for c in winning_value.hex():
        if int(c, 16) < 10:
            sequence.append(int(c, 16))
        if len(sequence) == sequence_length:
            return actual_draw_height, sequence

    actual_draw_height += 1
      

Calculate seed hash from random seed:

if isinstance(random_seed, str):
    random_seed = bytes.fromhex(random_seed)

sha256 = hashlib.sha256()
sha256.update(random_seed)

seed_hash = sha256.digest()
print(seed_hash.hex())