John Landon John Landon -4 years ago 72
C# Question

How to generate provably fair dice rolls in C#?

I looked into provably fair random numbers, and came across this website: https://dicesites.com/provably-fair

First off, what class should be used for the server sided hash? There are so many hash algorithms like SHA512, SHA256 or SHA384Cng and I do not understand the difference between them.

Second off, what method would be used to convert from the unhashed seed to the hashed seed, and what method would be used to take the string for the seed provided by the user into account during the hash creation. Also, is the nonce simply added at the end of the string provided by the user in order to prevent duplicate hashes?

Third off, I do not understand why the hashed server seed is originally a SHA256 hash but is later used to calculate a HMAC SHA512 hash.

Lastly, what would be used to convert the first 5 characters of the final generated hash to a roll number?

I have had no luck in finding any examples of any random number generators that use a server seed and client seed, only things like

System.Security.Cryptography.RandomNumberGenerator
.

Answer Source

The page you link to describes the process, however I will try to go in to more detail and provide a C# example.

First there are two hashings that happen. One general hash to prove that the server is not changing the server key as you gamble, this hash is not secret and is given to the player at the start of play. There is also a keyed hash (called a HMAC) to actually generate the dice rolls and uses a combination of the server key, the data the user provided, and a number that counts up.

Here is the process that happens:

  1. The server generates a secret key for the play session and sets a counter to 0.
  2. SHA256 is used on the key to generate a hash, this hash is given to the player. This hash is not used in any math to generate the dice rolls, it is only used for verification by the player.
  3. The player requests a dice roll and provides a phrase to be used in the generation of the number.
  4. The server uses a SHA512-HMAC using secret key as the key then the string the user provided plus "-" plus the number of the counter set in step 1 to generate a hash.
  5. The server increments the counter by 1, this is done because the same server key is used every time and if the same user string was used it would just generate the same number over and over.
  6. The server takes the first 21 bits of the hash generated, converts it to a long, it then checks to see if the long is larger than 999999 if it is it keeps repeating till it finds a number that is not over 999999.
  7. It takes the number from step 6 and does number%(10000)/100.0 on it to get a floating point number.
  8. That floating point number is returned to the user.
  9. Either repeat starting at step 3 for a new roll or continue to step 10.
  10. The player signals the play session is over. The server returns the secret key to the user and restarts at step 1.

The user once he gets the secret key from step 10 can hash it using SHA256 and check that he gets the same hash he was told at the start of the play session. He can then re-do all the steps the server did now that he has the secret key and verify that the server did not fake any dice rolls.

How to do this in code:

using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace SandboxConsole
{
    public class Result
    {
        public Result(string hmacMessage, float roll)
        {
            HmacMessage = hmacMessage;
            Roll = roll;
        }

        public string HmacMessage { get; }
        public float Roll { get; }
    }

    class FairDiceRollServer
    {
        private byte[] _serverKey;
        private ulong _nonce;

        public byte[] StartSession()
        {
            if (_serverKey != null)
                throw new InvalidOperationException("You must call EndSession before starting a new session");

            //Generate a new server key.
            using (var rng = RandomNumberGenerator.Create())
            {
                _serverKey = new byte[128];
                rng.GetBytes(_serverKey);
            }
            _nonce = 0;
            //Hash the server key and return it to the player.
            using (var sha = SHA256.Create())
            {
                return sha.ComputeHash(_serverKey);
            }
        }

        public Result RollDice(string userKey)
        {
            if(_serverKey == null)
                throw new InvalidOperationException("You must call StartSession first");
            if(_nonce == ulong.MaxValue)
                throw new InvalidOperationException("Ran out of Nonce values, you must start a new session.");

            using (var hmac = new HMACSHA256(_serverKey))
            {
                float? roll = null;
                string message = null;
                while (roll == null)
                {
                    message = userKey + "-" + _nonce;
                    _nonce++;

                    var data = Encoding.UTF8.GetBytes(message);
                    var hash = hmac.ComputeHash(data);
                    roll = GetNumberFromByteArray(hash);
                }
                return new Result(message, roll.Value);
            }
        }

        private float? GetNumberFromByteArray(byte[] hash)
        {
            var hashString = string.Join("", hash.Select(x => x.ToString("X")));
            const int chars = 5;
            for (int i = 0; i <= hashString.Length - chars; i += chars)
            {
                var substring = hashString.Substring(i, chars);
                var number = int.Parse(substring, System.Globalization.NumberStyles.HexNumber);
                if(number > 999999)
                    continue;
                return (number % 10000) / 100.0f;
            }
            return null;
        }

        public byte[] EndSession()
        {
            var key = _serverKey;
            _serverKey = null;
            return key;
        }
    }
}

Example of it in use

using System;
using System.Linq;

namespace SandboxConsole
{
    class Program
    {
        private int _test;
        static void Main(string[] args)
        {
            var server = new FairDiceRollServer();
            var hash = server.StartSession();
            Console.WriteLine(string.Join("", hash.Select(x => x.ToString("X"))));
            for (int i = 0; i < 10; i++)
            {
                var roll = server.RollDice("My Key");
                Console.WriteLine("Message: {0} Result: {1}", roll.HmacMessage, roll.Roll);
            }
            var key= server.EndSession();
            Console.WriteLine(string.Join("", key.Select(x => x.ToString("X"))));
            Console.ReadLine();
        }

    }
}

Using the published information about the algorthom used, the information returned by RollDice and the key given to the user returned from EndSession the user can re-create all dice rolls and prove the server truly did a random generation (thanks to the user provided data in the roll the server was not allowed to choose) and not some faked pre-chosen key that is guaranteed to cause a loss.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download