透明度 · Transparency

已加固内容,
与尚未做到的差距

我们只说能证明的。已经做到的,自信地讲清楚;还没做到的,如实标成路线图——而不是用绝对化的话术盖过去。这一页就是那份清单。

权威可验证证据始终以 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、端到端加密底牌、本地复验),但尚未达到"官方与玩家在数学上都无法作弊"的强可证明公平标准。上述路线图项即为差距所在。我们选择公开披露,而不是用绝对化措辞掩盖。

Transparency

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.

CapabilityWhat 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 validationAll 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 validationBets must be non-negative safe integers, closing off NaN and similar values that could poison the pot.
Deck / key structural checksThe 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 coverageThe offline transcript verifier now recognises and checks every table event type, no longer silently skipping any.
Session domain separationEach signing session is bound to a random sessionNonce; the offline verifier uses it to detect sequence replay and reordering within a session.
Key strength floorThe 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 hashA 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 displayThe security panel compares the running CID against the authoritative release CID and shows locked / mismatch / not-pinned.
Relay token out of the URLThe 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 boundaryThe 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.

GapStatus and plan
Verifiable shuffle proofShuffles 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 authorizationControl 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 logicThe 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 boundaryTo 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.