Try   HackMD

zkShuffle: Mental Poker on SNARK for Ethereum

Background and Motivation

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

zkShuffle is our implementation of the Mental Poker scheme, a framework for playing card/board game without physical cards and without a trusted third party. For example, if you build a mental poker framework for a deck of 52 cards, you can almost write any poker games such as Texas Hold'em using only solidity.

For a comprehensive overview, Nicolas Mohnblatt from Geometry Research has a very nice series of articles (part-1, part-2).

However, Geometry Research's implementation uses Starknet curve, and is quite expensive even if we simply replace the Starknet curve using BN254. In this work, we propose a new mental poker design focusing on reducing gas cost on Ethereum so that everyone can play Texas Hold'em and Hearth Stone on Ethereum!

zkShuffle Scheme Overview

At a high level, zkShuffle can distribute and shuffle a deck of cards privately to each individual player. And a player can reveal a single card (or a subset of cards) on her hand when she plays her hand.

Note: our construction uses Groth16 on a pairing friendly curve. Thus, we use the following notation:

G: the group element in the embedded curve, i.e. Baby Jubjub
Fq
: the base field of
G

Fr
: the scalar field of
G

Suppose there are

k players (
P={p1,,pk}
) and a deck of
n
cards (
C=[c1,,cn]
). Our zkShuffle scheme consists of 4 functions:

  • Setup: The mental poker scheme provides a generator
    g:G
    . Each player
    pi
    generates a random secret key
    ski:Fr
    and uses the generator to produce her public key
    pki:G
    where:
    pki:=skig

    We also get an aggregated public key:
    pk:=(sk1++skk)g
  • shuffle_encrypt: To shuffle a deck of cards, every player need to take her turn to call shuffle_encrypt. A player
    pi
    takes a deck of cards
    Ci
    from previous player, and then shuffles and encrypts to produce a new deck of cards
    Ci+1
    . First, let
    A
    be a randomly sampled permutation matrix.
    pi
    can produce a deck of shuffled card:
    ACi

    Then,
    pi
    randomly sampled a vector
    Ri:Frn
    , and applied a homomorphic encryption scheme (ElGamal):
    Ci+1=ElGamal(g,pk,ACi,Ri)

Note: by the homomorphic property of ElGamal, one nice property of shuffle_encrypt is, the order of encryption is irrelevant to the result! After the shuffle and encryption of the deck,

pi posts the produced deck as well as a zero-knowledge proof of the validity of the shuffle and encryption on chain.

  • decrypt: A player
    pi
    takes a set of encrypted cards
    C
    to make one round of decryption. Let's ignore the detail now.
  • decrypt_post: A player
    pi
    takes a set of encrypted cards and calls decrypt. After decrypt, the player post the decrypted card as well as the validity proof of decryption on chain.

Intuition Behind ZKShuffle and How to Use It

Let's say Alice, Bob, and Charlie want to play a poker game together. The first step is that they need to shuffle a deck of encrypted cards together. We require each player join the shuffle so that none of any subset of the players can control the sequence of shuffled cards or encryption. Here, we need to use a homomorphic encryption scheme, to ensure the sequence of encryption and decryption would not affect the final result.

Shuffle the Deck

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

To shuffle the deck, we start from a deck of open card. Then, each player takes turns calling shuffle_encrypt to randomly shuffle the deck of card she received and apply the homomorphic encryption scheme ("add a lock" as shown in the above figure). At the end of the round, we achieve:

  1. a deck of shuffled and encrypted cards on chain
  2. every player has shuffled the deck once and encrypted each card once

Let's ask two questions:
Q1. Why every player needs to join the shuffle?
Since we don't want to trust anyone. Notice, after the first player, each player only shuffle the encrypted card deck. Essentially every player contribute the randomness of the final deck and unless all players collude, the shuffle is fair.

Q2. Why do we need homomorphic encryption?
Homomorphic encryption is needed since the order of encryption and decryption is irrelavent to the end encrypt/decrypt result. We need to leverage this property in the card dealing part.

Card Dealing

Card dealing handles 3 cases in the poker game:

  • Deal a card from the deck to a single player (say Alice).
  • A single player plays one card from her hand.
  • Deal a community card

