I made a provably fair 1v1 coin flip game in Laravel and I want to be sure it's safe (cannot be hacked) and that the outcome is random (50/50).

At first Player 1 starts the bet and the hash will be created. The hash is a SHA256 of the $secret, which will be revealed to the players after the game. This hash ensures that the game outcome depends on the client and not on the server (provably fair). Does it matter which hash type I use? Are they all equally safe?

$secret = str_random(40);
$bet = new Bet;
$bet->user_id = Auth::id();
$bet->amount = $amount;
$bet->secret_key = $secret;
$bet->secret_hash = hash('sha256', $secret);

So after that, player 2 will be able to join player 1's bet. When player 2 accepts the bet, this is what happens:

$(document).delegate('.flipcoin-btn', 'click', function() {
    // When player 2 clicks the button
    SubmitFlipCoin($(this).attr('id'), CreateClientKey());

function CreateClientKey()
    var outStr = "", newStr;
    while(outStr.length < 20)
        newStr = Math.random().toString(36).slice(2);
        outStr += newStr.slice(0, Math.min(newStr.length, (20 - outStr.length)));
    console.log("Client string created: "+outStr.toUpperCase());
    return outStr.toUpperCase();

function SubmitFlipCoin(id, client)
    StopMoneyUpdate = 1;

        method: 'POST',
        url: flipcoin_link,
        data: {id: id, clientstr: client, _token: token},
        dataType: 'JSON',
    .done(function(result) {
        if(result.hasOwnProperty("error")) {
    .fail(function(error) {
        ShowBetError('<b>An error occurred while flipping the coin, please try again.</b>');

As you can see, the client (player 2) generates a random string, which will be send to the server. The server will then make an HMAC hash of bet_id+clientstring with the $secret as key. The outcome of the game is determined by this HMAC hash. It takes the second number of the HMAC hash. If the result is 0 to 4 then player 1 wins, and if result is 5 to 9 player 2 will win.

$resulthash = hash_hmac('sha512', $request->clientstr."-".$bet->id, $bet->secret_key);
$resultnumber = filter_var($resulthash, FILTER_SANITIZE_NUMBER_INT);
if($resultnumber[1] < 5) {
    $result = 0;
else {
    $result = 1;

EDIT: I did some testing to see if the outcome is really 50/50 and it seems pretty accurate.

$dealer = 0;
$player = 0;
$times = 1000000;

for($i = 0; $i < $times; $i++) {
    $result = filter_var(hash('sha512', rand()), FILTER_SANITIZE_NUMBER_INT)[1];
    if($result < 5) {
    else {

echo "Dealer: ".$dealer/$times * 100;
echo "</p>Player: ".$player/$times * 100;

This was the outcome that I got:

Dealer: 49.9891 Player: 50.0109

  • \$\begingroup\$ You running a million iterations and saying it looks about fair does not prove that it is fair and you said in the question title you wanted this to be provable. Why not take advantage of the years of research and development in random number generation and use the built in cryptographically secure random number source like random_int() \$\endgroup\$ – Chris Baldwin Aug 5 '17 at 18:41

So why are you saving both the $secret and the hash of the $secret in the DB?

$bet->secret_key = $secret;
$bet->secret_hash = hash('sha256', $secret);

Also how does the hash of the $secret allow you to ensure that the outcome is fair? Do you ever use this hash anywhere?

Also carrying out any critical operations that you expect to remain secure on a client that allows someone to view and alter the code via a console cannot be considered secure. Basically the game should be run entirely on the server with input from the clients, such as the bet amount and bet acceptance, and then the results can be distributed to the clients for display.

Do you have some mathematical proof showing that the distribution of numbers in the second position of a string generated from a HMAC hash after non-number characters are removed is actually random? If not then you cannot assert that this method is truly random and would give you the 50/50 chance you desire.

Since PHP already has a built int cryptographically secure pseudo-random integer generator, int random_int ( int $min , int $max ), why don't your just use that to generate you numbers between 0 and 9?

  • \$\begingroup\$ The hash of the secret and the id of the game are shown to the player before accepting the bet. When the bet ends, the secret is also shown. With this secret key, people can see that the server did not cheat, because the bet outcome was influenced by the client. I don't see why it's unsafe to let the client alter the code. If the player messes with the code, e.g. sending an empty client string, the outcome would still be fair. I have no proof that the 2nd char of a HMAC will give a 50/50 outcome. How can I make it 50/50? Is there an other method to make the game 100% provably fair? \$\endgroup\$ – FriedChicken Aug 5 '17 at 17:02
  • \$\begingroup\$ From the documentation for PHP's random_int() function: "Generates cryptographic random integers that are suitable for use where unbiased results are critical, such as when shuffling a deck of cards for a poker game." Using this to generate you flips would be a good start to guaranteeing fairness. \$\endgroup\$ – Chris Baldwin Aug 5 '17 at 18:46
  • \$\begingroup\$ Using random_int() means that the server will decide who wins and I don't think there's a way to make this provably fair. Isn't it worse if the server can make you lose on purpose? \$\endgroup\$ – FriedChicken Aug 5 '17 at 19:20
  • \$\begingroup\$ Your server is already determining who wins. You are taking information generated by the server (bet id and secret), combining it with something from one of the clients, processing it and hoping that the result is random. In fact the majority of the data you are putting into the hash function to determine who wins comes from the server. \$\endgroup\$ – Chris Baldwin Aug 5 '17 at 20:02
  • \$\begingroup\$ No because the string that the client sends determines who wins. The only problem is that I'm not sure if the outcome is 50/50. Is there something to create a "random" number based on a string? \$\endgroup\$ – FriedChicken Aug 5 '17 at 20:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.