如何用C#生成可证明公平的骰子卷?

时间:2017-06-12 21:09:43

标签: c# random hash cryptography sha

我查看了相当公平的随机数字,并且发现了这个网站:https://dicesites.com/provably-fair

首先,什么类应该用于服务器端哈希?有很多哈希算法,如SHA512,SHA256或SHA384Cng,我不明白它们之间的区别。

其次,将使用什么方法将未散列种子转换为散列种子,以及在散列创建期间将用于将用户提供的种子的字符串用于何种方法。另外,是否只是在用户提供的字符串末尾添加了nonce以防止重复哈希?

第三,我不明白为什么散列服务器种子最初是SHA256散列,但后来用于计算HMAC SHA512散列。

最后,将用于将最终生成的哈希的前5个字符转换为卷号的内容是什么?

我没有找到使用服务器种子和客户端种子的任何随机数生成器的任何示例,只有像System.Security.Cryptography.RandomNumberGenerator这样的东西。

1 个答案:

答案 0 :(得分:3)

您链接的页面描述了该过程,但我将尝试深入了解更多详细信息并提供C#示例。

首先有两个哈希发生。一个通用哈希来证明服务器在你赌博时没有改变服务器密钥,这个哈希不是秘密的,并且在游戏开始时被提供给玩家。还有一个键控哈希(称为HMAC)来实际生成骰子卷,并使用服务器密钥,用户提供的数据和计数的数字的组合。

以下是发生的过程:

  1. 服务器为播放会话生成密钥并将计数器设置为0.
  2. SHA256用于生成哈希的密钥,此哈希值将提供给播放器。任何数学运算都不会使用此哈希来生成骰子卷,它仅用于玩家的验证。
  3. 玩家请求掷骰子并提供用于生成数字的短语。
  4. 服务器使用SHA512-HMAC使用密钥作为密钥,然后使用用户提供的字符串加“ - ”加上步骤1中设置的计数器编号以生成散列。
  5. 服务器将计数器递增1,这样做是因为每次都使用相同的服务器密钥,如果使用相同的用户字符串,它只会反复生成相同的数字。
  6. 服务器获取生成的哈希的前21位,将其转换为int,然后检查int是否大于999999,如果是,它会一直重复直到找到一个不超过999999的数字。
  7. 从第6步开始,在number%(10000)/100.0上获取浮点数。
  8. 将浮点数返回给用户。
  9. 从第3步开始重复新卷或继续执行第10步。
  10. 玩家发出播放会话结束的信号。服务器将密钥返回给用户,并在步骤1重新启动。
  11. 用户一旦从步骤10获得密钥,就可以使用SHA256对其进行散列,并检查他是否获得了在播放会话开始时告知的相同哈希值。然后,他可以重新执行服务器现在执行的所有步骤,即他拥有密钥并验证服务器是否没有伪造任何骰子。

    如何在代码中执行此操作:

    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("X2")));
                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;
            }
        }
    }
    

    使用中的示例

    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("X2"))));
                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("X2"))));
                Console.ReadLine();
            }
    
        }
    }
    

    使用已发布的有关所使用的algorthom的信息,RollDice返回的信息以及从EndSession返回给用户的密钥,用户可以重新创建所有骰子卷并证明服务器真正做到了随机生成(感谢用户提供的数据,服务器不允许选择),而不是一些预先确定会导致丢失的伪造预选密钥。