← 返回博客

·

德州扑克如何公平发牌:随机洗牌、牌组承诺与底牌隐私

一手牌如何洗出、底牌怎样直达玩家、服务器什么时候能看到牌?用当前默认密封发牌实现,解释每一层技术保证与信任边界。

一手德州扑克,管理下注的服务器真的需要提前知道所有人的底牌吗?BluffKing 当前默认实现,把洗牌和底牌交付交给独立的发牌进程,让牌局服务器按规则推进游戏,而不在正常交付路径中经手真人未公开底牌。

本文只讲德州扑克当前默认的“密封发牌”,不涵盖其他游戏。这里的服务器不可见,准确地说是正常运行中的牌局主进程不经手真人未公开底牌。独立发牌进程仍知道整副牌;运营者仍须被信任。它不能被宣传成“所有服务端组件在密码学上都无法看牌”。

公平发牌,需要解决三个不同的问题

第一,牌序是否来自可靠的随机过程;第二,结束时返回的牌组材料是否与开局承诺一致;第三,未公开的牌面有没有进入不需要知道它的代码。

默认实现分别用密码学安全随机数、牌组哈希承诺和进程隔离回答这三个问题。它们各有作用,也各有边界。把它们说清楚,比笼统地说一句“绝对公平”更有意义。

第一步:用安全随机数洗出一副牌

发牌进程为每手牌从操作系统安全随机源取得 32 字节,也就是 256 位的种子,再用 ChaCha20 随机数生成器产生洗牌所需的随机数。ChaCha 随机数库文档将这类生成器定义为密码学安全随机数生成器。

随后,程序对标准的 52 张牌执行 Fisher–Yates 洗牌:从后往前,随机选取一个尚在范围内的位置,与当前位置交换。算法在每一步选择均匀的前提下,能产生均匀的随机排列;当前随机索引实现使用拒绝采样,避免直接取模造成偏差。

这意味着,正确运行的实现不会根据玩家身份或下注动作临时决定下一张牌。整个牌序在这一手创建时就已生成。安全性依赖随机源、算法和实际运行代码的正确性;仅凭随机算法的名字,不能证明恶意运营者没有改过程序或提前筛选牌组。

第二步:先给牌组留下不可随意替换的“指纹”

发牌进程生成牌序后,会计算一个 SHA-256 承诺。可以把它理解成封好牌盒时留下的数字指纹:

承诺 = SHA-256(
  协议标识 + 本手 ID + 随机种子 + 随机 nonce + 完整牌序
)

这个指纹随开局信息交给牌局服务器,也会随玩家的领牌信息传到客户端。手牌结束、完整材料返回时,系统检查它是否恰好包含 52 张互不重复的合法牌,并重新计算承诺,与开局值核对。

在安全哈希假设下,事后替换结束时返回的牌组材料,却仍让它通过原承诺核对,在计算上不可行。NIST 的安全哈希标准也将消息摘要用于检测生成摘要后消息是否发生变化。

哈希是核对材料是否一致的工具。它不是加密牌面的工具,也不能证明没有人提前看过牌。当前结束检查核对的是完整材料与开局承诺的一致性,不能单独排除承诺之前的选牌行为,更不等于玩家已经独立验证了整套公平性。当前结束检查没有把实战中交付的底牌、已公开的公共牌和摊牌逐一与返回牌组交叉核对,也没有从种子重新洗牌比对;实际打出的牌确实来自所承诺牌组,仍依赖发牌进程正确执行。

第三步:底牌直接送到玩家,不绕经牌局主进程

这套架构把工作拆成两个进程。牌局进程负责轮到谁行动、下注是否合法、底池如何分配;发牌进程负责生成和保管牌组、交付底牌,以及在适当阶段提供公开牌。

创建一手牌时,牌局进程正常拿到的是本手对应的承诺和各座位领取凭证。它把凭证交给对应玩家;玩家客户端再建立通往发牌进程的独立 WebSocket 连接,凭证领取自己的两张底牌。

  1. 发牌进程生成并保管牌组,将承诺与座位领取凭证交给牌局进程。
  2. 牌局进程把领取凭证交给对应玩家。
  3. 玩家客户端凭证连接发牌进程,直接领取自己的两张底牌。

客户端收到牌后,会核对本手 ID、座位、承诺,以及两张牌的取值和不重复性。它能借此拒绝串手、串座位或格式错误的数据;仅检查承诺字符串一致,并不等于单凭两张牌就能验证整副牌的承诺。

关键变化是:真人底牌不再需要经过牌局主进程的正常明文交付路径。引擎可以先用不含牌面的占位来表示这些底牌,继续处理下注。接触未公开牌面的代码范围因此缩小,减少误用和意外暴露的机会。

不提前知道底牌,仍然能正常打完一手牌

跟注、加注、弃牌和底池计算,都主要依据公开行动。真正需要牌面的时刻,再取回对应信息:

阶段牌局进程正常取得的信息
开局承诺、领取凭证;含机器人时另取机器人自己的底牌。
翻牌、转牌、河牌当前阶段需要公开的公共牌。
摊牌需要参与比牌的座位底牌。
结束后完整牌组及承诺核对材料,用于持久化、局后复盘与教练分析。

机器人只通过专门接口取得已登记机器人座位的底牌;该接口拒绝读取真人座位。局后取得完整牌组,也不代表把所有人的弃牌公开给全桌,用户历史查询仍有各自的权限边界。

这里的保密承诺有明确时间范围:行动进行时,按正常路径限制真人未公开底牌进入主进程;牌局结束后,主进程可以取得完整材料。

发牌失败时,不能靠泄露底牌来补救

如果发牌服务、底牌交付或取牌流程出现无法继续的错误,当前手会进入作废和退还本手投入的处理,而不是为了打完这一手把整个明文牌组塞回普通发牌路径。后续手牌仍选择密封发牌,连续失败则会关闭牌桌。

这描述的是封存发牌失败的处理,不是“玩家一断网就一定退款”。普通重连、行动超时和发牌失败,需要按各自的实际状态处理。

这套设计的保证,具体到哪里?

它带来的技术约束很明确:安全随机数用于生成不可轻易预测的牌序,承诺用于检查结束材料是否与开局一致,进程隔离和直接交付用于缩小真人未公开底牌的接触范围。

但发牌进程知道整副牌,牌局服务也持有可用于领牌的凭证。发牌进程依据通过内部认证的指令公开公共牌、执行结束操作,并不独立验证完整下注过程。因此,一个控制全部服务的运营者仍有能力突破这些正常流程;玩家也无法仅凭页面远程证明线上运行的就是某份源码。

准确的产品承诺应该是:在正确运行当前默认实现时,真人未公开底牌不经过牌局主进程的正常明文交付路径,正常实现于创建时确定牌组,而结束检查只证明返回材料与开局承诺一致;它不独立证明实际交付过的每张牌都与该牌组相符。每一项约束都有具体代码负责,而它们共同依赖的运营者信任边界,也应当同样清楚。