【问题标题】:How to generate provably fair dice rolls in C#? [closed]如何在 C# 中生成可证明公平的掷骰子? [关闭]
【发布时间】:2017-11-14 11:31:14
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 请每个问题问 一个 问题,而不是四个问题。请参阅Meta 了解更多信息。
  • 抱歉,我只是假设我可以将多个相关问题归为一组,这些问题都导致产生可证明公平的掷骰子。

标签: c# random hash cryptography sha


【解决方案1】:

您链接到的页面描述了该过程,但是我将尝试更详细地介绍并提供 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 步重新启动。

用户从第 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();
        }

    }
}

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

【讨论】:

  • 谢谢!这很有帮助,但我想知道如何使用与我链接的网站上相同类型的字符串种子,这样人们就可以轻松验证它,而不必在我的代码中使用字节数组
  • 我在最后的示例用法中生成字符串种子Console.WriteLine(string.Join("", key.Select(x =&gt; x.ToString("X"))));
  • @ScottChamberlain ToString 是有损的。 0x0304 只是变成“34”。您希望x.ToString("X2") 保持领先的 0。
  • @bartonjs 很好,谢谢!我确定了答案。
  • @OğuzhanKahyaoğlu 可证明的公平意味着服务器无法根据您猜测的更高或更低来更改其答案。您玩家可以证明在您进行第一次投注之前预先计算且无法更改的整个数字序列。密钥决定了结果的值,并且因为您在第一次下注之前获得了密钥的哈希值,您可以确定他们没有在您下注之后而是在您告诉结果让您放松之前更改密钥。至于神奇的数字,您必须询问在原始问题中提出算法的人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 2012-02-29
  • 1970-01-01
  • 2017-04-09
  • 1970-01-01
相关资源
最近更新 更多