Wie wird Provably Fair im Code implementiert?
Angenommen, das Spiel ist beendet und uns liegen der nicht gehashte Server-Seed, der Client-Seed und die Nonce vor – so funktioniert es.
Zur Generierung von Spielergebnissen sind drei wesentliche Schritte erforderlich.
byteGenerator (Generierung zufälliger Bytes)
generateFloats (Umwandlung von Bytes in Floats (Ziffern))
Floats zu Spielereignissen (Umwandlung von Floats in tatsächliche Ereignisse in Originalspielen)
ByteGenerator-Funktion als Zufalls- Byte- Generator
Die byteGenerator Funktion dient als Generator für zufällige Bytes.
Sie verwendet eindeutige Werte von clientSeed, serverSeed, nonce, und cursor um mithilfe der kryptografischen Funktion HMAC_SHA256 einen zufälligen und eindeutigen, mit SHA-256 gehashten Wert zu generieren.
Der generierte SHA-256-Wert ist 32 Bytes groß. Um ein Gleichgewicht zwischen einem ausreichend zufälligen Spielergebnis und dem Rechenaufwand zu gewährleisten, werden die 32 Bytes zur Generierung jedes Spielergebnisses in 8 Abschnitte mit jeweils 4 Bytes* aufgeteilt.
In bestimmten Spielen, in denen mehr als 8 Spielergebnis erforderlich ist, verwenden wir den cursor. Der Cursor beginnt zunächst bei 0 und wird auf 1,2,3,4 erhöht, um die Ergebnisanforderung zu erfüllen,
Bei Spielen, für die nicht mehr als 8 zufälliges Ergebnis erforderlich ist, wird der Wert des Cursors nicht erhöht.
*4 Bytes an Daten ergeben 2^32 (4,294,967,296) mögliche Ergebnisse, was einen ausreichend großen Pool für Zufälligkeit darstellt.
function* byteGenerator({ serverSeed, clientSeed, nonce, cursor }: ByteGeneratorInterface) {
// Setup cursor variables let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
// Generate outputs until cursor requirement fullfilled
while (true) {
// HMAC function used to output provided inputs into bytes
const hmac = crypto.createHmac('sha256', serverSeed);
hmac.update(`${clientSeed}:${nonce}:${currentRound}`);
const buffer = hmac.digest();
// Update curser for next iteration of loop
while (currentRoundCursor < 32) {
yield Number(buffer[currentRoundCursor]);
currentRoundCursor += 1;
}
currentRoundCursor = 0;
currentRound += 1;
}
}GenerateFloats-Funktion zur Umwandlung von Bytes in Floats
Diese Funktion wandelt den hexadezimalen SHA-256-Wert aus Bytes in Floats um, die für weitere Berechnungen von Spielereignissen verwendet werden.
Nachfolgend wird dargestellt, wie ein hexadezimaler SHA-256-Wert von Bytes in Floats umgewandelt wird. Die endgültige Ausgabe numArr enthält alle möglichen, für das Spiel erforderlichen Ergebnisse. Wenn nur 1 Ergebnis erforderlich ist, enthält die Liste nur einen Wert.
Sie gibt ein Array mit Zahlen zwischen 0-1 zurück, wobei count die Anzahl der Elemente im zurückgegebenen Array angibt.
Code:
// Convert the hash output from the rng byteGenerator to floats
export function generateFloats({
serverSeed,
clientSeed,
nonce,
cursor,
count,
}: GenerateFloatsInterface) {
// Random number generator function
const rng = byteGenerator({ serverSeed, clientSeed, nonce, cursor });
// Declare bytes as empty array
const bytes = [];
// Populate bytes array with sets of 4 from RNG output
while (bytes.length < count * 4) {
bytes.push(rng.next().value!);
}
// Return bytes as floats using lodash reduce function
const numArr = chunk(bytes, 4).map(bytesChunk =>
bytesChunk.reduce((result, value, i) => {
const divider = 256 ** (i + 1);
const partialResult = value / divider;
return result + partialResult;
}, 0),
);
return numArr;
}
Alle unsere Originalspiele verwenden sowohl ByteGenerator- als auch GenerateFloats-Funktionen zur Generierung zufälliger Floats zwischen 0 und 1. Ab diesem Punkt verwendet jedoch jedes Spiel ein eigenes Verfahren, um anhand des generierten Floats das Spielereignis zu bestimmen.
Das jeweilige Verfahren wird ausführlich in Spielereignisse erläutert.
Veranschaulichendes Beispiel
Hier veranschaulichen wir, wie Eingaben zur Generierung eines Spielereignisses in einem Würfelspiel verwendet werden.
Input Values: Given some random input values
serverSeed, clientSeed, nonce, cursor
Step 1: byteGenerator creates a SHA-256 byte
"a3f4e0ac7c7e8e9b5f16106c6b1d14e87c2c5a8d59b1d1c6a0b5f3e5a7d4c9a8”
Step 2: 256 bytes is split into 8 equal set of 32 bytes
"a3f4e0ac”, “7c7e8e9b”, “5f16106c”, “6b1d14e8”,
“7c2c5a8d”, “59b1d1c6”, “a0b5f3e5”, “a7d4c9a8”
Step 3: Each set is broken down into 2 bytes each
(only first 2 sets is shown)
set 1: a3-f4-e0-ac
set 2: 7c-7e-8e-9b
......
Step 4: Each of the 2 bytes represent a number from 0 to 255
set 1: [163, 244, 224, 172]
set 2: [124, 126, 142, 155]
......
Step 5: Using the formula in the code to generate numArr
Float 1 = (163 / (256^1)) + (244 / (256^2)) + (224 / (256^3))
+ (172 / (256^4)) = 0.64045528601
Float 2 = (124 / (256^1)) + (126 / (256^2)) + (142 / (256^3))
+ (155 / (256^4)) = 0.48630610737 Float 3 ......
Step 6: Output the floats in a list
numArr = [0.64045528601, 0.48630610737, ...... ]
*** Game Event Generation ***
Step 7: The numArr output will be used to generate game events,
depending on the game requirement
Example 7: Using dice as an example.
The game event is generated using the first numArr value.
const resultValue = floats.map(val => Math.floor(floats * 10001) / 100);
resultValue = Math.floor(0.64045528601 * 10001) / 100
= 64.05
Hinweis: Während ein Spiel aktiv ist, wird der Server-Seed gehasht. Daher können der Spieler und der Betreiber die einzelnen Ausgaben dieses Prozesses während des Spiels nicht einsehen.
Da während und nach dem Spiel derselbe Algorithmus und dieselben Funktionen verwendet werden, können Nutzer das Ergebnis bei denselben Eingaben jederzeit nachweisen.
