如何在 C# 中生成可证明公平的掷骰子?

Joh*_*don 3 c# random hash cryptography sha

我研究了可证明公平的随机数,并发现了这个网站: https: //dicesites.com/provively-fair

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

其次,将使用什么方法从未散列的种子转换为散列的种子,以及在散列创建过程中将使用什么方法将用户提供的种子字符串考虑在内。另外,随机数是否只是简单地添加在用户提供的字符串末尾以防止重复哈希?

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

最后,如何将最终生成的哈希值的前 5 个字符转换为卷号?

我没有运气找到任何使用服务器种子和客户端种子的随机数生成器的示例,只有诸如System.Security.Cryptography.RandomNumberGenerator.

Sco*_*ain 5

您链接到的页面描述了该过程,但我将尝试更详细地介绍并提供 C# 示例。

首先发生了两次散列。一个通用散列,用于证明服务器在您赌博时没有更改服务器密钥,该散列不是秘密的,而是在游戏开始时提供给玩家的。还有一个密钥散列(称为 HMAC)来实际生成骰子,并使用服务器密钥、用户提供的数据和累加数字的组合。

这是发生的过程:

  1. 服务器生成播放会话的密钥并将计数器设置为 0。
  2. SHA256 用于密钥生成哈希值,该哈希值将提供给玩家。该哈希值不会在任何数学中用于生成骰子,它仅用于玩家验证。
  3. 玩家请求掷骰子并提供用于生成数字的短语。
  4. 服务器使用SHA512-HMAC,使用秘密密钥作为密钥,然后用户提供的字符串加上“-”加上步骤1中设置的计数器的数字来生成哈希。
  5. 服务器将计数器加 1,这样做是因为每次都使用相同的服务器密钥,并且如果使用相同的用户字符串,它只会一遍又一遍地生成相同的数字。
  6. 服务器获取生成的哈希值的前 21 位,将其转换为 a int,然后检查 是否int大于 999999,如果是,则不断重复,直到找到不超过 999999 的数字。
  7. 它获取步骤 6 中的数字并number%(10000)/100.0对其进行处理以获得浮点数。
  8. 该浮点数将返回给用户。
  9. 对于新卷,请重复从步骤 3 开始,或者继续执行步骤 10。
  10. 玩家发出游戏结束的信号。服务器将密钥返回给用户并在步骤 1 处重新启动。

用户从第 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用中的示例

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

    }
}
Run Code Online (Sandbox Code Playgroud)

使用有关所使用的算法的已发布信息,返回的信息RollDice以及从用户返回给用户的密钥EndSession可以重新创建所有骰子掷骰并证明服务器确实进行了随机生成(感谢用户在掷骰子中提供的数据)服务器不允许选择),而不是一些伪造的预选密钥,这肯定会导致损失。