A raffle is a gambling game, where players buy tickets; a winning ticket is randomly picked and its owner gets the jackpot prize.
The Michelson language does not provide an instruction to generate a random number. We can't use the current date (value of
now) as a source of randomness either. Indeed, bakers have some control on this value for the blocks they produce, and could therefore influence the result.
The source code of the raffle contract and the orresponding test scenario are available in this repository.
Picking the winning ticket
The winning ticket id is obtained as the remainder of the euclidean division of an arbitraly large number, called here the raffle key, by the number of ticket buyers, called here players. For example, if the raffle key is 2022, and the number of raffle players is 87, then the winning ticket id is 21 (typically the 21st ticket).
The constraint is that this raffle key must not be known by anyone, nor the players or even the admin. Indeed if someone knows in advance the raffle key, it is then possible to influence the outcome of the game by buying tickets until one of them is the winning one (there is only one ticket per address, but someone can have several addresses). As a consequence:
- the raffle key cannot be simply stored in the contract.
- the raffle key cannot be a secret that only the admin knows (for the reason above), and that the admin would pass to the contract when it is time to announce the winner. Indeed, the admin could disappear, and no winner would ever be announced.
For the admin not to be the only one to know the key, each player must possess a part of the key (called here partial key), such that the raffle key is the sum of each player's partial key. For the player's partial key not to be known by the other players, it must be cyphered by the player. When it comes to selecting the winning ticket, the user is required to reveal its key for the contract to compute the raffle key.
However, a player could influence the outcome by not revealing the partial key. It is then necessary that the encrypted partial key can be decrypted by anyone at some time. A reward is sent to the account that reveals a key.
The timelock encryption feature of the Michelson
chest data type provides the required property: a timelocked value is encrypted strongly enough that even the most powerful computer will take more than a certain amount of time to crack it, but weakly enough that given a bit more time, any decent computer will manage to crack it. That is to say that, beyond a certain amount of time, the value may be considered public.
Raffle key chest
In this tutorial example, the chest time parameter imposed by the contract is
Note that it's probably not a decent chest time value since it takes only 20s to break on a standard computer ...
Generate chest value
Player Alice's partial key is
123456, Player Jack's is
234567, and Player Bob is
To get the timelocked value, the value is first packed (turned into bytes) with the following tezos client command
We then use the Completium timelock-utils tool to timelock the packed data:
The timelock encryption generates a chest value, and the key to unlock it.
In the test scenario, Bob generates the chest value with the wrong time value
10000001. As a result, the call to
reveal removes Bob as a player.
The following command is used to compute the chest key (ie. crack chest):
The contract is originated with the following parameters:
owneris the address of the contract administrator
jackpotis the prize in tez
ticket_priceis the price in tez of a ticket
The contract holds:
- a state with 3 possible values:
Createdis the initial state during which tickets cannot be bought yet
Initialisedis the state when the administrator initialises the raffle
Transferredis the state when prize has been transferred to the winner
- the open date beyond which tickets can be bought, initialized to
- the date beyond which tickets cannot be bought, initialized to
The schema below illustrates the periods defined by these dates, and the contract's states:
The contract also holds:
- the reveal fee, initialized to
- the time used to generate the timelocked value of the raffle key (it should be high enough to be compliant with the close date), initialized to
- a collection that will contain the addresses of all players and their raffle key:
- the raffle key, updated when a player's partial key is revealed:
initialise entrypoint is called by the contract admin (called "owner") to set the main raffle parameters:
- open buy is the date beyond which players can buy ticket
- close buy is the date beyond which players cannot buy ticket
- chest time is the difficulty to break players' partial raffle key encryption
- reveal fee the pourcentage of ticket price transferred when revealing a player's raffle key
Currently you may count from a chest time of 500 000 per second on a standard computer, to a chest time value of 500 000 000 per second on dedicated hardware.
It requires that:
- the open and close dates be consistent
- the reveal fee be equal to or less than 1
- the transferred amount of tez be equal to the
It transitions from
Created state to
Initialised, and sets the raffle parameters.
buy entrypoint may be called by anyone to buy a ticket. The player must transfer the encrypted value of the partial raffle key, so that the partial key value may be potentially publically known when it comes to declaring the winner ticket.
It requires that:
- the contract be in
- the transferred amount of tez be equal to the ticket price
- the close date not be reached
It records the caller's address in the
Note that the
add method fails with
(Pair "KeyExists" "player") if the caller is already in the collection.
reveal entry point may be called by anyone to reveal a player's partial key and contribute to the computation of the raffle key. The caller receives a percentage of the ticket price as a reward.
It requires that:
- the contract be in
- the date is valid is beyond
Note that the player
addr may be removed in 2 situations:
- the chest key opens the chest but is unable to decypher the content; this is the case if for example the chest was not generated with the correct chest time value
- the chest is decyphered properly, but it does not contain an integer value
Note at last that in all cases, the caller is rewarded for the chest key when it is valid.
When all players have been revealed, anyone can call the
transfer entrypoint to transfer the jackpot to the the winning ticket. It transitions to