您链接到的页面描述了该过程,但是我将尝试更详细地介绍并提供 C# 示例。
首先发生了两个散列。一个通用散列来证明服务器在您赌博时没有更改服务器密钥,该散列不是秘密的,并且在游戏开始时提供给玩家。还有一个键控散列(称为 HMAC)来实际生成掷骰子,并使用服务器密钥、用户提供的数据和一个递增的数字的组合。
这是发生的过程:
- 服务器为播放会话生成密钥并将计数器设置为 0。
- 在密钥上使用SHA256 来生成散列,这个散列被提供给玩家。此哈希值不会在任何数学运算中用于生成掷骰子,它仅用于玩家验证。
- 玩家请求掷骰子并提供用于生成数字的短语。
- 服务器使用 SHA512-HMAC,使用密钥作为密钥,然后是用户提供的字符串加上“-”加上第 1 步中设置的计数器编号以生成哈希。
- 服务器将计数器加 1,这样做是因为每次都使用相同的服务器密钥,如果使用相同的用户字符串,它只会一遍又一遍地生成相同的数字。
- 服务器获取生成的哈希的前 21 位,将其转换为
int,然后检查 int 是否大于 999999,如果它不断重复,直到找到一个数字不超过 999999。
- 它从第 6 步获取数字并对其执行
number%(10000)/100.0 以获得浮点数。
- 该浮点数返回给用户。
- 要么从第 3 步开始重复新卷,要么继续第 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 返回给用户的密钥,用户可以重新创建所有掷骰子并证明服务器确实进行了随机生成(感谢用户在卷中提供的数据,服务器不允许选择),而不是一些保证会导致丢失的伪造的预选密钥。