Let's first look at how to deal a Card to Alice. Suppose we already have a shuffled and encrypted deck on chain. And now we are dealing the first card on the deck to Charlie. Below is the sequence of actons:

  1. Bob calls decrypt_post on the first card, which calls decrypt the first card and writes it back on chain
  2. Charlie calls decrypt_post on the card (notice, now this is the card Bob already calls decrypt_post), and write it back on chain.
  3. Alice fetch the card on chain, and calls decrypt (without writing it back on chain, otherwise everyone knows).
    Notice, after 2, only Alice's encryption (a.k.a. lock) is left on the card. So Alice can decrypt the card privately in her hand.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Now, suppose Alice wants to play the card just dealt to her from her hand. She reveals the card and uses zero-knowledge proof to show that this is indeed the card she decrypted from the encrypted card given to her.

And also dealing community cards is simply a full round of decrypt and the last person needs to reveal the card on chain!

Let's also ask three questions about dealing:
Q1. When dealing card to Alice, what prevents other players or anyone else from seeing Alice's card?
For the entire dealing sequence, only Alice can see the plain text after her final decrypt. Through the process, neither Bob, Charlie, nor blockchain validators can see her card.

Q2. Does the decrypt sequence have to be Bob->Charlie->Alice ?
No. Thanks to the homomorhpic encryption scheme we use, as long as Alice is the last one to decrypt the card, we are good!

Q3. When Alice plays her hand, what prevents her from cheating (a.k.a. playing a card that doesn't belong to her)?
That is why when Alice plays her hand, she cannot just reveal her card, she needs to submit a zero-knowledge proof to show the validity of her decryption on chain as well. We will get into details about when and where SNARK is needed next.

Why and Where SNARK is needed?

Both shuffle_encrypt and decrypt_post require the player to generate a zero-knowledge proof and submit the zero-knowledge proof together with the result.

In shuffle_encrypt, a zero-knowledge proof is needed to prove the validity of the shuffle and encryption. This is required to guarantee the soundness of the shuffle, for example, people don't put random content in the encrypted cards so that nothing can be decrypted. For not leaking shuffle order on chain, the proof generated in shuffle_encrypt needs to be strictly zero-knowledge.

In decrypt_post, a zero-knowledge proof is needed to prove the validity of the decryption. This prevents from the player posting wrong decryption results intentionally to gain an advantage in the game. The proof generated in decrypt_post needs to be zero-knowledge for not leaking player's secret key.

Detailed Construction

ElGammal Encryption

Setup

Given a randomly selected generator

g:G and each player's randomly selected secret key
ski:Fr
, we can produce an aggregated public key
pk:G

pk=(sk1++skk)g

Encrypt

This step has the following signature:

Encrypt(g:G,pk:G,m:G×G,r:Fr)G×G

The output is ciphertext

(c1:G,c2:G) where

c1=m[1]+rg

c2=m[2]+rpk

Decrypt

This step has the following signature:

decrypt(sk:Fr,C:G×G)G

The output is message

m where

m=c2skc1

Shuffle and Remask Argument on SNARK

Permutation Matrix

A matrix

M is a permutation matrix if

  • It is a square matrix. More specifically, supposing there are
    m
    rows and
    n
    columns, we have
    m=n
    .
  • Each element
    mi,j
    is either
    0
    or
    1
    .
  • The sum of each row and each column is exactly
    1
    . In other words, we have

imi,j=1,j{1,2,,n}

jmi,j=1,i{1,2,,n}

shuffle_encrypt circuit

For a poker game, there is a pre-defined number of cards

n.

Given

  • Public generator
    g:G
    and an aggregated public key
    pk:G
    .
  • Public matrices of masked cards
    X=[(x0,0,x0,1),(x1,0,x1,1),,(xn1,0,xn1,1)]

Y=[(y0,0,y0,1),(y1,0,y1,1),,(yn1,0,yn1,1)]

the prover proves the knowledge of

  • Private matrix
    A
    of shape
    n×n
  • Private vector of randomness
    R=[r0,r1,,rn1]

such that

  • A
    is a permutation matrix
  • B=A×X
  • Y=ElGamal.Encryption(g,pk,B,R)

Note:

ElGamal.Encrypt(g,pk,B,R) means
n
El Gamal encryption with individual
ri
.

Cost Analysis

We implement the circuit in Circom, the total R1CS constraint count is about

170,000.

decrypt_post circuit

Suppose a prover wants to decrypt the

ith card value.

Given

  • Public generator
    g:G
  • Public masked card
    yi=(yi,0,yi,1)
  • Current player's public key
    pkj=skjg

the prover proves the knowledge of

  • Current player's secret key
    skj

such that

  • pkj=skjg
  • out=yi,1skjyi,0

Cost Analysis

We program the circuit in Circom, the decrypt_post circuit consists of

3,500 R1CS constraints.