已加固内容,
与尚未做到的差距
我们只说能证明的。已经做到的,自信地讲清楚;还没做到的,如实标成路线图——而不是用绝对化的话术盖过去。这一页就是那份清单。
研究项目声明:Fair Poker 是非营利、纯兴趣的开源学术研究项目,研究德州扑克如何用密码学实现可验证公平;它不是对公众运营的游戏平台,不提供任何游戏服务。在线演示向公众开放,用于协议自测与体验;欢迎 下载全部源码 在自己的电脑上搭建研究——我们提供思路与代码,不提供服务。
权威可验证证据始终以 Game client CID、源码包 SHA256、source fingerprint、签名 hash-chain transcript 与本地 verifier 为准。
已实现的加固 已上线
以下能力已在代码中实现并通过单元测试;标 ✓ 者已在真实浏览器双人对局验证。
| 能力 | 说明 |
|---|---|
| 官方零裁判 · 零踢人 ✓ | 协议层没有"操作员踢人"事件类型,也没有中心化裁判。中继已降为哑中继:只排序、转发、报告谁在线,不计算座位 / 回合 / 赢家 / 筹码。下注 / 弃牌 / 坐离均需玩家本人签名行动;无法继续时走全员共识作废+全额退还。中继切断 WebSocket 只触发客户端指数退避自动重连,不能改变牌局状态或夺座位、筹码。 |
| 超时弃牌改"自我授权" ✓ | 想牌超时只会被你自己的客户端判弃牌(按本机时钟),或在你确已掉线时由他人替你弃牌以免卡桌;记分员不再用中继盖的时间戳判断超时。这样即便中继与某个座位合谋,也无法伪造一个"假超时"把正轮到、还没超时的对手强制盖牌、吞掉争议池——动钱的弃牌只认本人签名或客观掉线,碰不到在场对手。 |
| 浏览器即权威 · 刷新不掉桌 ✓ | 座位 / 底池 / 轮到谁 / 赢家 / 战绩全部由你的浏览器用同一套确定性 reducer 从签名事件日志算出,不依赖中继的房间状态——两个浏览器读同一份日志必然算出一致结果。重连后按 sinceSeq 重放未读消息把日志补齐,再从日志重新算出完全相同的状态;轮到你时回归直接续上 turn,不再被错误归类为"观战中 / 请重新坐下",下注也不会两边对不上。战绩本地留存,刷新不丢。 |
| 每手 4 灯公平校验 ✓ | 每局结束自动跑 4 项校验(牌堆完整 · 全员参与洗牌 · 记录指纹一致 · 签名齐全)并以覆盖层显示结果,通过盖"已验证"印,异常红灯并提示下载证据——主动告警层,可疑事件不需要玩家自己查 transcript。 |
| 断线规则极简 · 自愈或整桌作废 ✓ | 固定两条规则:可恢复掉线 → 无损自动恢复;密钥不可达 → 本手作废、全额退还、整桌结束建议换桌。没有"罚款 / 没收 / 操作员代踢"开关。建桌页与入场页都展示规则面板。 |
| WS 自动重连+消息重放 ✓ | WebSocket 断开 500ms→10s 指数退避自动重连(含抖动),重连按 sinceSeq 拉取未读消息恢复状态,不丢局、不丢 turn。 |
| 私有发牌端到端加密 ✓ | 私有 card/decrypt 的逐卡解密钥用收牌方公钥端到端密封(RSA-OAEP),并绑定 sender / recipient / round / cardOffset,重定向到其它人或卡位的密文会被拒收。中继只见密文。 |
| 解密钥只在本手期间暂存 ✓ | 逐卡解密钥仅在当前这一手牌进行期间暂存于本机(以便断线/刷新/关闭后重连恢复这一手),牌局一结束(分出胜负或作废)即删除,不长期保留。 |
| 认证不泄露密码 ✓ | 登录改为客户端 PBKDF2 派生 authSecret 发往服务端,密钥库密钥本地派生、永不出本机。服务端不再接触明文密码,无法即时解开密钥库冒充玩家。 |
| 签名绑桌号防跨桌重放 ✓ | 每个签名事件绑定 tableId,接收端保守拒收不属于本桌的事件。 |
| 默认拒收未签名事件 ✓ | GameRoom 默认 rejectUnsignedEvents = true,正式入口显式开启;未签名 wire 事件直接拒收,离线验证器拒掉未签名 Fair Poker v0 transcript 条目。 |
| 运行时事件校验 ✓ | 所有桌面事件(下注 / 弃牌 / 开局 / 设置)与发牌事件(牌堆 / 密钥)在进入状态机前做严格结构校验,畸形或越界输入被拒,杜绝异常输入导致的状态污染或崩溃。 |
| 下注金额校验 ✓ | 下注必须为非负安全整数,堵住 NaN 等异常值污染底池。 |
| 牌堆 / 密钥结构校验 ✓ | 牌堆须为 52 个合法整数密文,逐卡密钥与公钥须合法且长度受限,防止超大数解析造成的拒绝服务。 |
| 验证器事件覆盖 ✓ | 离线 transcript 验证器现已识别并校验全部桌面事件类型,不再静默跳过。 |
| 会话域分离与重放检测 ✓ | 每个签名会话绑定随机 sessionNonce;离线验证器据此检测同一会话内的序号重放与乱序。 |
| 密钥强度下限 ✓ | mental-poker SRA 位数下限提升至 1024(2048 位模数,离散对数不可行),并设 2048 位上限——弱参数自动上调、超大参数被夹住,同时消除"极小位数崩溃"与"超大位数卡死"两种拒绝服务;牌面还编码成二次剩余并避开 0/1 不动点(否则编码落在"1"的那张牌密文会恒为"1"、被零钥匙一眼认出),连残差指纹也不泄露任何隐藏牌。 |
| 每手统一记录哈希 ✓ | 用公开事件签名计算"接收者无关"的 canonicalHandHash,离线验证器暴露且与线上一致——任意两名玩家可比对各自记录、发现篡改或缺漏。 |
| 客户端 CID 校验显示 ✓ | 安全面板对比"运行中的 CID 与权威发布 CID",显示已锁定 / 不符 / 未从固定入口运行。 |
| 中继 token 移出 URL ✓ | 中继认证 token 改走 WebSocket 子协议而非 URL 查询串,避免进入代理 / CDN / 访问日志。 |
| 指纹覆盖全信任边界 ✓ | source fingerprint 现覆盖认证 / 传输 / 启动 / 身份等全部信任边界文件,不再只是发牌核心。 |
| 客户端可复现构建 ✓ | 公开的源码包是完整可构建的客户端源码;在版本锁定的 Docker 工具链里 npm ci 重新编译,得到的 IPFS CID 与线上 Game client CID 字节级一致——证明"你读到的公开源码"就是"线上真正在跑的程序",官方无法给你看一份干净代码、却偷偷在跑另一套。复现步骤见 独立验证指南。 |
已知差距与路线图 路线图
以下为尚未实现的已知差距。它们多属协议级或研究级工程,需在受控环境充分测试后才能上线,因此我们如实披露,而非声称已解决。对每一项,我们也写明它实际影响什么、可能的风险是什么、以及现有系统已经把多少风险兜住。
先记住一条:每手牌都自检。每手牌结束,你的浏览器会自动跑一遍公平验证器(牌堆完整 · 全员洗牌 · 记录指纹一致 · 签名齐全),并在摊牌时解出真实牌做重复明文检查。只要这手牌亮的是绿灯,无论下面这些研究级证明有没有上线,这一手都已被实测验证为合法——下面的差距改变的是"数学证明的强度",而不是"这手牌到底有没有被做手脚"。
| 差距 | 可能的风险 · 当前兜底 · 计划 |
|---|---|
| 可验证洗牌证明 |
实质作弊每手必被抓出
可能影响:理论上有人想做"假牌堆"或操纵发牌顺序。 当前兜底:三道关一起封死这条路——① 每个玩家都必须各自参与洗牌并加锁,没有任何单方(包括官方)能决定或得知牌序;② 定稿牌堆必须是 52 张互不相同的密文,做假牌堆会当场红灯;③ 每手摊牌时验证器解出真实牌再查有无重复明文、能否正确解密。只要这手牌 4 灯通过且无重复明文,这副牌就是被全员合法洗过的 52 张真牌。缺的只是"在不亮牌前用零知识数学证明合法置换",即把现在的"事后每手必抓"升级为"事前数学上不可能"。 计划:引入可验证洗牌证明并纳入验证器。 |
| 全下掉线的退款边界 |
不偷钱 · 仅"真掉线"可触发
可能影响:全下之后只剩翻公共牌时,一名快要输的玩家可以直接掉线、并扣住自己的解牌钥匙,使这手牌翻不出板而被判作废、双方退还本金——把"大概率会输"变成"退款"。 当前兜底:这不是偷钱(双方都退回本金、筹码不增不减);"在线状态下喊作废"的赖账已被完全封死(当作弃牌、照常输掉),只剩"真掉线"这一条。我们刻意保留"退款"而非"掉线就判负",因为后者会给掌控连线的官方一个新的作弊面(可在你钥匙广播前掐断连接逼你判负)——退款才是对玩家最安全的选择,并可在账号层对反复全下掉线做检测告警。 计划:引入零知识 / 可中途退出的发牌——让公共牌可凭公开承诺由在场各方独立翻出、无需逃跑者事后配合,从根上消除这条赖账(届时真掉线也照常分出胜负)。 |
| 登录抗离线爆破(部分) |
强密码下已兜住 · 不影响牌局
可能影响:只影响账号登录,且仅在"服务端被攻破、有人拿到密码库后慢慢离线猜测弱密码"时才有意义。不影响任何牌局、筹码或底牌。 当前兜底:登录只发客户端 PBKDF2 派生的 authSecret,服务端从不接触明文密码、密钥库密钥永不出本机,无法即时解开钥匙库冒充你;高轮次 PBKDF2 也大幅拖慢猜测。强密码基本完全免疫,残留风险只针对弱密码 + 已被攻破并持库的服务端。 计划:迁移到完整 OPAQUE / SRP,连弱密码的离线猜测也一并堵死。 |
| 签名跨手强绑定(部分) |
跨桌重放已封死
可能影响:把一条合法签名事件"重放"到别的桌、别的版本或过期后再用。 当前兜底:每条签名事件绑定 tableId 并在线上强制拒收不属于本桌的事件——跨桌重放已完全封死;sessionNonce 会话域分离 + 序号检测还能发现同一会话内的重放与乱序。尚未强绑定的是 handId / buildCid / expiry,残留的只是"同桌跨手"这类更窄的边角场景。 计划:补全剩余域字段并在受控测试后线上强制。 |
| 桌面控制事件授权 |
够不到筹码与发牌
可能影响:只影响"谁能按开新局、改设置这类桌面控制按钮"。不影响下注、弃牌、筹码与发牌——那些必须玩家本人签名,任何人都偷不走你的筹码、也改不了你的牌。 当前兜底:按零裁判设计,协议层本就没有"操作员 / 房主"特权;真正涉及钱与牌的行动全部需本人签名 + 浏览器为权威 + 无法继续时全员共识作废全额退还。所以这项缺失最多造成"控制按钮的权限边界不够明确",够不到筹码或牌面。 进展:已定义房主角色(开第一手的人即房主),并让离线验证器把"非房主擅自改设置 / 重开报名"在可复验记录里标注出来、可归因到具体签名者(CI 测试覆盖)。下一步是把这条授权在收发端也强制(现仅在验证侧标注,不在现场拦截,以免误伤刚刚重构过的开局/重开流程)。 |
| 验证器与实时逻辑统一 |
只会误报 · 不会漏判
可能影响:离线验证器与实时状态机暂未合并为同一套纯函数,理论上存在"某个合法事件顺序被离线验证器误判"的边角情况。 当前兜底:关键在于这种偏差只会让验证器把一手合法牌误报为可疑(宁可错杀),永远不会放过一次真正的作弊。也就是说它影响的是"会不会偶尔虚惊一场",而不是"会不会漏掉坏人"。 进展:现场端已统一为单一确定性 reducer——座位 / 底池 / 轮到谁 / 赢家全部由它从签名事件日志算出,中继不再参与。并已加一致性测试(CI 强制):把同一份签名记录同时喂给"可下载的离线验证器"和现场 reducer,断言两者算出的筹码结果逐分一致——弃牌结束与摊牌(含边池/比牌赢家)两条路径均已覆盖,二者永远不会悄悄分叉。剩余仅一项:把离线验证器与现场逻辑合并成同一份编译产物(需引入打包工具、并进可复现源码包,留待可验证一次发布时稳做)。 |
| 有限开源边界(非玩法页面) |
不影响公平性本身
可能影响:这不是安全漏洞,而是公开范围的取舍——可玩客户端(界面 + 公平内核 + 入口 + 静态资源)的完整可构建源码已经公开、并可字节级复现;仍未公开的只是首页、营销文案、AI 摘要页与后端运维这类不参与发牌、也不进入 Game client CID 的非玩法部分。影响的只是"能否一键重建整个站点的每个角落",不影响任何一手牌的公平性。 当前兜底:所有与公平性相关的核心文件都已开源;你可以在固定 Docker 环境里用公开源码重编出与线上一致的客户端 CID(见独立验证指南),并对照逐文件哈希、信任边界清单与运行中 CID 校验,核对你正在运行的就是公开审计过的那份代码。 计划:在不被整体抄袭的前提下持续扩大可复现构建范围,并引入外部签名发布日志与第三方复现见证。 |
诚实结论:Fair Poker 比传统黑箱发牌更可审计(WebCrypto 随机、事件签名、hash-chain transcript、公开 CID、端到端加密底牌、本地复验),并且每手牌都会自动自检、实质作弊会被当场抓出。但我们仍未达到"官方与玩家在数学上都无法作弊"的强可证明公平标准——需要强调的是,上述差距大多是把"每手都能被抓出"升级为"数学上不可能"的更高标准,而非当前存在可被利用的作弊通道。我们选择公开披露,而不是用绝对化措辞掩盖。
What's hardened,
and what isn't yet
We only claim what we can prove. What's done, we state plainly; what isn't, we mark honestly as roadmap — instead of papering over it with absolute language. This page is that list.
Research project notice: Fair Poker is a non-profit, hobbyist open-source academic research project on making Texas Hold’em verifiably fair with cryptography. It is not a game platform operated for the public and provides no gaming service; the live demo is open to the public for protocol self-testing. Download the full source and run your own instance — we share ideas and code, not a service.
Authoritative verifiable evidence is always the Game client CID, source archive SHA256, source fingerprint, signed hash-chain transcripts, and the local verifier.
Hardening shipped live
The following is implemented in code and covered by unit tests; items marked ✓ are also verified in a real two-browser game.
| Capability | What it does |
|---|---|
| Operator zero referee · zero kick power ✓ | The protocol has no "operator kick" event type and no centralized referee. The relay is now a dumb relay: it only orders, forwards, and reports who is online — it computes no seats / rounds / winners / chips. Bets / folds / sit-outs require the player's own signed action; only when continuation is impossible does a unanimous-void path refund the hand in full. The relay closing a WebSocket only triggers exponential-backoff client reconnect — it cannot change game state or seize a seat or chips. |
| Thinking-timeout fold is self-authorized ✓ | A thinking-timeout only folds you via your own client (on its local clock), or — when you are genuinely unreachable — by others so the table never freezes; the scorekeeper no longer uses the relay's timestamp to judge a timeout. So even a relay colluding with a seat cannot forge a "fake timeout" to fold an on-turn opponent who has not actually timed out and take the disputed pot — a money-moving fold requires the player's own signature or a genuine disconnect, and can never touch a present opponent. |
| Browser-authoritative · refresh-proof seat ✓ | Seats / pot / turn order / winner / history are all computed by your browser with the same deterministic reducer over the signed event log, never from relay room state — two browsers reading the same log necessarily agree. After reconnect, sinceSeq replays missed messages to complete the log, then the browser re-derives the identical state; returning while it's your turn resumes that turn instead of being misclassified as "spectator / please sit back down", and bets no longer disagree across clients. History is kept locally and survives a refresh. |
| Four-light fairness audit per hand ✓ | At the end of every hand, the browser automatically runs four checks (deck integrity · all players shuffled and locked · matching record fingerprint · signatures complete) and shows the result in an overlay. A pass stamps "verified"; a warn lights red and offers the evidence for download — an active alert on top of passive evidence. |
| Disconnect rule simplified · recover or void ✓ | Two fixed rules: a recoverable disconnect auto-recovers losslessly; if a needed decrypt key is unavailable the hand voids, every bet is refunded in full, and the table ends so players start a fresh room. No "penalty / forfeit / operator kick" knobs. The rules panel is shown on host setup and the joiner waiting view. |
| WebSocket auto-reconnect + replay ✓ | The WebSocket reconnects with capped exponential backoff (500 ms → 10 s, jittered) and replays missed messages by sinceSeq, so a blip never loses the hand or the turn. |
| End-to-end sealed dealing ✓ | Per-card decryption keys in private card/decrypt messages are end-to-end sealed to the recipient's public key (RSA-OAEP), bound to sender / recipient / round / cardOffset; ciphertext redirected to a different recipient or card position is rejected. The relay sees only ciphertext. |
| Decrypt keys kept only for the live hand ✓ | Per-card decrypt keys are kept on your device only for the duration of the hand currently in play (so a disconnect, refresh, or reopen can recover that hand), and are erased when the hand ends (settled or voided); never retained long-term. |
| Auth never exposes the password ✓ | Login sends a client-side PBKDF2-derived authSecret; the vault key is derived locally and never leaves your browser. The server never touches the plaintext password and cannot open your vault to impersonate you. |
| Signatures bound to table id ✓ | Every signed event is bound to its tableId; receivers conservatively reject events that don't belong to the table, blocking cross-table replay. |
| Reject unsigned events by default ✓ | GameRoom defaults rejectUnsignedEvents = true, the official setup path enables it explicitly, unsigned wire events are dropped, and the offline verifier rejects unsigned Fair Poker v0 transcript entries. |
| Runtime event validation ✓ | All table events (bet / fold / start / settings) and dealing events (deck / keys) are strictly structurally validated before reaching the state machine. Malformed or out-of-range input is rejected. |
| Bet amount validation ✓ | Bets must be non-negative safe integers, closing off NaN and similar values that could poison the pot. |
| Deck / key structural checks ✓ | The deck must be 52 valid integer ciphertexts; per-card keys and public keys must be valid and length-bounded, preventing huge-number parsing denial of service. |
| Verifier event coverage ✓ | The offline transcript verifier now recognises and checks every table event type, no longer silently skipping any. |
| Session domain separation ✓ | Each signing session is bound to a random sessionNonce; the offline verifier uses it to detect sequence replay and reordering within a session. |
| Key strength floor ✓ | The mental-poker SRA bit floor was raised to 1024 (a 2048-bit modulus, discrete-log infeasible) with a 2048-bit ceiling — weak parameters are clamped up and oversized ones are capped, removing both the tiny-bits crash and the huge-bits hang denial of service; cards are also encoded as quadratic residues shifted off the 0/1 fixed points (otherwise the card landing on "1" would always cipher to "1" and be identifiable with zero keys), so no residue bit leaks about any hidden card. |
| Per-hand consensus hash ✓ | A receiver-independent canonicalHandHash is computed from public event signatures and exposed by the offline verifier, identical to live — any two players can compare their records and detect tampering or omission. |
| Client CID verification display ✓ | The security panel compares the running CID against the authoritative release CID and shows locked / mismatch / not-pinned. |
| Relay token out of the URL ✓ | The relay auth token now travels in a WebSocket subprotocol instead of the URL query string, keeping it out of proxies, CDNs, and access logs. |
| Fingerprint covers the trust boundary ✓ | The source fingerprint now covers auth / transport / bootstrap / identity files — the full trust boundary, not just the dealing core. |
| Reproducible client build ✓ | The published source package is the complete buildable client source; recompiling it with npm ci inside a version-pinned Docker toolchain yields an IPFS CID that is byte-for-byte identical to the live Game client CID — proving the public source you read is exactly the code running live, so the operator cannot show clean code while running something else. Steps: verification guide. |
Known gaps on the roadmap roadmap
These are known gaps that are not yet implemented. Most are protocol-level or research-level work that must be thoroughly tested in a controlled environment before shipping, so we disclose them rather than claim they are solved. For each one we also spell out what it actually affects, what the possible risk is, and how much of that risk the existing system already contains.
First, one thing: every hand self-checks. At the end of each hand your browser automatically runs the fairness verifier (deck integrity · everyone shuffled · matching record fingerprint · signatures complete) and, at showdown, decrypts the real cards to check for duplicate plaintext. As long as that hand shows green, it has been actively verified as legitimate — whether or not these research-level proofs have shipped. The gaps below change the strength of the mathematical proof, not whether that hand was actually tampered with.
| Gap | Possible risk · what contains it today · plan |
|---|---|
| Verifiable shuffle proof |
Real cheating caught every hand
What it affects: in theory, someone trying to "stack the deck" or rig the deal order. Contained today: three gates already close this off — ① every player must each shuffle and lock, so no single party (including the operator) can set or learn the order; ② the finalized deck must be 52 distinct ciphertexts, so a stacked deck lights red on the spot; ③ at every showdown the verifier decrypts the real cards and checks for duplicate plaintext and correct decryption. If a hand passes its four lights with no duplicate plaintext, that deck was a genuine 52-card deck shuffled by everyone. What's missing is only a zero-knowledge proof of a valid permutation without revealing — i.e. upgrading today's "caught every hand, after the fact" to "mathematically impossible, before the fact". Plan: add a verifiable shuffle proof and fold it into the verifier. |
| All-in disconnect refund boundary |
no theft · only a genuine disconnect
Could affect: after an all-in, when only the board reveal remains, a player who is about to lose can disconnect and withhold their decryption keys so the board can't be revealed — the hand voids and both stakes are refunded, turning a likely loss into a refund. Current cover: this is not theft (both stakes returned, no chips minted or destroyed); the connected "claim a void" dodge is fully blocked (treated as a fold, the loss stands), leaving only a genuine disconnect. We deliberately keep "refund" rather than "disconnect = forfeit", because forfeiting would hand the connectivity-controlling operator a new attack surface (cut your link before your keys broadcast to force a loss) — refund is the player-safest choice, with account-level detection of repeated all-in disconnects. Plan: a zero-knowledge / abort-resistant deal — the board is recoverable from public commitments by the present players with no cooperation from the leaver, removing this dodge at the root (a genuine disconnect then still resolves normally). |
| Offline-guess-resistant login (partial) |
Contained for strong passwords · no game impact
What it affects: account login only, and only in the case where the server is breached and someone slowly offline-guesses weak passwords from the stolen database. It does not affect any hand, chips, or hole cards. Contained today: login only sends a client-side PBKDF2-derived authSecret; the server never touches the plaintext password, the vault key never leaves your device, and it cannot instantly open your vault to impersonate you. High-iteration PBKDF2 also greatly slows guessing. A strong password is essentially immune; the residual risk is only weak passwords plus an already-breached, database-holding server. Plan: migrate to full OPAQUE / SRP to also close off offline guessing of weak passwords. |
| Cross-hand signature binding (partial) |
Cross-table replay already closed
What it affects: replaying a legitimate signed event into a different table, a different build, or after it has expired. Contained today: every signed event is bound to its tableId and live-rejected if it doesn't belong to the table — cross-table replay is fully closed; sessionNonce domain separation plus sequence checks also catch replay and reordering within a session. Not yet strongly bound are handId / buildCid / expiry, so the residual is only narrow edge cases like "same-table, across hands". Plan: add the remaining domain fields and enforce them live after controlled testing. |
| Table-control authorization |
Cannot reach chips or dealing
What it affects: only "who may press table-control buttons like start-new-hand or change-settings". It does not affect bets, folds, chips, or dealing — those require the player's own signature, so no one can take your chips or change your cards. Contained today: by the zero-referee design the protocol has no "operator / table owner" privilege to begin with; everything involving money and cards requires your own signature, browser-authoritative state, and a unanimous void with full refund when play can't continue. So this gap can at most leave "the permission boundary on control buttons under-specified" — it cannot reach chips or card faces. Progress: the host role is now defined (the signer of the first hand is the host), and the offline verifier flags a non-host changing settings or reopening registration in the replayable record, attributable to the specific signer (covered by CI tests). Next is to also enforce it on senders/receivers (today it is flagged at verify time, not blocked live, to avoid disturbing the just-reworked start/restart flow). |
| Unify verifier and live logic |
Can only false-alarm · never miss
What it affects: the offline verifier and the live state machine aren't yet one shared pure function, leaving a theoretical edge case where a legal event order could be misjudged offline. Contained today: crucially, such a divergence can only make the verifier flag a legitimate hand as suspicious (fail-safe) — it can never let a real cheat through. So it affects "might we occasionally raise a false alarm", not "might we miss a bad actor". Progress: the live side is now a single deterministic reducer — seats / pot / turn / winner are all derived by it from the signed event log, with the relay no longer involved. A CI-enforced parity test now feeds the same signed record to both the downloadable offline verifier and the live reducer and asserts their chip outcomes are identical to the chip — both the fold-out and the showdown (side-pot / best-hand winner) paths are covered, so the two can never silently diverge. Only one item remains: merging the offline verifier and live logic into one compiled artifact (needs adding a bundler and baking it into the reproducible source package — left for a release we can verify). |
| Limited-open-source boundary (non-gameplay pages) |
Does not affect fairness itself
What it affects: this is not a security hole but a disclosure choice — the complete buildable source of the playable client (UI + fairness core + entry points + static assets) is public and byte-for-byte reproducible; what is still withheld is only non-gameplay parts — the homepage, marketing copy, AI summary pages, and backend operations — which do not deal cards and are not inputs to the Game client CID. It affects only "can you one-click rebuild every corner of the whole site", not the fairness of any single hand. Contained today: all fairness-critical core files are open; you can recompile the public source in a pinned Docker environment to a client CID identical to the live one (see the verification guide), and cross-check per-file hashes, a trust-boundary manifest, and a live CID check to confirm that what you're running is the same code that was publicly audited. Plan: keep widening the reproducibly-buildable scope without exposing the project to wholesale copying, and add an external signed publish log plus third-party reproduction witnesses. |
Honest bottom line: Fair Poker is far more auditable than black-box dealing (WebCrypto randomness, event signatures, hash-chain transcripts, public CIDs, end-to-end encrypted hole cards, local replay), and every hand self-checks so real cheating is caught on the spot. But it still does not yet meet a strong, fully provable "neither operator nor players can cheat" standard — and to be clear, most of the gaps above are about upgrading "caught every hand" to "mathematically impossible", not an exploitable cheating path that exists today. We disclose them rather than hide them behind absolute claims.