Files
casino/pkg/fair/fair_test.go
drjones 41c1bb2fdf feat(fair,scratch): add commit-reveal fairness and scratch tickets
Scratch odds tables are derived from the same tier list that generates
outcomes, so the published odds cannot drift from reality. Tests assert
observed frequencies and empirical RTP against the published figures;
the initial prize tables claimed 98% but actually paid 56%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 03:28:27 +00:00

109 lines
2.9 KiB
Go

package fair_test
import (
"bytes"
"testing"
"github.com/drjones/quantum-arcade/pkg/fair"
)
func TestCommitmentHidesSeed(t *testing.T) {
s := fair.NewServerSeed()
c := s.Commitment()
raw := s.Bytes()
if bytes.Contains(c[:], raw[:8]) {
t.Fatal("commitment leaks seed bytes")
}
}
func TestCommitmentVerifies(t *testing.T) {
s := fair.NewServerSeed()
c := s.Commitment()
if !fair.VerifyCommitment(c, s) {
t.Fatal("valid seed failed its own commitment")
}
other := fair.NewServerSeed()
if fair.VerifyCommitment(c, other) {
t.Fatal("a different seed satisfied the commitment")
}
}
func TestClientSeedDependsOnEveryParticipant(t *testing.T) {
a := []byte("player-a-pubkey")
b := []byte("player-b-pubkey")
c := []byte("player-c-pubkey")
withAll := fair.ClientSeed([][]byte{a, b, c})
withoutC := fair.ClientSeed([][]byte{a, b})
if withAll == withoutC {
t.Fatal("removing a participant did not change the client seed")
}
}
// Join order must matter in a defined way, but the same set in the same order
// must always produce the same seed.
func TestClientSeedIsStable(t *testing.T) {
keys := [][]byte{[]byte("k1"), []byte("k2")}
if fair.ClientSeed(keys) != fair.ClientSeed(keys) {
t.Fatal("client seed is not stable for identical input")
}
}
func TestRoundSeedIsDeterministic(t *testing.T) {
s := fair.NewServerSeed()
cs := fair.ClientSeed([][]byte{[]byte("p1")})
first := fair.RoundSeed(s, cs, 7)
for i := 0; i < 50; i++ {
if fair.RoundSeed(s, cs, 7) != first {
t.Fatal("round seed is not deterministic")
}
}
}
func TestNonceSeparatesOutcomes(t *testing.T) {
s := fair.NewServerSeed()
cs := fair.ClientSeed([][]byte{[]byte("p1")})
seen := map[[32]byte]bool{}
for n := uint64(0); n < 1000; n++ {
seed := fair.RoundSeed(s, cs, n)
if seen[seed] {
t.Fatalf("nonce %d collided with an earlier round seed", n)
}
seen[seed] = true
}
}
// The full protocol as a player would check it: the commitment published before
// the round must match the seed revealed after, and the seed must reproduce the
// outcome.
func TestEndToEndVerification(t *testing.T) {
server := fair.NewServerSeed()
published := server.Commitment()
participants := [][]byte{[]byte("alice"), []byte("bob")}
cs := fair.ClientSeed(participants)
const nonce = 42
seed := fair.RoundSeed(server, cs, nonce)
// After the round the server reveals the seed. A player recomputes:
if !fair.VerifyCommitment(published, server) {
t.Fatal("revealed seed does not match published commitment")
}
recomputed := fair.RoundSeed(server, fair.ClientSeed(participants), nonce)
if recomputed != seed {
t.Fatal("independent recomputation produced a different seed")
}
}
func TestServerSeedsAreUnique(t *testing.T) {
seen := map[[32]byte]bool{}
for i := 0; i < 1000; i++ {
s := fair.NewServerSeed()
if seen[s.Bytes()] {
t.Fatal("NewServerSeed returned a duplicate")
}
seen[s.Bytes()] = true
}
}