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 }