Um Transparenz und nachweisbare Fairness sicherzustellen, haben wir für jedes Multiplayer-Spiel eine Kette aus 10 Millionen SHA256-Hashes generiert, beginnend mit einem Server-Geheimnis, dessen Ausgabe wiederholt 10 Millionen Mal in sich selbst durch SHA256 zurückgeführt wurde
Der SHA256-Wert des finalen Hash für Platform lautet: eeb4d340bfaed702601b9a08faba2f8088c4810c3e3a883293accaf25053b883
Der SHA256-Wert des finalen Hash für Crash lautet: c46f0705f6ba4df891ce44accda8f89f5a6fa8b987eb7c7b445280cf6cabfbc6
Der SHA256-Wert des finalen Hash für Battle lautet: 63cb1eb64ec4eb4de02f6bad85e42365d08f37497a1b7e907bd0b9183f27e1b3
Durch die Veröffentlichung hier verhindern wir jede Möglichkeit, eine alternative SHA256-Kette auszuwählen. Der Spielserver durchläuft diese Hash-Kette nun in umgekehrter Reihenfolge und verwendet diese Werte, um die Spielergebnisse nachweisbar fair zu berechnen.
1. Geheimer Ausgangs-Hash: Jeder Multiplayer-Spieltyp verfügt über seinen eigenen privaten Ausgangs-Hash (nur dem Server bekannt), der die Wurzel einer einzigartigen Hash-Kette bildet.
2. Hash-Ketten-Generierung: Ausgehend vom geheimen Ausgangs-Hash generieren wir durch wiederholte Anwendung von SHA256 eine Kette aus 10.000.000 Hashes.
func HashSeed(seed string) string {
hash := sha256.Sum256([]byte(seed))
return hex.EncodeToString(hash[:])
}
func CalculateHashChain(firstHash string) []string {
hashes := make([]string, 10_000_000)
for i := int64(0); i <= 10_000_000; i++ {
firstHash = HashSeed(firstHash)
hashes[i] = firstHash
}
return hashes
}
3. Umgekehrter Verbrauch: Wir veröffentlichen den letzten Hash der Kette. Jede Spielrunde verbraucht die Hashes in umgekehrter Reihenfolge: Runde 1 verwendet hash[10_000_000] Runde 2 verwendet hash[9_999_999] und so weiter…
4. Dieses Design macht es unmöglich, zukünftige Hashes zu fälschen, während es jedem ermöglicht, alle vorherigen Hashes zu überprüfen.
Unvorhersehbarkeit: Zukünftige Spielergebnisse können nicht bekannt sein oder beeinflusst werden, nicht einmal vom Server.
Überprüfbarkeit: Spieler können die Integrität jeder vorherigen Runde ausschließlich anhand des öffentlich veröffentlichten finalen Hash überprüfen.
Spielisolation: Jeder Spieltyp verfügt über seine eigene Kette, wodurch spielübergreifende Manipulation verhindert wird.
Um nachweisbare Fairness bei Einzelspieler-Spielen sicherzustellen, verwendet unsere Plattform ein System, das auf drei Hauptkomponenten zur Generierung des Ergebnis-Seeds basiert:
1. Client-Seed: Dies ist ein vom Nutzer kontrollierter Seed. Er wird automatisch bei der Registrierung eines Nutzers generiert, kann aber vom Nutzer jederzeit geändert werden. Eine Änderung des Client-Seeds löst die Generierung eines neuen Server-Seeds aus.
2. Server-Seed: Dieser wird vom Server anhand interner Parameter generiert, die dem Nutzer nicht offengelegt werden. Er ist eindeutig an den aktuellen Client-Seed gebunden. Für jeden Client-Seed gibt es einen entsprechenden Server-Seed. Wenn der Nutzer den Client-Seed ändert, wird der vorherige Server-Seed zu Verifizierungszwecken offengelegt.
3. Nonce: Eine eindeutige Zahl, die auf dem Zeitstempel der Wette (in Millisekunden) basiert. Sie stellt sicher, dass selbst wiederholte Aktionen unterschiedliche Ergebnisse liefern.
Diese drei Werte werden kombiniert und mittels HMAC-SHA256 gehasht, um den finalen Spiel-Seed zu generieren, der dann zur Bestimmung des Spielergebnisses verwendet wird.
func GenerateUserGameSeed(userSeed string, userServerSeed string, nonce int64) (string, error) {
data := fmt.Sprintf("%s:%s:%d", userServerSeed, userSeed, nonce)
mac := hmac.New(sha256.New, []byte(data))
gameSeed := hex.EncodeToString(mac.Sum(nil))
return gameSeed, nil
}
Der Server stellt die gehashte Version des aktuellen Server-Seeds bereit, bevor eine Wette platziert wird.
Nachdem der Nutzer seinen Client-Seed geändert hat, wird der zuvor verwendete Server-Seed offengelegt.
Dies ermöglicht es Nutzern zu überprüfen, dass alle mit dem vorherigen Server-Seed generierten Ergebnisse konsistent und fair waren.