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

问题描述:

我查看了可证明的公平随机数,并且遇到了这个网站:https://dicesites.com/provably-fair如何在C#中生成可证明的公平骰子卷?

首先,应该使用什么类的服务器双面散列?有很多像SHA512,SHA256或SHA384Cng这样的散列算法,我不明白它们之间的区别。

其次,将使用什么方法将未经哈希的种子转换为经哈希的种子,以及在哈希创建过程中将使用何种方法将用户提供的种子字符串考虑在内。另外,是否在用户提供的字符串末尾添加了nonce以防止重复的哈希?

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

最后,将最后生成的散列的前5个字符转换为卷号的操作是什么?

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

+4

请问每个问题* 1 *问题,而不是4。请参阅[Meta](https://meta.stackexchange.com/questions/39223/one-post-with-multiple-questions-or-multiple-posts)了解更多信息。 – Amy

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

    } 
} 

使用有关algorthom使用,并给予公开信息,通过RollDice返回的信息的关键用户从EndSession返回,用户可以重新创建所有骰子卷,并证明服务器确实做了一个随机生成(感谢用户提供的数据,服务器不允许选择),而不是一些伪造的预选k这是保证造成损失。

+0

谢谢!这有很大的帮助,但我想知道如何在我链接的网站上使用相同类型的字符串种子,这样可以很容易地为人们验证,而不必在我的代码中使用字节数组。 –

+0

我生成在末尾的示例useage中的字符串种子'Console.WriteLine(string.Join(“”,key.Select(x => x.ToString(“X”))));' –

+0

@ScottChamberlain ToString是有损的。 0x0304变成了“34”。你想'x.ToString(“X2”)'保持前导0。 – bartonjs