已加固内容,
与尚未做到的差距
我们只说能证明的。已经做到的,自信地讲清楚;还没做到的,如实标成路线图——而不是用绝对化的话术盖过去。这一页就是那份清单。
权威可验证证据始终以 Game client CID、源码包 SHA256、source fingerprint、签名 hash-chain transcript 与本地 verifier 为准。
已实现的加固 已上线
以下能力已在代码中实现并通过单元测试;标 ✓ 者已在真实浏览器双人对局验证。
| 能力 | 说明 |
|---|---|
| 官方零裁判 · 零踢人 ✓ | 协议层没有"操作员踢人"事件类型,也没有中心化裁判。下注 / 弃牌 / 坐离均需玩家本人签名行动;无法继续时走全员共识作废+全额退还。中继切断 WebSocket 只触发客户端指数退避自动重连,不能改变牌局状态或夺座位、筹码。 |
| 浏览器即权威 · 刷新不掉桌 ✓ | 本地引擎与签名 transcript 是真相源头。重连后按 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 位数下限由 128 提升至 256(弱参数自动上调而非报错,同时消除"极小位数令客户端崩溃"的拒绝服务)。 |
| 每手统一记录哈希 | 用公开事件签名计算"接收者无关"的 canonicalHandHash,离线验证器暴露且与线上一致——任意两名玩家可比对各自记录、发现篡改或缺漏。 |
| 客户端 CID 校验显示 | 安全面板对比"运行中的 CID 与权威发布 CID",显示已锁定 / 不符 / 未从固定入口运行。 |
| 中继 token 移出 URL | 中继认证 token 改走 WebSocket 子协议而非 URL 查询串,避免进入代理 / CDN / 访问日志。 |
| 指纹覆盖全信任边界 | source fingerprint 现覆盖认证 / 传输 / 启动 / 身份等全部信任边界文件,不再只是发牌核心。 |
已知差距与路线图 路线图
以下为尚未实现的已知差距。它们多属协议级或研究级工程,需在受控环境充分测试后才能上线,因此我们如实披露,而非声称已解决。
| 差距 | 现状与计划 |
|---|---|
| 可验证洗牌证明 | 当前洗牌未附带零知识 / 重加密置换证明;验证器在摊牌阶段已对已揭示牌做重复明文检查,但无法在不揭示的前提下证明整副牌是合法置换。计划:引入可验证洗牌证明并纳入验证器。 |
| 登录抗离线爆破(部分) | 已实现:登录改发客户端派生的 authSecret,服务端不再接触明文密码、无法即时解开密钥库。尚未:完整 OPAQUE / SRP 以防止持库服务端对弱密码做离线爆破。计划:迁移到 OPAQUE / SRP。 |
| 签名跨手强绑定(部分) | 已实现:sessionNonce 会话域分离 + tableId 绑定与线上桌号强制拒收。尚未:handId / buildCid / expiry 强绑定与线上强制。计划:补全剩余域字段并在受控测试后启用。 |
| 桌面控制事件授权 | 开新局、改设置等控制事件尚无 host / 法定多数授权(协议层暂无"房主"概念)。计划:定义授权角色并在收发端与验证器一致校验。 |
| 验证器与实时逻辑统一 | 离线验证器与实时状态机尚未合并为同一套纯函数 reducer,可能存在合法事件顺序被验证器误判的边角情况。计划:统一为单一 reducer。 |
| 有限开源边界 | 出于防止整体复制的考虑,公开的是核心公平性审计包而非完整可一键构建的前端工程。我们提供逐文件哈希与信任边界清单,标明哪些文件在公平信任边界内。 |
诚实结论: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.
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. 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. |
| Browser-authoritative · refresh-proof seat ✓ | Your local engine and signed transcript are the source of truth on your own presence. After reconnect, sinceSeq replays missed messages and restores state; returning while it's your turn resumes that turn instead of being misclassified as "spectator / please sit back down". |
| 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 from 128 to 256 (weak parameters are clamped up rather than erroring, also removing a tiny-bits crash denial of service). |
| 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. |
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.
| Gap | Status and plan |
|---|---|
| Verifiable shuffle proof | Shuffles do not yet carry a zero-knowledge / re-encryption permutation proof. The verifier already checks for duplicate revealed cards at showdown, but cannot prove the full deck is a valid permutation without revealing it. Plan: add a verifiable shuffle proof and fold it into the verifier. |
| Offline-guess-resistant login (partial) | Shipped: login sends a client-derived authSecret, so the server never sees the plaintext password and cannot instantly open the vault. Not yet: full OPAQUE / SRP to stop a server that holds the database from offline-guessing weak passwords. Plan: migrate to OPAQUE / SRP. |
| Cross-hand signature binding (partial) | Shipped: sessionNonce domain separation plus tableId binding with live rejection. Not yet: strong binding of handId / buildCid / expiry with live enforcement. Plan: add the remaining domain fields and enable after controlled testing. |
| Table-control authorization | Control events (start new hand, change settings) have no host / quorum authorization yet — the protocol has no "table owner" concept. Plan: define an authorized role and check it consistently on senders, receivers, and the verifier. |
| Unify verifier and live logic | The offline verifier and the live state machine are not yet a single shared pure-function reducer, leaving edge cases where a legal event order could be misjudged offline. Plan: unify into one reducer. |
| Limited-open-source boundary | To prevent wholesale cloning, what's public is the core fairness audit package, not a complete one-click-buildable client. We publish per-file hashes and a trust-boundary manifest marking which files are inside the fairness boundary. |
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) — but it does not yet meet a strong, fully provable "neither operator nor players can cheat" standard. The roadmap items above are exactly where the gaps are. We disclose them rather than hide them behind absolute claims.