# CoronaPoker: a zero-trust Texas Hold'em table where the host cannot peek

> CoronaPoker is a peer-hosted No-Limit Hold'em game in Java whose Mental Poker protocol lets every player verify the shuffle. It suits private home games, not tournaments, and the docs leave some operational questions open.

**tonikelope/coronapoker** — In many ways, the poker game we always deserved.

- Repository: https://github.com/tonikelope/coronapoker
- Stars: 22 · Forks: 2
- Language: Java
- License: GPL-3.0
- Published: 2026-08-21 · Updated: 2026-08-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/tonikelope-coronapoker

## The problem CoronaPoker targets: a host you do not have to trust

Most online poker software asks you to trust the operator. CoronaPoker inverts that. The README states the project's single security goal directly: detect or prevent cheating, spying and tampering even when the host is not trusted. The author built it during the COVID-19 lockdown for a circle of friends, and the scope grew from a game into what the README calls a long-term software engineering laboratory.

The audience is narrow and specific. This is a No-Limit Hold'em implementation aimed at private home games rather than tournament structures, with 2 to 10 players in any mix of humans and other seats. There are no accounts, no central game server and no third-party hand log. If your group already trusts whoever hosts, the cryptography is overhead. If your group does not, or if you simply dislike the idea that one machine sees every hole card, this is the design point.

## How the zero-trust deck protocol actually deals a hand

The mechanism is a commutative Mental Poker protocol built on SRA, implemented over Ristretto255. Each card is shuffled and locked collectively by every human member of the hand's cryptographic ring. Every ring member then independently re-checks a zero-knowledge verifiable shuffle, so a malicious host cannot silently peek, duplicate or relocate a card in a completed deal. The README describes the construction as DLEQ-chained dealing with a Bayer-Groth shuffle.

Secrecy follows from the lock order. Pocket cards stay sealed end-to-end until showdown, and community cards unlock per street. No single participant, not even the host, can learn another player's hole cards or the board before its legitimate reveal.

Identity and history are layered on top. Each nick carries a persistent Ed25519 keypair stored locally with restricted permissions (POSIX 0600, or an owner-only Windows ACL). Betting actions, community and showdown reveals, straddle decisions, Rabbit requests, seat-draw commitments and closing receipts each use a distinct signed domain. A hash chain ratchets over each hand as H_{t+1} = SHA-256(record || sig), committing every peer to the exact action history, and the closing state folds in the hand's settlement.

Transport is separate from the deck. Client-to-host application traffic is encrypted with AES-256-CBC plus HMAC-SHA256 over keys negotiated by ECDH, so a network observer sees opaque authenticated frames rather than game state or chat. The recovery payload reader installs a strict ObjectInputFilter whitelist (HashMap, String and numeric boxes only, 10 MB cap, 20 levels deep) so a malicious host cannot hand a client arbitrary Java types to deserialize. That last detail is the kind of thing most hobby projects skip.

## Getting CoronaPoker: the release download is the documented route

The README does not document a build-from-source path. It points at the GitHub releases page, where the latest release is v24.10 (CoronaPoker 24.10). The repository does contain a Maven pom.xml, an .mvn directory and a coronaupdater module with a coronaupdater.jar at the top level, which suggests an update path, but the README does not explain how to run the updater or what it checks. Treat the release download as the supported route.

Because the README gives no launcher command, no install snippet and no configuration file, there is no command to quote here that would not be invented. The verified operational facts are these: the game listens on port 7234 by default, and the port is configurable per game. The README states that CoronaPoker tries to open the listening port on your router via automatic UPnP port mapping and cleans it up when the table closes, with manual port forwarding as the fallback. If UPnP is disabled on your router, forward the port yourself before inviting anyone.

Hosting a table is a dialog flow rather than a config file. The README describes an optional password for the table itself, on top of channel encryption. There is no documented YAML or TOML file to edit, so do not go looking for one.

Joining is the other half. The Join dialog keeps a persisted recent-server list of past tables, browsable with the up and down arrow keys, so a returning player reconnects to a known host without retyping an address. A player invited mid-recovery watches the in-progress hand as a passive observer with no cards dealt and no actions requested, then joins normally on the next hand.

## Crash recovery is the part that will bite you in a real home game

Every hand is checkpointed to a local SQLite database: per-action history, balances, dealer, small blind and big blind, plus a crypto fossil holding the full cascaded deck and the keys needed to re-derive your hole cards. A game can resume from the exact stop point after a crash, power loss or reboot, for host and clients alike.

The commit discipline is stricter than the feature list suggests. Hand creation commits the hand row and its complete unique balance roster atomically and publishes the generated id only after commit. Normal close and MISDEAL commit metadata and the exact final or refunded roster atomically. Any failed row rolls the operation back and prevents advancement. An interrupted hand keeps its durable hand number, and if showdown had already completed, recovery advances to the following hand number on both the table and the game log.

Conservation is enforced before durable close. Every peer checks that paid plus closing carry equals contributed plus opening carry, and exchanges signed receipts with the currently expected human ring members. A conservation error, a divergent or missing expected receipt, or an invalid signature stops SQLite close and hand advancement, preserving the open state for recovery. That is the correct behaviour for a money game and an unpleasant one for a casual table: a single peer with a corrupted local state can stall the hand until the state is resolved. The README does not document a rollback or a manual override for that stall, which is a gap worth knowing about before you host.

## Identity continuity: TOFU, CHANGED, and why nothing stops to ask you

Key trust is trust-on-first-use. The first key seen for a nick is recorded. A later, different key is accepted as CHANGED, replaces the stored key, increments the session count and clears the out-of-band verification flag.

