Onchain Texas Hold'em poker on Stellar with cryptographically private cards using ZK-MPC (coSNARKs).
No single party ever sees the full deck. A 3-node MPC committee (TACEO coNoir) shuffles and deals cards using REP3 secret sharing. UltraHonk ZK proofs verify every deal, reveal, and showdown onchain via Soroban's native BN254 host functions.
Live Demo · Demo Video · Slide Deck
Stellar Poker is built directly on top of the cryptographic primitives introduced in Stellar's recent protocol upgrades:
- Protocol 25 (X-Ray) introduced native BN254 elliptic-curve operations and Poseidon2 hashing as Soroban host functions. The
zk-verifiercontract uses these to verify UltraHonk proofs onchain at a fraction of the cost of a pure-Rust implementation. - Protocol 26 (Yardstick) added multi-scalar multiplication, scalar-field arithmetic, and curve-membership checks. These are used inside the verifier's Shplemini opening scheme, which is the most compute-intensive part of UltraHonk verification.
Without these host functions, onchain UltraHonk verification would exceed Soroban's instruction budget. Protocol 25 and 26 are what make this project possible.
ZK proofs can verify that a computation was done correctly, but they cannot keep inputs secret from the prover. In a card game, the entity generating the proof would necessarily know all cards. REP3 secret sharing across multiple MPC nodes ensures no single party — including the coordinator — ever holds the full deck. The slide deck above covers this in detail.
Player A Player B
| |
+------+ +--------+
| |
[Web App] Next.js frontend
|
[Coordinator] Orchestrates MPC sessions (Axum)
/ | \
[Node0] [Node1] [Node2] TACEO coNoir MPC nodes (REP3)
|
[Soroban] Onchain settlement
/ | \
[PokerTable] [ZKVerifier] [CommitteeRegistry]
Supports up to 6 players. Includes a solo mode against a deterministic AI opponent.
- Private cards — Cards exist only as REP3 secret shares across 3 MPC nodes. Privacy holds as long as at least 2 nodes are honest.
- ZK-verified — Deal, reveal, and showdown proofs are UltraHonk proofs verified onchain via Soroban's native BN254 host functions (Protocol 25).
- Trustless settlement — All bets, pot calculation, and payouts are handled entirely in Soroban smart contracts.
- Reusable library —
stellar-zk-cardsis a standalone Rust crate for card encoding and hand evaluation that any Soroban app can use.
| Contract | Address | |---|---|---| | Poker Table | CA3RAB66WJ3OONO4OFYEATISRZA3ND65MN3DET5IE2XXSZZMPKH3CHAV | | Committee Registry | GBTYELEQ2YZH2W6SXLHT4AX6TYBHHU7LNNPKJV7J37VS3S5GPA75KRDU |
Works alongside the Stellar Game Studio deployed at CB4VZAT2U3UC6XFK3N23SKRF2NDCMP3QHJYMCHHFMZO7MRQO6DQ2EMYG.
stellar-poker/
contracts/
poker-table/ Main game contract (betting, state machine, settlement)
zk-verifier/ UltraHonk proof verification (BN254 native ops)
committee-registry/ MPC committee management and slashing
game-hub/ Mock Game Hub contract (Stellar Game Studio interface)
circuits/
lib/ Shared Noir library (cards, commitments, Merkle)
deal_valid/ Proves deck shuffle + deal consistency
reveal_board_valid/ Proves community card reveals match committed deck
showdown_valid/ Proves winner has the best hand
stellar-zk-cards/ Reusable card-game library crate (encoding, hand eval)
services/
coordinator/ Axum HTTP server orchestrating MPC sessions
node/ MPC node (TACEO coNoir participant)
app/ Next.js web frontend
tests/ Integration tests
vendor/ Vendored UltraHonk verifier
scripts/ Build, deploy, and test scripts
docker-compose.yml Full-stack local development
| Component | Technology |
|---|---|
| Smart contracts | Soroban (Rust, soroban-sdk 22.0.0) |
| ZK proofs | Noir circuits + UltraHonk (Barretenberg) |
| MPC | TACEO coNoir (REP3, 3-party) |
| Hash function | Poseidon2 |
| Frontend | Next.js 15, Freighter wallet |
- Rust (stable)
- Nargo 1.0.0-beta.17 —
noirup -v 1.0.0-beta.17 - Node.js 18+
- Docker
- Stellar CLI —
cargo install stellar-cli --features opt - co-noir (for CRS download) —
cargo install --git https://github.com/TaceoLabs/co-snarks co-noir
# Install dependencies and verify build
./scripts/setup.sh
# Download BN254 common reference string
./scripts/download-crs.sh
# Start full stack
docker-compose upThen open http://localhost:3000.
# Check all Rust crates
cargo check
# Run contract tests
cargo test -p poker-table
# Compile and test Noir circuits
./scripts/compile-circuits.sh
cd circuits/lib && nargo test
# Run the frontend
cd app && npm run dev
# Run integration tests (requires docker-compose up)
python3 scripts/test-flow.pyNETWORK=testnet ./scripts/deploy.shStaging mirrors production with a dedicated coordinator, three MPC nodes, and a staging frontend, while still settling against Soroban testnet.
cp .env.staging.example .env.staging
# fill in staging contracts, keys, identities, and public URLs
NETWORK=staging OUTPUT_ENV_FILE=.env.staging.deploy ./scripts/deploy.sh
# copy the generated contract IDs from .env.staging.deploy into .env.staging
./scripts/deploy-staging.shThat flow starts the staging services and runs the integration suite against staging before production promotion. The staging runbook lives in docs/staging-environment.md.
Before deploying to mainnet, complete every item below:
- Fund deployer account — The deployer account needs at least ~100 XLM to cover contract deployment fees and minimum reserve. Export the secret key:
export DEPLOYER_SECRET=S... # secret key of your funded Stellar account
- Download the CRS — The BN254 Common Reference String must be present on every machine that generates proofs (coordinator and all MPC nodes):
./scripts/download-crs.sh
- Complete the MPC committee key ceremony — Each of the three node operators must generate their REP3 key share independently. Key shares must never reside on the same machine. Coordinate this out-of-band before registering the committee onchain.
- Compile circuits — Verification keys embedded in the
zk-verifiercontract must match the compiled circuit artifacts:./scripts/compile-circuits.sh
- Provision MPC node infrastructure — All three nodes must be running and reachable at their public endpoints before
register_membercalls are made. - Back up the deployer key — Contract admin operations (upgrades, key rotation) require the deployer secret. Store it securely offline.
export DEPLOYER_SECRET=S... # funded mainnet account
NETWORK=mainnet ./scripts/deploy.sh| Variable | Default | Description |
|---|---|---|
NETWORK |
testnet |
testnet or mainnet |
SOROBAN_RPC |
auto | Soroban RPC URL (auto-selected per network) |
SOROBAN_NETWORK_PASSPHRASE |
auto | Stellar network passphrase (auto-selected per network) |
DEPLOYER_SECRET |
— | Required for mainnet. Secret key (S...) of the funded deployer account. |
For production infrastructure, including a self-hosted Soroban RPC stack with Stellar Core, Horizon, tuning, and monitoring guidance, see docs/soroban-rpc-node.md.
Mainnet deployment has not been performed yet. This table will be updated once contracts are deployed to the Stellar public network.
| Contract | Address |
|---|---|
| Poker Table | — |
| ZK Verifier | — |
| Committee Registry | — |
- Create table — Admin creates a
PokerTablecontract with config (blinds, buy-in range, timeout). - Join — Players join with a buy-in (tokens escrowed in contract).
- Start hand — Triggers the MPC committee to shuffle and deal.
- Deal — Committee generates a
deal_validZK proof, commits deck Merkle root + hand commitments onchain, privately delivers hole cards to each player. - Betting — Players submit actions (fold / check / call / bet / raise / all-in) to the contract.
- Reveal — After each betting round, committee reveals community cards with a
reveal_board_validproof. - Showdown — Committee reveals remaining hands, generates a
showdown_validproof, contract settles the pot.
- Private:
deck[52],salts[52](secret-shared in MPC) - Public:
deck_root,hand_commitments[6],dealt_indices - Proves: Valid 52-card deck, Merkle root matches commitments, hand commitments match dealt cards.
- Private:
deck[52],salts[52] - Public:
deck_root,revealed_cards,revealed_indices,previously_used_indices - Proves: Revealed cards match committed deck, no indices reused.
- Private:
hole_cards,board_cards,salts - Public:
hand_commitments,board_commitments,declared_winner - Proves: Cards match commitments, hand evaluation is correct, winner has best hand.
The live demo at stellar-poker-six.vercel.app runs against Stellar testnet. Here is exactly what is live and what is simulated:
| Component | Status | Notes |
|---|---|---|
| Soroban contracts | ✅ Live on testnet | Poker table, ZK verifier, committee registry all deployed |
| ZK proof verification | ✅ Live onchain | zk-verifier contract verifies real UltraHonk proofs via BN254 host functions |
| Frontend | ✅ Live | Hosted on Vercel, connects to testnet via Freighter wallet |
| Solo mode (vs AI) | ✅ Fully functional | Deals, betting, showdown, and settlement all work end-to-end |
| MPC nodes (multiplayer) | 3 TACEO coNoir nodes run for demo purposes; in production these would be independently operated | |
| Multiplayer (2–6 players) | ✅ Functional | Requires all players to have Freighter and testnet XLM |
The ZK proofs in solo mode are generated and verified onchain in every hand — this is not mocked.
The technical patterns in this project are directly applicable to real-world use cases:
- MPC committee + ZK verification — the same architecture works for any multi-party secret: sealed-bid auctions, private voting, threshold key custody, blind RFQ matching in DeFi.
stellar-zk-cards— the card encoding, commitment, and hand evaluation library is a standalone Rust crate any Soroban app can use for any card or tile-based game.- Onchain UltraHonk verifier — the
zk-verifiercontract is a general-purpose Noir proof verifier. Any Noir circuit can be verified through it by swapping the verification key. - REP3 secret sharing via coNoir — the node and coordinator services are structured to be reused for any coSNARK application, not just poker.
StellPoker includes a built-in feature-flag system for gradual rollouts. No third-party service is required — flags are stored in memory and controlled through environment variables and a runtime admin API.
| Flag key | Env variable | Purpose |
|---|---|---|
new_circuits |
FEATURE_FLAG_NEW_CIRCUITS |
Enable experimental ZK circuit versions |
contract_upgrade |
FEATURE_FLAG_CONTRACT_UPGRADE |
Gate new Soroban contract function calls |
experimental_ui |
FEATURE_FLAG_EXPERIMENTAL_UI |
Signal the frontend to use new UI |
chat_enabled |
FEATURE_FLAG_CHAT_ENABLED |
Enable in-table WebSocket chat |
solo_mode |
FEATURE_FLAG_SOLO_MODE |
Allow solo / bot-opponent table creation |
Set any flag via an environment variable before starting the coordinator:
FEATURE_FLAG_SOLO_MODE=1 FEATURE_FLAG_CHAT_ENABLED=1 cargo run -p coordinatorAccepted truthy values: 1, true, yes, on. Everything else is false.
Append _TABLE_<id> or _PLAYER_<stellar-address> to scope an override:
# Enable chat only on table 3
FEATURE_FLAG_CHAT_ENABLED_TABLE_3=1
# Give one specific player access to experimental UI
FEATURE_FLAG_EXPERIMENTAL_UI_PLAYER_GABC...=1Resolution order: per-player → per-table → global (most-specific wins).
The coordinator exposes two endpoints for live flag management without a restart:
| Method | Path | Description |
|---|---|---|
GET |
/api/flags |
List all current flag values |
POST |
/api/flags/:key |
Set a flag ({"enabled": true|false}) |
# Check current flags
curl http://localhost:8080/api/flags
# Enable solo_mode at runtime
curl -X POST http://localhost:8080/api/flags/solo_mode \
-H 'Content-Type: application/json' \
-d '{"enabled": true}'
# Enable chat only for table 5 (scoped key)
curl -X POST http://localhost:8080/api/flags/chat_enabled.table.5 \
-H 'Content-Type: application/json' \
-d '{"enabled": true}'import { useFeatureFlags, isFlagEnabled } from "@/lib/use-feature-flags";
export function CreateTableButton() {
const { flags, loading } = useFeatureFlags();
if (loading || !isFlagEnabled(flags, "solo_mode")) return null;
return <button>Create Solo Table</button>;
}