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

     1  package flip
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"math/big"
    11  	"strings"
    12  	"time"
    13  
    14  	chat1 "github.com/keybase/client/go/protocol/chat1"
    15  	clockwork "github.com/keybase/clockwork"
    16  )
    17  
    18  // Excludes `Params` from being logged.
    19  func (s Start) String() string {
    20  	return fmt.Sprintf("{StartTime:%v CommitmentWindowMsec:%v RevealWindowMsec:%v SlackMsec:%v CommitmentCompleteWindowMsec:%v}",
    21  		s.StartTime, s.CommitmentWindowMsec, s.RevealWindowMsec, s.SlackMsec, s.CommitmentWindowMsec)
    22  }
    23  
    24  type GameMessageWrapped struct {
    25  	Sender              UserDevice
    26  	Msg                 GameMessageV1
    27  	Me                  *playerControl
    28  	Forward             bool
    29  	FirstInConversation bool
    30  }
    31  
    32  func (m GameMessageWrapped) isForwardable() bool {
    33  	t, _ := m.Msg.Body.T()
    34  	return t != MessageType_END
    35  }
    36  
    37  func (m GameMessageWrapped) GameMetadata() GameMetadata {
    38  	return m.Msg.Md
    39  }
    40  
    41  func (g GameMetadata) ToKey() GameKey {
    42  	return GameKey(strings.Join([]string{g.Initiator.U.String(), g.Initiator.D.String(), g.ConversationID.String(), g.GameID.String()}, ","))
    43  }
    44  
    45  func (g GameMetadata) String() string {
    46  	return string(g.ToKey())
    47  }
    48  
    49  func (g GameMetadata) check() bool {
    50  	return g.Initiator.check() && !g.ConversationID.IsNil() && g.GameID.Check()
    51  }
    52  
    53  type GameKey string
    54  type GameIDKey string
    55  type UserDeviceKey string
    56  
    57  func (u UserDevice) ToKey() UserDeviceKey {
    58  	return UserDeviceKey(strings.Join([]string{u.U.String(), u.D.String()}, ","))
    59  }
    60  
    61  func (u UserDevice) check() bool {
    62  	return u.U.Bytes() != nil && u.D.Bytes() != nil
    63  }
    64  
    65  func GameIDToKey(g chat1.FlipGameID) GameIDKey {
    66  	return GameIDKey(g.String())
    67  }
    68  
    69  type Result struct {
    70  	Shuffle []int
    71  	Bool    *bool
    72  	Int     *int64
    73  	Big     *big.Int
    74  }
    75  
    76  type Game struct {
    77  	md                     GameMetadata
    78  	msgID                  int
    79  	clockSkew              time.Duration
    80  	start                  time.Time
    81  	isLeader               bool
    82  	params                 Start
    83  	key                    GameKey
    84  	msgCh                  <-chan *GameMessageWrapped
    85  	stage                  Stage
    86  	stageForTimeout        Stage
    87  	players                map[UserDeviceKey]*GamePlayerState
    88  	commitments            map[string]bool
    89  	gameUpdateCh           chan GameStateUpdateMessage
    90  	nPlayers               int
    91  	dealer                 *Dealer
    92  	me                     *playerControl
    93  	commitmentCompleteHash Hash
    94  	clock                  func() clockwork.Clock
    95  	clogf                  func(ctx context.Context, fmt string, args ...interface{})
    96  
    97  	// To handle reorderings between CommitmentComplete and commitements,
    98  	// wee need some extra bookkeeping.
    99  	iWasIncluded          bool
   100  	iOptedOutOfCommit     bool
   101  	gotCommitmentComplete bool
   102  	latecomers            map[UserDeviceKey]bool
   103  }
   104  
   105  type GamePlayerState struct {
   106  	ud               UserDevice
   107  	commitment       *Commitment
   108  	commitmentTime   time.Time
   109  	leaderCommitment *Commitment
   110  	included         bool
   111  	secret           *Secret
   112  }
   113  
   114  func (g *Game) GameMetadata() GameMetadata {
   115  	return g.md
   116  }
   117  
   118  func MakeGameMessageEncoded(s string) GameMessageEncoded {
   119  	return GameMessageEncoded(s)
   120  }
   121  
   122  func (e GameMessageEncoded) String() string {
   123  	return string(e)
   124  }
   125  
   126  func (e GameMessageEncoded) Decode() (*GameMessageV1, error) {
   127  	raw, err := base64.StdEncoding.DecodeString(string(e))
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	var msg GameMessage
   132  	err = msgpackDecode(&msg, raw)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	v, err := msg.V()
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	if v != Version_V1 {
   141  		return nil, BadVersionError(v)
   142  	}
   143  	tmp := msg.V1()
   144  	if !tmp.Md.check() {
   145  		return nil, ErrBadData
   146  	}
   147  	return &tmp, nil
   148  }
   149  
   150  func (e *GameMessageWrappedEncoded) Decode() (*GameMessageWrapped, error) {
   151  	v1, err := e.Body.Decode()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	ret := GameMessageWrapped{Sender: e.Sender, Msg: *v1, FirstInConversation: e.FirstInConversation}
   156  	if !e.GameID.Eq(ret.Msg.Md.GameID) {
   157  		return nil, BadGameIDError{G: ret.Msg.Md, I: e.GameID}
   158  	}
   159  	return &ret, nil
   160  }
   161  
   162  func (m GameMessageWrapped) Encode() (GameMessageEncoded, error) {
   163  	return m.Msg.Encode()
   164  }
   165  
   166  func (b GameMessageBody) Encode(md GameMetadata) (GameMessageEncoded, error) {
   167  	v1 := GameMessageV1{Md: md, Body: b}
   168  	return v1.Encode()
   169  }
   170  
   171  func (v GameMessageV1) Encode() (GameMessageEncoded, error) {
   172  	msg := NewGameMessageWithV1(v)
   173  	raw, err := msgpackEncode(msg)
   174  	if err != nil {
   175  		return GameMessageEncoded(""), err
   176  	}
   177  	return GameMessageEncoded(base64.StdEncoding.EncodeToString(raw)), nil
   178  }
   179  
   180  func (d *Dealer) run(ctx context.Context, game *Game) {
   181  	doneCh := make(chan error)
   182  	key := game.key
   183  	go game.run(ctx, doneCh)
   184  	err := <-doneCh
   185  
   186  	if err != nil {
   187  		d.dh.CLogf(ctx, "[%s] Error running game %s: %s", d.dh.Me(), key, err.Error())
   188  
   189  	} else {
   190  		d.dh.CLogf(ctx, "Game %s ended cleanly", key)
   191  	}
   192  
   193  	// If the game was shutdown via the Dealer#Stop call, then
   194  	// don't close the channel (again) or remove the channel from the
   195  	// map, it's already dead.
   196  	if _, ok := err.(GameShutdownError); ok {
   197  		return
   198  	}
   199  
   200  	d.Lock()
   201  	if ch := d.games[key]; ch != nil {
   202  		close(ch)
   203  		delete(d.games, key)
   204  		delete(d.gameIDs, GameIDToKey(game.md.GameID))
   205  	}
   206  	d.Unlock()
   207  }
   208  
   209  func (g *Game) getNextTimer() <-chan time.Time {
   210  	dl := g.nextDeadline()
   211  	return g.clock().AfterTime(dl)
   212  }
   213  
   214  func (g *Game) CommitmentEndTime() time.Time {
   215  	// If we're the leader, then let's cut off when we say we're going to cut off
   216  	// If we're not, then let's give extra time (a multiple of 2) to the leader.
   217  	return g.start.Add(g.params.CommitmentWindowWithSlack(g.isLeader))
   218  }
   219  
   220  func (g *Game) RevealEndTime() time.Time {
   221  	return g.start.Add(g.params.RevealWindowWithSlack())
   222  }
   223  
   224  func (g *Game) nextDeadline() time.Time {
   225  	switch g.stageForTimeout {
   226  	case Stage_ROUND1:
   227  		return g.CommitmentEndTime()
   228  	case Stage_ROUND2:
   229  		return g.RevealEndTime()
   230  	default:
   231  		return time.Time{}
   232  	}
   233  }
   234  
   235  func (g Game) commitmentPayload() CommitmentPayload {
   236  	return CommitmentPayload{
   237  		V: Version_V1,
   238  		U: g.md.Initiator.U,
   239  		D: g.md.Initiator.D,
   240  		C: g.md.ConversationID,
   241  		G: g.md.GameID,
   242  		S: g.params.StartTime,
   243  	}
   244  }
   245  
   246  func (g *Game) setSecret(ctx context.Context, ps *GamePlayerState, secret Secret) error {
   247  	expected, err := secret.computeCommitment(g.commitmentPayload())
   248  	if err != nil {
   249  		return err
   250  	}
   251  	if ps.secret != nil {
   252  		return DuplicateRevealError{G: g.md, U: ps.ud}
   253  	}
   254  	if !expected.Eq(*ps.commitment) {
   255  		return BadRevealError{G: g.md, U: ps.ud}
   256  	}
   257  	ps.secret = &secret
   258  	return nil
   259  }
   260  
   261  func (g *Game) finishGame(ctx context.Context) error {
   262  	var xor Secret
   263  	for _, ps := range g.players {
   264  		if !ps.included {
   265  			continue
   266  		}
   267  		if ps.secret == nil {
   268  			return NoRevealError{G: g.md, U: ps.ud}
   269  		}
   270  		xor.XOR(*ps.secret)
   271  	}
   272  	prng := NewPRNG(xor)
   273  	err := g.doFlip(ctx, prng)
   274  	g.sendOutgoingChat(ctx, NewGameMessageBodyWithEnd())
   275  	return err
   276  }
   277  
   278  func (g *Game) doFlip(ctx context.Context, prng *PRNG) error {
   279  	params := g.params.Params
   280  	t, err := params.T()
   281  	if err != nil {
   282  		return err
   283  	}
   284  	var res Result
   285  	switch t {
   286  	case FlipType_BOOL:
   287  		tmp := prng.Bool()
   288  		res.Bool = &tmp
   289  	case FlipType_INT:
   290  		tmp := prng.Int(params.Int())
   291  		res.Int = &tmp
   292  	case FlipType_BIG:
   293  		var modulus big.Int
   294  		modulus.SetBytes(params.Big())
   295  		res.Big = prng.Big(&modulus)
   296  	case FlipType_SHUFFLE:
   297  		res.Shuffle = prng.Permutation(int(params.Shuffle()))
   298  	default:
   299  		return BadFlipTypeError{G: g.GameMetadata(), T: t}
   300  	}
   301  
   302  	g.gameUpdateCh <- GameStateUpdateMessage{
   303  		Metadata: g.GameMetadata(),
   304  		Result:   &res,
   305  	}
   306  	return nil
   307  }
   308  
   309  func (g *Game) playerCommitedInTime(ps *GamePlayerState, now time.Time) bool {
   310  	diff := ps.commitmentTime.Sub(g.start)
   311  	return diff < g.params.CommitmentWindowWithSlack(true)
   312  }
   313  
   314  func (g *Game) getPlayerState(ud UserDevice) *GamePlayerState {
   315  	key := ud.ToKey()
   316  	ret := g.players[key]
   317  	if ret != nil {
   318  		return ret
   319  	}
   320  	ret = &GamePlayerState{ud: ud}
   321  	g.players[key] = ret
   322  	return ret
   323  }
   324  
   325  func (g *Game) handleCommitment(ctx context.Context, sender UserDevice, now time.Time, com Commitment) (err error) {
   326  	ps := g.getPlayerState(sender)
   327  	if ps.commitment != nil {
   328  		return DuplicateRegistrationError{g.md, sender}
   329  	}
   330  	if ps.leaderCommitment != nil && !ps.leaderCommitment.Eq(com) {
   331  		return CommitmentMismatchError{G: g.GameMetadata(), U: sender}
   332  	}
   333  	ps.commitment = &com
   334  	ps.commitmentTime = now
   335  
   336  	comHex := hex.EncodeToString(com[:])
   337  	if g.commitments[comHex] {
   338  		return DuplicateCommitmentError{}
   339  	}
   340  	g.commitments[comHex] = true
   341  
   342  	// If this user was a latecomer (we got the commitment after we got the CommitmentComplete),
   343  	// then we mark them as being accounted for.
   344  	delete(g.latecomers, sender.ToKey())
   345  
   346  	g.gameUpdateCh <- GameStateUpdateMessage{
   347  		Metadata: g.GameMetadata(),
   348  		Commitment: &CommitmentUpdate{
   349  			User:       sender,
   350  			Commitment: com,
   351  		},
   352  	}
   353  	return g.maybeReveal(ctx)
   354  }
   355  
   356  func (g *Game) maybeReveal(ctx context.Context) (err error) {
   357  
   358  	if !g.gotCommitmentComplete {
   359  		return nil
   360  	}
   361  	if len(g.latecomers) > 0 {
   362  		return nil
   363  	}
   364  
   365  	g.stage = Stage_ROUND2
   366  	g.stageForTimeout = Stage_ROUND2
   367  
   368  	if g.me == nil {
   369  		return nil
   370  	}
   371  
   372  	if !g.iWasIncluded && !g.iOptedOutOfCommit {
   373  		g.clogf(ctx, "The leader didn't include me (%s) so not sending a reveal (%s)", g.me.me, g.md)
   374  		return nil
   375  	}
   376  
   377  	reveal := Reveal{
   378  		Secret: g.me.secret,
   379  		Cch:    g.commitmentCompleteHash,
   380  	}
   381  	g.sendOutgoingChat(ctx, NewGameMessageBodyWithReveal(reveal))
   382  	return nil
   383  }
   384  
   385  func (g *Game) handleCommitmentCompletePlayer(ctx context.Context, u UserDeviceCommitment) (err error) {
   386  
   387  	ps := g.getPlayerState(u.Ud)
   388  	if ps.leaderCommitment != nil {
   389  		return DuplicateCommitmentCompleteError{G: g.md, U: u.Ud}
   390  	}
   391  	if ps.commitment != nil && !ps.commitment.Eq(u.C) {
   392  		return CommitmentMismatchError{G: g.md, U: u.Ud}
   393  	}
   394  	if ps.commitment == nil {
   395  		g.latecomers[u.Ud.ToKey()] = true
   396  	}
   397  	ps.leaderCommitment = &u.C
   398  	ps.included = true
   399  	g.nPlayers++
   400  
   401  	if g.me != nil && g.me.me.Eq(u.Ud) {
   402  		g.iWasIncluded = true
   403  	}
   404  	return nil
   405  }
   406  
   407  func (g *Game) handleCommitmentComplete(ctx context.Context, sender UserDevice, now time.Time, cc CommitmentComplete) (err error) {
   408  
   409  	if !sender.Eq(g.md.Initiator) {
   410  		return WrongSenderError{G: g.md, Expected: g.md.Initiator, Actual: sender}
   411  	}
   412  
   413  	if !checkUserDeviceCommitments(cc.Players) {
   414  		return CommitmentCompleteSortError{G: g.md}
   415  	}
   416  
   417  	for _, u := range cc.Players {
   418  		err = g.handleCommitmentCompletePlayer(ctx, u)
   419  		if err != nil {
   420  			return err
   421  		}
   422  	}
   423  
   424  	cch, err := hashUserDeviceCommitments(cc.Players)
   425  	if err != nil {
   426  		return err
   427  	}
   428  
   429  	g.commitmentCompleteHash = cch
   430  	g.gotCommitmentComplete = true
   431  
   432  	// for now, just warn if users who made it in on time weren't included.
   433  	for _, ps := range g.players {
   434  		if !ps.included && g.playerCommitedInTime(ps, now) && g.clogf != nil {
   435  			g.clogf(ctx, "User %s wasn't included, but they should have been", ps.ud)
   436  		}
   437  	}
   438  
   439  	g.gameUpdateCh <- GameStateUpdateMessage{
   440  		Metadata:           g.GameMetadata(),
   441  		CommitmentComplete: &cc,
   442  	}
   443  
   444  	return g.maybeReveal(ctx)
   445  }
   446  
   447  func errToOk(err error) string {
   448  	if err == nil {
   449  		return "ok"
   450  	}
   451  	return "ERROR: " + err.Error()
   452  }
   453  
   454  func (g *Game) handleMessage(ctx context.Context, msg *GameMessageWrapped, now time.Time) (err error) {
   455  
   456  	msgID := g.msgID
   457  	g.msgID++
   458  
   459  	g.clogf(ctx, "+ Game#handleMessage: %s@%d <- %+v", g.GameMetadata(), msgID, *msg)
   460  	defer func() { g.clogf(ctx, "- Game#handleMessage: %s@%d -> %s", g.GameMetadata(), msgID, errToOk(err)) }()
   461  
   462  	t, err := msg.Msg.Body.T()
   463  	if err != nil {
   464  		return err
   465  	}
   466  	badStage := func() error {
   467  		return BadMessageForStageError{G: g.GameMetadata(), MessageType: t, Stage: g.stage}
   468  	}
   469  	switch t {
   470  
   471  	case MessageType_START:
   472  		return badStage()
   473  
   474  	case MessageType_END:
   475  		return io.EOF
   476  
   477  	case MessageType_COMMITMENT:
   478  		if g.stage != Stage_ROUND1 {
   479  			g.clogf(ctx, "User %s sent a commitment too late, not included in game %s", msg.Sender, g.md)
   480  			return nil
   481  		}
   482  
   483  		err = g.handleCommitment(ctx, msg.Sender, now, msg.Msg.Body.Commitment())
   484  		if err != nil {
   485  			return err
   486  		}
   487  
   488  	case MessageType_COMMITMENT_COMPLETE:
   489  		if g.stage != Stage_ROUND1 {
   490  			return badStage()
   491  		}
   492  
   493  		err = g.handleCommitmentComplete(ctx, msg.Sender, now, msg.Msg.Body.CommitmentComplete())
   494  		if err != nil {
   495  			return err
   496  		}
   497  
   498  	case MessageType_REVEAL:
   499  		if g.stage != Stage_ROUND2 {
   500  			return badStage()
   501  		}
   502  
   503  		key := msg.Sender.ToKey()
   504  		ps := g.players[key]
   505  
   506  		if ps == nil {
   507  			g.clogf(ctx, "Skipping unregistered revealer %s for game %s", msg.Sender, g.md)
   508  			return nil
   509  		}
   510  		if !ps.included {
   511  			g.clogf(ctx, "Skipping unincluded revealer %s for game %s", msg.Sender, g.md)
   512  			return nil
   513  		}
   514  		if now.After(g.RevealEndTime()) {
   515  			return RevealTooLateError{G: g.md, U: msg.Sender}
   516  		}
   517  
   518  		reveal := msg.Msg.Body.Reveal()
   519  		if !g.commitmentCompleteHash.Eq(reveal.Cch) {
   520  			return BadCommitmentCompleteHashError{G: g.GameMetadata(), U: msg.Sender}
   521  		}
   522  		err := g.setSecret(ctx, ps, reveal.Secret)
   523  		if err != nil {
   524  			return err
   525  		}
   526  		g.gameUpdateCh <- GameStateUpdateMessage{
   527  			Metadata: g.GameMetadata(),
   528  			Reveal: &RevealUpdate{
   529  				User:   msg.Sender,
   530  				Reveal: reveal.Secret,
   531  			},
   532  		}
   533  
   534  		g.nPlayers--
   535  		if g.nPlayers == 0 {
   536  			return g.finishGame(ctx)
   537  		}
   538  
   539  	default:
   540  		return BadMessageError{G: g.GameMetadata()}
   541  	}
   542  
   543  	return nil
   544  }
   545  
   546  func (g *Game) userDeviceCommitmentList() []UserDeviceCommitment {
   547  	var ret []UserDeviceCommitment
   548  	for _, p := range g.players {
   549  		if p.commitment != nil {
   550  			ret = append(ret, UserDeviceCommitment{Ud: p.ud, C: *p.commitment})
   551  		}
   552  	}
   553  	sortUserDeviceCommitments(ret)
   554  	return ret
   555  }
   556  
   557  func (g *Game) completeCommitments(ctx context.Context) error {
   558  	cc := CommitmentComplete{
   559  		Players: g.userDeviceCommitmentList(),
   560  	}
   561  	body := NewGameMessageBodyWithCommitmentComplete(cc)
   562  	g.stageForTimeout = Stage_ROUND2
   563  	g.sendOutgoingChat(ctx, body)
   564  	return nil
   565  }
   566  
   567  func (g *Game) absentees() []UserDevice {
   568  	var bad []UserDevice
   569  	for _, p := range g.players {
   570  		if p.included && p.secret == nil {
   571  			bad = append(bad, p.ud)
   572  		}
   573  	}
   574  	return bad
   575  }
   576  
   577  func (g *Game) sendOutgoingChat(ctx context.Context, body GameMessageBody) {
   578  	// Call back into the dealer, to reroute a message back into our
   579  	// game, but do so in a Go routine so we don't deadlock. There could be
   580  	// 100 incoming messages in front of us, all coming off the chat channel,
   581  	// so we're ok to send when we can. If use the game in the context of
   582  	// replay, the dealer will be nil, so no need to send.
   583  	if g.dealer != nil {
   584  		go func() {
   585  			err := g.dealer.sendOutgoingChat(ctx, g.GameMetadata(), nil, body)
   586  			if err != nil {
   587  				g.clogf(ctx, "Error sending outgoing chat %+v", err)
   588  			}
   589  		}()
   590  	}
   591  }
   592  
   593  func (g *Game) handleTimerEvent(ctx context.Context) error {
   594  	if g.isLeader && g.stageForTimeout == Stage_ROUND1 {
   595  		return g.completeCommitments(ctx)
   596  	}
   597  
   598  	absentees := g.absentees()
   599  
   600  	if g.stageForTimeout == Stage_ROUND2 && len(absentees) > 0 {
   601  		return AbsenteesError{Absentees: absentees}
   602  	}
   603  
   604  	return TimeoutError{G: g.md, Stage: g.stageForTimeout}
   605  }
   606  
   607  func (g *Game) runMain(ctx context.Context) error {
   608  	for {
   609  		timer := g.getNextTimer()
   610  		var err error
   611  		select {
   612  		case <-timer:
   613  			err = g.handleTimerEvent(ctx)
   614  		case msg, ok := <-g.msgCh:
   615  			if !ok {
   616  				return GameShutdownError{G: g.GameMetadata()}
   617  			}
   618  			err = g.handleMessage(ctx, msg, g.clock().Now())
   619  		case <-ctx.Done():
   620  			return ctx.Err()
   621  		}
   622  		if err == io.EOF {
   623  			return nil
   624  		}
   625  		if err != nil {
   626  			g.gameUpdateCh <- GameStateUpdateMessage{
   627  				Metadata: g.GameMetadata(),
   628  				Err:      err,
   629  			}
   630  			return err
   631  		}
   632  	}
   633  }
   634  
   635  func (g *Game) runDrain(ctx context.Context) {
   636  	i := 0
   637  	for range g.msgCh {
   638  		i++
   639  	}
   640  	if i > 0 {
   641  		g.clogf(ctx, "drained %d messages on shutdown in game %s", i, g.md)
   642  	}
   643  }
   644  
   645  func (g *Game) run(ctx context.Context, doneCh chan error) {
   646  	doneCh <- g.runMain(ctx)
   647  	g.runDrain(ctx)
   648  }
   649  
   650  func absDuration(d time.Duration) time.Duration {
   651  	if d < time.Duration(0) {
   652  		return time.Duration(-1) * d
   653  	}
   654  	return d
   655  }
   656  
   657  func (d *Dealer) computeClockSkew(ctx context.Context, md GameMetadata, leaderTime time.Time, myNow time.Time) (skew time.Duration, err error) {
   658  	serverTime, err := d.dh.ServerTime(ctx)
   659  	if err != nil {
   660  		return skew, err
   661  	}
   662  	return computeClockSkew(md, serverTime, leaderTime, myNow)
   663  }
   664  
   665  func computeClockSkew(md GameMetadata, serverTime time.Time, leaderTime time.Time, myNow time.Time) (skew time.Duration, err error) {
   666  	localTime := myNow
   667  	leaderSkew := leaderTime.Sub(serverTime)
   668  	localSkew := localTime.Sub(serverTime)
   669  
   670  	if absDuration(localSkew) > MaxClockSkew {
   671  		return time.Duration(0), BadLocalClockError{G: md}
   672  	}
   673  	if absDuration(leaderSkew) > MaxClockSkew {
   674  		return time.Duration(0), BadLeaderClockError{G: md}
   675  	}
   676  	totalSkew := localTime.Sub(leaderTime)
   677  
   678  	return totalSkew, nil
   679  }
   680  
   681  func (d *Dealer) handleMessageStart(ctx context.Context, msg *GameMessageWrapped, start Start) error {
   682  	d.Lock()
   683  	defer d.Unlock()
   684  	md := msg.GameMetadata()
   685  	key := md.ToKey()
   686  	gameIDKey := GameIDToKey(md.GameID)
   687  	if d.games[key] != nil {
   688  		return GameAlreadyStartedError{G: md}
   689  	}
   690  	if _, found := d.gameIDs[gameIDKey]; found {
   691  		return GameReplayError{G: md.GameID}
   692  	}
   693  	if !msg.Sender.Eq(md.Initiator) {
   694  		return WrongSenderError{G: md, Expected: msg.Sender, Actual: md.Initiator}
   695  	}
   696  	cs, err := d.computeClockSkew(ctx, md, start.StartTime.Time(), d.dh.Clock().Now())
   697  	if err != nil {
   698  		return err
   699  	}
   700  
   701  	if !msg.FirstInConversation {
   702  		return GameReplayError{md.GameID}
   703  	}
   704  
   705  	isLeader := true
   706  	me := msg.Me
   707  	// Make a new follower player controller if one didn't already exist (since we were
   708  	// the Leader)
   709  	if me == nil {
   710  		me, err = d.newPlayerControl(d.dh.Me(), md, start)
   711  		if err != nil {
   712  			return err
   713  		}
   714  		isLeader = false
   715  	}
   716  
   717  	optedOutOfCommit := false
   718  	if !isLeader {
   719  		optedOutOfCommit = !d.dh.ShouldCommit(ctx)
   720  	}
   721  
   722  	msgCh := make(chan *GameMessageWrapped)
   723  	game := &Game{
   724  		md:                msg.GameMetadata(),
   725  		isLeader:          isLeader,
   726  		clockSkew:         cs,
   727  		start:             d.dh.Clock().Now(),
   728  		key:               key,
   729  		params:            start,
   730  		msgCh:             msgCh,
   731  		stage:             Stage_ROUND1,
   732  		stageForTimeout:   Stage_ROUND1,
   733  		gameUpdateCh:      d.gameUpdateCh,
   734  		players:           make(map[UserDeviceKey]*GamePlayerState),
   735  		commitments:       make(map[string]bool),
   736  		dealer:            d,
   737  		me:                me,
   738  		clock:             d.dh.Clock,
   739  		clogf:             d.dh.CLogf,
   740  		latecomers:        make(map[UserDeviceKey]bool),
   741  		iOptedOutOfCommit: optedOutOfCommit,
   742  	}
   743  	d.games[key] = msgCh
   744  	d.gameIDs[gameIDKey] = md
   745  	d.previousGames[GameIDToKey(md.GameID)] = true
   746  
   747  	go d.run(ctx, game)
   748  
   749  	// Once the game has started, we are free to send a message into the channel
   750  	// with our commitment. We are now in the inner loop of the Dealer, so we
   751  	// have to do this send in a Go-routine, so as not to deadlock the Dealer.
   752  	if !isLeader && !optedOutOfCommit {
   753  		go func() {
   754  			err := d.sendCommitment(ctx, md, me)
   755  			if err != nil {
   756  				game.clogf(ctx, "Error sending commitment: %+v", err)
   757  			}
   758  		}()
   759  	}
   760  	return nil
   761  }
   762  
   763  func (d *Dealer) handleMessageOthers(c context.Context, msg *GameMessageWrapped) error {
   764  	d.Lock()
   765  	defer d.Unlock()
   766  	md := msg.GameMetadata()
   767  	key := md.ToKey()
   768  	game := d.games[key]
   769  	if game == nil {
   770  		return GameFinishedError{G: md}
   771  	}
   772  	game <- msg
   773  	return nil
   774  }
   775  
   776  func (d *Dealer) handleMessage(ctx context.Context, msg *GameMessageWrapped) error {
   777  	d.dh.CLogf(ctx, "flip.Dealer: Incoming: %+v", msg)
   778  
   779  	t, err := msg.Msg.Body.T()
   780  	if err != nil {
   781  		return err
   782  	}
   783  	switch t {
   784  	case MessageType_START:
   785  		err = d.handleMessageStart(ctx, msg, msg.Msg.Body.Start())
   786  		if err != nil {
   787  			d.gameUpdateCh <- GameStateUpdateMessage{
   788  				Metadata: msg.Msg.Md,
   789  				Err:      err,
   790  			}
   791  		}
   792  	default:
   793  		err = d.handleMessageOthers(ctx, msg)
   794  	}
   795  	if err != nil {
   796  		return err
   797  	}
   798  	if !(msg.Forward && msg.isForwardable()) {
   799  		return nil
   800  	}
   801  	// Encode and send the message through the external server-routed chat channel
   802  	emsg, err := msg.Encode()
   803  	if err != nil {
   804  		return err
   805  	}
   806  	err = d.dh.SendChat(ctx, msg.Msg.Md.Initiator.U, msg.Msg.Md.ConversationID, msg.Msg.Md.GameID, emsg)
   807  	if err != nil {
   808  		return err
   809  	}
   810  	return nil
   811  }
   812  
   813  func (d *Dealer) stopGames() {
   814  	d.Lock()
   815  	defer d.Unlock()
   816  	for k, ch := range d.games {
   817  		delete(d.games, k)
   818  		close(ch)
   819  	}
   820  }
   821  
   822  type playerControl struct {
   823  	me         UserDevice
   824  	md         GameMetadata
   825  	secret     Secret
   826  	commitment Commitment
   827  	start      Start
   828  	dealer     *Dealer
   829  }
   830  
   831  func (d *Dealer) newPlayerControl(me UserDevice, md GameMetadata, start Start) (*playerControl, error) {
   832  	secret := GenerateSecret()
   833  	cp := CommitmentPayload{
   834  		V: Version_V1,
   835  		U: md.Initiator.U,
   836  		D: md.Initiator.D,
   837  		C: md.ConversationID,
   838  		G: md.GameID,
   839  		S: start.StartTime,
   840  	}
   841  	commitment, err := secret.computeCommitment(cp)
   842  	if err != nil {
   843  		return nil, err
   844  	}
   845  	return &playerControl{
   846  		me:         me,
   847  		md:         md,
   848  		secret:     secret,
   849  		commitment: commitment,
   850  		start:      start,
   851  		dealer:     d,
   852  	}, nil
   853  }
   854  
   855  func (p *playerControl) GameMetadata() GameMetadata {
   856  	return p.md
   857  }
   858  
   859  func (d *Dealer) startFlip(ctx context.Context, start Start, conversationID chat1.ConversationID) (pc *playerControl, err error) {
   860  	return d.startFlipWithGameID(ctx, start, conversationID, GenerateGameID())
   861  }
   862  
   863  func (d *Dealer) startFlipWithGameID(ctx context.Context, start Start, conversationID chat1.ConversationID,
   864  	gameID chat1.FlipGameID) (pc *playerControl, err error) {
   865  	md := GameMetadata{
   866  		Initiator:      d.dh.Me(),
   867  		ConversationID: conversationID,
   868  		GameID:         gameID,
   869  	}
   870  	pc, err = d.newPlayerControl(d.dh.Me(), md, start)
   871  	if err != nil {
   872  		return nil, err
   873  	}
   874  	err = d.sendOutgoingChatWithFirst(ctx, md, pc, NewGameMessageBodyWithStart(start), true)
   875  	if err != nil {
   876  		return nil, err
   877  	}
   878  	err = d.sendCommitment(ctx, md, pc)
   879  	if err != nil {
   880  		return nil, err
   881  	}
   882  	return pc, nil
   883  }
   884  
   885  func (d *Dealer) sendCommitment(ctx context.Context, md GameMetadata, pc *playerControl) error {
   886  	return d.sendOutgoingChat(ctx, md, nil, NewGameMessageBodyWithCommitment(pc.commitment))
   887  }
   888  
   889  func (d *Dealer) sendOutgoingChat(ctx context.Context, md GameMetadata, me *playerControl, body GameMessageBody) error {
   890  	return d.sendOutgoingChatWithFirst(ctx, md, me, body, false)
   891  }
   892  
   893  func (d *Dealer) sendOutgoingChatWithFirst(ctx context.Context, md GameMetadata, me *playerControl, body GameMessageBody, firstInConversation bool) error {
   894  
   895  	gmw := GameMessageWrapped{
   896  		Sender:              d.dh.Me(),
   897  		Me:                  me,
   898  		FirstInConversation: firstInConversation,
   899  		Msg: GameMessageV1{
   900  			Md:   md,
   901  			Body: body,
   902  		},
   903  	}
   904  
   905  	// Only mark the forward bit to be true on messages that we can forward.
   906  	gmw.Forward = gmw.isForwardable()
   907  
   908  	// Reinject the message into the state machine.
   909  	d.chatInputCh <- &gmw
   910  
   911  	return nil
   912  }
   913  
   914  var DefaultCommitmentWindowMsec int64 = 3 * 1000
   915  var DefaultRevealWindowMsec int64 = 30 * 1000
   916  var DefaultCommitmentCompleteWindowMsec int64 = 15 * 1000
   917  var DefaultSlackMsec int64 = 1 * 1000
   918  
   919  // For bigger groups, everything is slower, like the time to digest all required messages. So we're
   920  // going to inflate our timeouts.
   921  func inflateTimeout(timeout int64, nPlayers int) int64 {
   922  	if nPlayers <= 5 {
   923  		return timeout
   924  	}
   925  	return int64(math.Ceil(math.Log(float64(nPlayers)) * float64(timeout) / math.Log(5.0)))
   926  }
   927  
   928  func newStart(now time.Time, nPlayers int) Start {
   929  	return Start{
   930  		StartTime:                    ToTime(now),
   931  		CommitmentWindowMsec:         inflateTimeout(DefaultCommitmentWindowMsec, nPlayers),
   932  		RevealWindowMsec:             inflateTimeout(DefaultRevealWindowMsec, nPlayers),
   933  		CommitmentCompleteWindowMsec: inflateTimeout(DefaultCommitmentCompleteWindowMsec, nPlayers),
   934  		SlackMsec:                    inflateTimeout(DefaultSlackMsec, nPlayers),
   935  	}
   936  }