← Back

Protocol

Initial Handshake

  1. Decide which player is Player 1 and which player is Player 2. In a networked card game, a simple method is to name the player listening for connections Player 1 and the player who who initiated the connection Player 2.
  2. Generate SEED_LENGTH bytes of random data and share it with the other player.
  3. shared_seed is SEED_LENGTH * 2 bytes, consisting of Player 1’s seed followed by Player 2’s seed.
  4. Generate RAND_LENGTH bytes of random data and store it privately as local_rand_seed.
  5. Players create their decks of NUM_CARDS_IN_DECK cards, storing them as an array of integers (implementation-defined size) in local_deck_initial.
  6. Compute HASH_FUNC(shared_seed + local_rand_seed + local_deck_initial) (where + denotes concatenation) and send it to the other player, storing the received data as remote_handshake_hash.
  7. If card backs differ, generate an array of NUM_CARDS_IN_DECK integers (implementation-defined size) specifying the card backs and send it to the opposing player.

Per-Turn Handshake

  1. Generate RAND_LENGTH bytes of random data and store it privately as this turn’s shared_rand_seed[idx], where idx is the player number (1 or 2).
  2. Compute HASH_FUNC(shared_seed + shared_rand_seed[idx]) and send it to the other player.
  3. After both players have received the other player’s hash, send shared_rand_seed[idx], storing the received value in the other slot of shared_rand_seed.
  4. Verify that the hash matches the received data.
  5. Compute HASH_FUNC(shared_seed + shared_rand_seed[1] + shared_rand_seed[2]) and store it privately as shared_rand_state.
  6. Compute HASH_FUNC(shared_rand_state + local_rand_seed) and store it privately as local_rand_state.
  7. Set both shared_rand_idx and local_rand_idx to 0.

Ending Handshake

  1. Send local_rand_seed + local_deck_initial to the other player.
  2. Verify that remote_handshake_hash is correct.
  3. For each turn, verify that the actions taken were valid.

Appendix A: Random Number Generation

To generate a random number in [0, n):

  1. Find the lowest number b such that n <= 256^b (where ^ denotes exponentiation).
  2. Find the highest number m such that m is a multiple of n and m <= 256^b
  3. Call advanceRNGMulti(b) until the result is less than or equal to m.
  4. Return the result of advanceRNGMulti modulo n.

advanceRNGMulti(b) is defined as:

  1. If b = 0: return 0. Otherwise:
  2. Store advanceRNGMulti(b - 1) as v.
  3. Return v + advanceRNG() * 256^(b - 1). (Bitwise OR is equivalent to addition here.)

advanceRNG() is defined as:

  1. If *_rand_idx is less than the length of *_rand_state:
  2. Store the *_rand_idx’th byte of *_rand_state in x.
  3. Add 1 to *_rand_idx.
  4. Return x.
  5. Otherwise:
  6. Set *_rand_idx to 0.
  7. Update *_rand_state to nextRNGState(*_rand_state).
  8. Return the 0th byte of *_rand_state.

nextRNGState(s) is defined as:

  1. Return HASH_FUNC(shared_seed + s) (where + denotes concatenation).

Appendix B: Shuffling

Basic shuffle is defined as the pseudocode:

for i from 1 to (num_items - 1):
    j := random number in [0, i)
    swap items[i] and items[j]

Deck shuffle consists of:

  1. Sort cards in the deck into groups based on their backs, keeping their original order.
  2. Perform a basic shuffle on the card backs using the shared random number state.
  3. For each card back type, in a deterministic order, perform a basic shuffle on the group using the local random number state.
  4. For each card back in the sequence generated by step 2, put the first unused card in that card back’s group into the card back’s sequence position.