// Package fair implements the commit-reveal protocol that makes every outcome
// independently verifiable.
//
// The protocol, per round:
//
//  1. Commit — the server generates a random 32-byte seed and publishes
//     SHA-256(seed) before betting opens. It is now bound to that seed.
//  2. Client seed — derived from the public keys of everyone in the round.
//     The operator does not control these, so it cannot steer the outcome even
//     with full knowledge of its own seed.
//  3. Outcome — HMAC-SHA256(serverSeed, clientSeed || nonce) seeds the
//     simulation. The result is a pure function of that seed.
//  4. Reveal — after settlement the server publishes the seed. Anyone can
//     recompute the commitment, re-derive the outcome, and confirm it.
//
// The security property is that the operator must choose its seed before it
// knows the participant set, and cannot change it afterwards without breaking
// a published SHA-256 commitment.
package fair

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/binary"
	"encoding/hex"
)

// ServerSeed is the operator's secret contribution to a round, revealed after
// settlement.
type ServerSeed struct {
	b [32]byte
}

// NewServerSeed generates a cryptographically random server seed.
func NewServerSeed() ServerSeed {
	var s ServerSeed
	if _, err := rand.Read(s.b[:]); err != nil {
		// A failure of the system CSPRNG is not something to paper over: any
		// fallback would silently weaken every outcome derived from it.
		panic("fair: system randomness unavailable: " + err.Error())
	}
	return s
}

// ServerSeedFromBytes reconstructs a seed, for verification of a past round.
func ServerSeedFromBytes(b [32]byte) ServerSeed { return ServerSeed{b: b} }

// Bytes returns the raw seed. Callers must not publish this before settlement.
func (s ServerSeed) Bytes() [32]byte { return s.b }

// Hex renders the seed for the reveal step.
func (s ServerSeed) Hex() string { return hex.EncodeToString(s.b[:]) }

// Commitment is SHA-256 of the seed, published before the round opens.
func (s ServerSeed) Commitment() [32]byte { return sha256.Sum256(s.b[:]) }

// VerifyCommitment reports whether a revealed seed matches a published
// commitment. The comparison is constant-time out of habit; nothing secret
// depends on it by this point, but the cost is zero.
func VerifyCommitment(commitment [32]byte, seed ServerSeed) bool {
	actual := seed.Commitment()
	return subtle.ConstantTimeCompare(commitment[:], actual[:]) == 1
}

// ClientSeed derives the players' collective contribution from the public keys
// of everyone in the round, in join order. Because the operator cannot control
// who joins, it cannot predict this value when it commits to its own seed.
func ClientSeed(pubkeys [][]byte) [32]byte {
	h := sha256.New()
	for _, pk := range pubkeys {
		// Length-prefix each key so that concatenation is unambiguous and two
		// different participant lists cannot hash to the same value.
		var n [4]byte
		binary.BigEndian.PutUint32(n[:], uint32(len(pk)))
		h.Write(n[:])
		h.Write(pk)
	}
	var out [32]byte
	copy(out[:], h.Sum(nil))
	return out
}

// RoundSeed combines both seeds and a nonce into the value that seeds the
// simulation. The nonce separates rounds, or individual plays, that share a
// server seed.
func RoundSeed(server ServerSeed, client [32]byte, nonce uint64) [32]byte {
	mac := hmac.New(sha256.New, server.b[:])
	mac.Write(client[:])
	var n [8]byte
	binary.BigEndian.PutUint64(n[:], nonce)
	mac.Write(n[:])
	var out [32]byte
	copy(out[:], mac.Sum(nil))
	return out
}

// Proof is everything a player needs to verify one outcome without trusting
// any server response. It is what the verification endpoint returns.
type Proof struct {
	Commitment   string   `json:"commitment"`   // published before the round
	ServerSeed   string   `json:"server_seed"`  // revealed after settlement
	Participants []string `json:"participants"` // hex public keys, join order
	Nonce        uint64   `json:"nonce"`
	RoundSeed    string   `json:"round_seed"` // derived, shown for convenience
}

// BuildProof assembles the verification record for a settled round.
func BuildProof(server ServerSeed, pubkeys [][]byte, nonce uint64) Proof {
	client := ClientSeed(pubkeys)
	seed := RoundSeed(server, client, nonce)

	participants := make([]string, len(pubkeys))
	for i, pk := range pubkeys {
		participants[i] = hex.EncodeToString(pk)
	}
	commitment := server.Commitment()

	return Proof{
		Commitment:   hex.EncodeToString(commitment[:]),
		ServerSeed:   server.Hex(),
		Participants: participants,
		Nonce:        nonce,
		RoundSeed:    hex.EncodeToString(seed[:]),
	}
}
