Skip to content

dpinones/liars-dice

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Liar's Dice — Bluffing with ZK Proofs on Stellar

Liar's Dice is a PvP bluffing game on Stellar where two players secretly roll dice, bid on the total count of a face across both hands, and call each other's bluffs. Zero-knowledge proofs keep hands hidden while guaranteeing fair play — no trust required. Lose a die each time you're wrong; lose them all and the game is over.

Game Board

Watch Demo on YouTube


Game Rules

Both players start with 5 dice. Each round:

  1. Dice are rolled secretly — nobody sees the other player's dice
  2. Before bidding, each player receives the sum of the opponent's dice (without the individual values)
  3. Players take turns bidding: "There are at least N dice showing face X across both hands"
  4. Each bid must exceed the previous one (higher quantity, or same quantity with a higher face)
  5. Stars (face 1) are wildcards — they count as any face, except when bidding on stars
  6. Instead of bidding, you can call "Liar!" — counts are revealed and the round resolves:
    • If the actual total is equal to or greater than the bid → the challenger loses a die
    • If the actual total is less than the bid → the bidder loses a die

The player who reaches 0 dice loses.


How It Works

Each round follows a 5-phase cryptographic protocol:

Game Round Flow

1. Nonce Commit

Each player generates a random nonce and commits Poseidon2(nonce, 0, 0, 0) to the contract. The nonce stays secret. It's committed before the seed exists — so neither party can influence the roll.

2. Dice Roll — ZK Proof #1

The contract generates a random seed via PRNG. Each player derives their dice deterministically:

die[i] = (last_byte(Poseidon2(seed, nonce, i, 0)) % 6) + 1

Then generates a ZK proof (dice_roll circuit) that proves:

  • The nonce matches the commitment from phase 1
  • Each die was correctly derived from seed + nonce + index
  • The dice commitment (Poseidon2 hash of the packed set) is valid

Verified on-chain. Nobody chooses their dice.

3. Reveal Sum — ZK Proof #2

Before bidding begins, both players generate a ZK proof (dice_sum circuit) that proves the total sum of their dice without revealing individual values.

Each player sees the opponent's sum as strategic information: if the opponent's sum is 25 (with 5 dice), they probably have high dice. If it's 8, probably low. But the exact values remain secret.

This is what ZK enables and commit-reveal cannot: proving an aggregate property of private data without exposing the data.

4. Bidding

Players take turns bidding: "There are at least N dice showing face X across both hands."

Rules:

  • Each bid must exceed the previous one (higher quantity, or same quantity with a higher face)
  • Stars (face 1) are wildcards — they count as any face, except when bidding on stars
  • Each player sees the opponent's sum (revealed in phase 3) to inform their strategy
  • When someone believes the bid is a bluff → "Liar!"

5. Reveal Count — ZK Proof #3

Both players generate a ZK proof (dice_count circuit) that proves:

  • The dice match the commitment from phase 2 (they can't be swapped)
  • The count of dice matching the challenged face is exactly N
  • Stars are correctly counted as wildcards

The contract sums both counts:

  • total >= bid_quantity → the bid was correct, the challenger loses a die
  • total < bid_quantity → it was a bluff, the bidder loses a die

When a player reaches 0 dice, the game ends. Otherwise, new round.


The Noir Circuits

commitment — Poseidon2 Hash (client-side only, no proof)

Utility for computing Poseidon2 hashes. Used for nonce and dice commitments.

dice_roll — Fair Dice Derivation

Type Inputs
Public nonce_commitment, seed, dice_commitment, num_dice
Private nonce, dice[5], dice_nonce

The circuit enforces: the nonce matches its commitment, each die is derived from Poseidon2(seed, nonce, i, 0), active dice are in [1,6], inactive ones are 0, and the dice commitment is valid.

The contract extracts public inputs from the proof blob and validates them against the game state:

Public Input Contract Validation What It Prevents
nonce_commitment Must match the commitment stored in phase 1 Using a different nonce to manipulate the roll
seed Must match game.seed Using a different seed to get more favorable dice
dice_commitment Must match the argument sent in the TX Decoupling the commitment from the proof
num_dice Must match the player's current dice count Proving with more dice than they have

dice_sum — Sum Without Revealing the Hand

Type Inputs
Public dice_commitment, claimed_sum, num_dice
Private dice[5], dice_nonce

The circuit enforces: the commitment matches the one from the roll phase, the sum of active dice is exactly claimed_sum, active dice are in [1,6], and inactive ones are 0.

The contract validates:

Public Input Contract Validation What It Prevents
dice_commitment Must match the commitment stored in phase 2 Proving the sum of different dice than committed
claimed_sum Extracted and stored for the opponent
num_dice Must match the player's current dice count Summing more or fewer dice than they have

dice_count — Count Without Revealing the Hand

Type Inputs
Public dice_commitment, face_value, claimed_count, num_dice
Private dice[5], dice_nonce

The circuit enforces: the commitment matches the one from the roll phase, the count is computed correctly (including the stars wildcard rule), and all values are in valid range.

The contract validates:

Public Input Contract Validation What It Prevents
dice_commitment Must match the commitment stored in phase 2 Swapping dice between the roll and the reveal
face_value Must match game.challenge_face Proving the count for a different face than challenged
claimed_count Extracted and used for resolution
num_dice Must match the player's current dice count Counting more or fewer dice than they have

ZK vs Commit-Reveal

Commit-reveal can solve fair dice derivation: the player commits hash(nonce), the contract generates a seed, and when revealing the nonce anyone can re-derive the dice. For the basic game loop (derivation + challenge), commit-reveal works.

Where ZK becomes necessary is when you want to prove partial properties of private data without exposing the data. In Liar's Dice, this manifests in the dice sum:

  • Each player proves the sum of their hand before bidding
  • The opponent sees the total (e.g., "sum = 16") but not the individual values
  • This gives real strategic information without destroying the hand's secrecy

With commit-reveal, revealing any property means revealing the nonce, which exposes all the dice. There's no mechanism for proving a partial aggregate.

Capability Commit-Reveal ZK Proofs
Fair dice derivation Yes Yes
Prove counts during a challenge Yes (revealing the whole hand) Yes (revealing nothing)
Prove the sum without revealing individual values No Yes
Partial property verification No Yes

Security

Attack Vector Defense
Choose your own dice Deterministic derivation Poseidon2(seed, nonce, i) enforced by the circuit. Seed post-commit, nonce pre-seed.
Lie about your count dice_count proof verified on-chain against committed dice.
Lie about your sum dice_sum proof verified on-chain against committed dice.
Swap dice between phases All three proofs reference the same dice_commitment.
Reuse an old proof Public inputs include round-specific seed and nonce_commitment.
Front-run the seed The nonce is committed before the contract generates the seed.
Forge a proof UltraHonk verified on-chain with circuit-specific verification keys.

Getting Started

Prerequisites

# Install dependencies
bun install

# Start local Soroban network
stellar container start local --limits unlimited

# Full deploy (fund + compile circuits + deploy contracts + update .env)
make deploy

# Dev server
bun run dev

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors