The subtitle of zkShuffle is "Mental Poker on SNARK for Ethereum." The term Mental Poker was introduced in a 1979 paper by Adi Shamir, Ron Rivest, and Leonard Adleman. It explores whether a fair poker game can be played online without physical cards while ensuring the following three principles:
Hide the card values from all players
Ensure that the cards are dealt correctly, with fair randomness
Reveal a card to a specific player or group of players
zkShuffle is a protocol that distributes and shuffles cards among players. It primarily follows the Barnett and Smart structure, which has also been improved and implemented by Geometry.
The key novelty of zkHoldem compared to Geometry's implementation is that it utilizes Groth16 to implement the shuffle argument more efficiently. For further details, interested readers can refer to the Geometry blog.
Modified ElGamal Encryption
ModifiedElGamal.Setup()→(sk,pk)
Randomly select a secret keysk in the range 1≤sk<q−1.
Compute the public keypk as follows:
pk=sk⋅G
where G is a generator of an additive group of a large prime size.
When called for the first time, m1=0 and m2=m, the message. This value is used for nested encryption.
A random scalar r where 1≤r<q−1 is used to determine the shared secret s.
s=r⋅pk
Generate the ciphertext (c1,c2) as follows:
c1=m1+r⋅G,c2=m2+r⋅pk
ModifiedElGamal.Decrypt(c1:G,c2:G,sk:F,r:F)→m:G
c1 is computed using the value r that was used when calling ModifiedElGamal.Encrypt. Note that this c1 computed individually by each party is different from the iterative c1 from .Encrypt.
c1=r⋅G
Compute the plaintext m as follows:
m=c2−sk⋅c1
Properties of Modified ElGamal
Consider a scenario where Alice, Bob, and Carol each have private keys sk0,sk1,sk2 and their corresponding public keys pk0,pk1,pk2. Now, let's examine the encryption process for a card m.
After this process, the final ciphertext c is generated as follows:
c=((r0+r1+r2)⋅G,m+r0⋅pk0+r1⋅pk1+r2⋅pk2)
At this stage, Alice, Bob, and Carol can decrypt in any order to retrieve the original message m. Suppose they decrypt in the order Bob → Carol → Alice:
One of the key features of Modified ElGamal is its order-independent decryption property.
This is particularly useful in blockchain environments, where:
Decryption results need to be submitted as on-chain transactions (tx).
The ordering of transactions within a block is unpredictable.
Since decryption can be performed in any order, this significantly enhances the usability of the protocol in decentralized systems.
Shuffling
In Texas Hold'em, a deck of 52 cards must be shuffled, which needs to be mathematically represented. For example, suppose we start with a set of four cards:
C0=(c0,c1,c2,c3)
If Alice wants to shuffle them into the following order:
C1=(c0,c2,c1,c3)
how can we express this mathematically?
This can be represented using a permutation matrix as a matrix multiplication:
Consider a scenario where k players P={p0,…,pk−1} participate, and there are n cards in the game. Each player pi is responsible for shuffling an encrypted deck Ci=(ci,0,…,ci,n−1)∈(G×G)n. Here, n is 52.
Where:
G is an elliptic curve group, such as Baby Jubjub.
F is the scalar field of the elliptic curve.
G is the generator of the elliptic curve group.
All cards are given publicly known values, for example:
C0=((0,0),(0,G),(0,2⋅G),…,(0,(n−1)⋅G))
Thus, for 0≤j<n, if j⋅G is given, the corresponding card can be determined.
zkShuffle.Setup
Each player pi randomly generates a secret keyski∈F.
The public keypki∈G is computed as follows:
pki=ski⋅G
zkShuffle.ShuffleEncrypt
Player pi generates a random permutation matrixAi∈Fn×n.
Upon receiving deck Ci=(ci,0,…,ci,n−1) from player pi−1, the shuffled deck Si+1=(si+1,0,…,si+1,n−1) is computed as follows:
Si+1=Ai⋅Ci
Player pi generates random values ri,j∈F (for 0≤j<n).
Player pi encrypts the shuffled deck Si+1 into Ci+1 where each card is calculated as follows (for 0≤j<n):
Each player pi decrypts a received encrypted card c:G×G to obtain m:G as follows (The reason shuffling does not need to be considered during decryption is that if we assume the cards were shuffled using zkShuffle.ShuffleEncrypt, the final recovered message (m) will always be one of the elements in C0.):
m=ModifiedElGamal.Decrypt(c[0],c[1],ski,r)
zkShuffle.DecryptPost
Each player pi decrypts an encrypted card c:G×G to obtain m:G:
m=ModifiedElGamal.Decrypt(c[0],c[1],ski,r)
The player generates a ZK-proof for the following circuit and submits it along with m in a transaction:
Since the contract already has the shuffled encrypted cards, it knows which card is at the top of the deck and which card needs to be decrypted. It verifies whether the public input corresponds to the correct card for decryption.
Game Flow
Shuffling Cards
Cards are given publicly known values.
All players shuffle the cards using zkShuffle.ShuffleEncrypt.
Distributing Two Cards to Each Player
Cards are dealt from the top of the deck to players in a predetermined order.
For all dealt cards that do not belong to them, players callzkShuffle.DecryptPost.
After step b is finished by every player, players reveal their own cards to themselves with zkShuffle.Decrypt.
Revealing Three Community Cards
The top three cards from the deck are placed on the table.
All players call zkShuffle.DecryptPost for these cards, revealing them.
Revealing Additional Community Cards (Two Rounds)
One additional card from the top of the deck is placed on the table in each round.
All players call zkShuffle.DecryptPost for these cards, revealing them.
Revealing Player Cards
Players who remain until the end reveal their cards using zkShuffle.DecryptPost.
How Are the Three Principles Ensured?
Hide the card values from all players.
Cards are encrypted using zkShuffle.ShuffleEncrypt. Therefore, unless all players participate in decryption, they cannot access the original card value.
Ensure that the cards are dealt correctly, with fair randomness.
Before the game starts, all players contribute randomness to shuffle the cards. Even if some players collude, the deck remains randomly shuffled.
zkShuffle.ShuffleEncrypt ensures that all players have encrypted the shuffled cards according to the shuffling rules using ZK.
From the shuffle ZK proof, the contract knows the order of the deck.
If a player attempts to decrypt a card that is not at "the top of the deck", the contract will reject the request.
Reveal a card to a specific player or group of players.
Revealing to one player
A player's card is revealed to them when calling zkShuffle.Decrypt after all other players have run zkShuffle.DecryptPost.
Since zkShuffle.Decrypt does not submit the result to the contract, other players cannot see their cards.
Revealing to all players
Once every player calls zkShuffle.DecryptPost on a card, it is revealed.
Conclusion
The Circom code for zkHoldem is private, but according to the paper:
zkShuffle.DecryptPost includes 3,500 constraints.
zkShuffle.ShuffleEncrypt includes 170,000 constraints.
Using ZK for online Texas Hold'em is an exciting concept.
However, there are some limitations:
Receiving a single card requires multiple interactions with other players for decryption.
Even if a player folds, they must stay online to complete the decryption process.
These challenges highlight key areas for improvement in real-world applications.