github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/flip/simple.go (about)

     1  package flip
     2  
     3  import (
     4  	"math/big"
     5  	"strings"
     6  )
     7  
     8  // Player is an identifier for a player in the flip game. It can be anything,
     9  // but must be unique for the game.  You can use user IDs in hex here, for instance,
    10  // or possibly (userid||deviceID) since each user in Keybase will likely enter
    11  // a flip multiple times (if many if his devices are on).
    12  type Player string
    13  
    14  func (p Player) key() string {
    15  	return strings.ToLower(string(p))
    16  }
    17  
    18  // PlayerState refers to all state about a player in the game. It includes the
    19  // Player's name, his commitment, and his revealed preimage.
    20  type PlayerState struct {
    21  	Player     Player
    22  	Commitment *Commitment
    23  	Reveal     Secret
    24  }
    25  
    26  func checkReveal(cp CommitmentPayload, c Commitment, r Secret) bool {
    27  	commitment, err := r.computeCommitment(cp)
    28  	if err != nil {
    29  		return false
    30  	}
    31  	return commitment.Eq(c)
    32  }
    33  
    34  func checkPlayer(err *Error, cp CommitmentPayload, player PlayerState) {
    35  	if player.Commitment == nil {
    36  		err.addNoCommitment(player.Player)
    37  		return
    38  	}
    39  	if player.Reveal.IsNil() {
    40  		err.addNoReveal(player.Player)
    41  		return
    42  	}
    43  
    44  	if !checkReveal(cp, *player.Commitment, player.Reveal) {
    45  		err.addBadCommitment(player.Player)
    46  		return
    47  	}
    48  }
    49  
    50  func checkPlayers(cp CommitmentPayload, player []PlayerState) error {
    51  	var err Error
    52  	d := make(map[string]bool)
    53  	for _, p := range player {
    54  		checkPlayer(&err, cp, p)
    55  		if d[p.Player.key()] {
    56  			err.addDuplicate(p.Player)
    57  		} else {
    58  			d[p.Player.key()] = true
    59  		}
    60  	}
    61  	if err.IsNil() {
    62  		return nil
    63  	}
    64  
    65  	return err
    66  }
    67  
    68  func computeSecret(players []PlayerState) Secret {
    69  	var res Secret
    70  	for _, p := range players {
    71  		res.XOR(p.Reveal)
    72  	}
    73  	return res
    74  }
    75  
    76  // Flip takes all the completed PlayerStates from the game, makes sure they don't have
    77  // an error, and if not, outputs a PRNG from which arbirarily many ints, or bools,
    78  // can be deterministically plucked. If there's an error, PRNG will be nil.
    79  func Flip(cp CommitmentPayload, players []PlayerState) (*PRNG, error) {
    80  	err := checkPlayers(cp, players)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	res := computeSecret(players)
    85  	return NewPRNG(res), nil
    86  }
    87  
    88  // FlipOneBig takes all the completed PlayerStates, and checks them.  If no error,
    89  // then outputs one random number between 0 and the given modulus, which is an arbitrarily
    90  // big number. If there was an error in the game setup, then it will return nil and the error.
    91  func FlipOneBig(cp CommitmentPayload, players []PlayerState, modulus *big.Int) (*big.Int, error) {
    92  	prng, err := Flip(cp, players)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	return prng.Big(modulus), nil
    97  }
    98  
    99  // FlipOneInt takes all the completed PlayerStates, and checks them.  If no error,
   100  // then outputs one random number between 0 and the given modulus, a signed 64-bit int.
   101  // If there was an  error in the game setup, then it will return 0 and the error.
   102  func FlipInt(cp CommitmentPayload, players []PlayerState, modulus int64) (int64, error) {
   103  	prng, err := Flip(cp, players)
   104  	if err != nil {
   105  		return 0, err
   106  	}
   107  	return prng.Int(modulus), nil
   108  }
   109  
   110  // FlipOneBool takes all the completed PlayerStates, and checks them. If no error,
   111  // then outputs one random bool. If there was an error in the game setup, then it will
   112  // return false and the error.
   113  func FlipBool(cp CommitmentPayload, players []PlayerState) (bool, error) {
   114  	prng, err := Flip(cp, players)
   115  	if err != nil {
   116  		return false, err
   117  	}
   118  	return prng.Bool(), nil
   119  }