Read that sequence carefully. The transition is non-blocking and has no modal. There is no dialog that stops the table and asks whether the new key is really your friend. The README's own advice is to compare the identity identicon or fingerprint out-of-band whenever key continuity matters. In practice that means a side channel: a phone call, a message in a group chat, a screenshot.

If your group will not do that, the identity layer degrades to a display feature. The signature domains still bind actions to a key, and the hash chain still commits the action history, but a substituted key is accepted silently and the verification flag is simply cleared. This is a deliberate trade of friction for continuity, and it is the weakest link in an otherwise carefully argued security story.

## Disconnects, chat floods and other operational limits

Reconnection has a defined shape. If a player drops, a 45-second base grace window holds their seat. Once the peer's reauthenticated reconnect intent reaches the host, the window extends to 80 seconds so a flaky link gets a second chance before the table asks whether to remove them. The host also tracks round-trip latency and reconnection count per seat and broadcasts it, so a bad link surfaces before it becomes a dispute.

Chat is throttled client-side to a minimum of 0.5 seconds between messages. That is a sender-side throttle, so it constrains the polite client and not a modified one.

Context-aware responses cover the protocol layer. Structural SRA failures, forged community reveals and unsafe early-cascade requests trigger immediate lockdown or MISDEAL. An invalid signed betting ACTION is neutralised as a deterministic synthetic fold, recorded in the hand flags, and causes receipt consensus to reject the durable close. Each response is logged with its precise reason, which matters if you ever have to reconstruct what happened.

The honest limits: this is a Java desktop client, not a browser game, and the README documents no mobile client. There is no central server, so the host's machine and its network are the table's availability. And the README does not cover tournament structures at all, by its own framing.

## How it differs from a normal online poker client

The obvious comparison is any conventional online poker room: a central operator runs the server, shuffles the deck, holds the accounts and keeps the hand history. You get zero setup, a browser or mobile client, and a trusted third party. CoronaPoker removes the third party and the shuffle authority in one move, and pays for it with setup: a Java runtime, a host machine, a forwarded port, and a group willing to compare fingerprints.

A second comparison is a plain peer-to-peer card game with no cryptography, where the host's client simply deals. That is simpler to run and much easier to write. It also means the host can see everything. CoronaPoker's entire architecture exists to make that impossible, and the verifiable shuffle plus per-action signatures plus receipt consensus are the price of that property.

A third point of contrast is the repository's own split. The README describes a game, but the structure (docs/SECURITY.md, a tools directory, a separate coronaupdater module, a robert_rules.pdf at the top level) reads like a protocol project that happens to ship a poker table. If you want a poker game, that is a lot of machinery. If you want a worked example of Mental Poker with signed action domains and crash-consistent settlement, the code is the interesting artifact.

## Licence, maintenance and the cost of upgrading

CoronaPoker is GPL-3.0. If you fork it, redistribute it or ship a modified client to your group, the copyleft terms apply to the derived work. Running it privately among friends is the normal case and does not raise the question. Nothing here is legal advice; read LICENSE and, for anything commercial, talk to someone qualified.

The repository is not archived, and the last push was on 2026-08-26, the same date as the v24.10 release. The README does not document a version compatibility policy between clients and hosts, which is the practical upgrade question: if one player updates and another does not, the README is silent on whether the handshake still succeeds. The presence of coronaupdater.jar and a coronaupdater module suggests an intended update mechanism, but the README does not describe how it is invoked, what it fetches, or whether it verifies signatures.

The upgrade cost is therefore mostly social. Because identity keys live locally and the SQLite checkpoint holds per-hand state, an upgrade mid-session is the risky moment. Finish the hand, close the table, then update everyone together.

## Conclusion

CoronaPoker fits private home games among people who can compare Ed25519 fingerprints out-of-band and who accept a Java client with no central server. It does not fit tournament structures, mobile-only players, or anyone who wants an account system and a hosted table. Before hosting, verify that you can open or forward port 7234, that your group will actually compare identity fingerprints, and that the build you download comes from the latest GitHub release.

## FAQ

### Does CoronaPoker hide my hole cards from the host?

Yes. The README states that no single participant, not even the host, can learn another player's hole cards or the board before its legitimate reveal, because each card is locked collectively by every human member of the hand's cryptographic ring. Pocket cards stay sealed until showdown and community cards unlock per street.

### Which port does CoronaPoker use when hosting?

The listening port defaults to 7234 and is configurable per game. The README also states that CoronaPoker tries to open the port on your router automatically via UPnP and cleans it up when the table closes, with manual port forwarding as a fallback.

### What happens in CoronaPoker if a player disconnects mid-hand?

A 45-second base grace window holds their seat, and once the peer's reauthenticated reconnect intent reaches the host the window extends to 80 seconds before the table asks whether to remove them. The host also tracks round-trip latency and reconnection count per seat and broadcasts it.

### Can a CoronaPoker game resume after a crash or power loss?

Yes. Every hand is checkpointed to a local SQLite database with per-action history, balances, blinds and a crypto fossil containing the cascaded deck and the keys needed to re-derive your hole cards, so host and clients can resume from the exact stop point. An interrupted hand keeps its durable hand number.

### Is CoronaPoker suitable for running a tournament?

The README describes it as a rules-correct No-Limit Hold'em implementation focused on private home games rather than tournament structures. The feature table covers 2 to 10 players, any mix of humans and other seats, and does not document tournament formats.

## Sources

- [Official README](https://github.com/tonikelope/coronapoker#readme)
- [Project repository](https://github.com/tonikelope/coronapoker)
- [Release notes](https://github.com/tonikelope/coronapoker/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tonikelope-coronapoker
