跳到正文
RandNext
Esc

可验证抽奖如何运作

每次抽奖都会公开足够的数据,让任何人都能重新计算中奖者。以下说明算法、信任模型以及它能够和不能够证明什么。

简要流程

  1. 组织者锁定参赛名单,RandNext 发布名单指纹和随机种子的承诺值。
  2. 组织者在抽奖前分享公开记录,参与者可以保存指纹和承诺值。
  3. 抽奖时公开种子和 drand 信标值,任何人都能重新计算中奖者。

1. 规范化名单

系统对每行执行 Unicode NFC 规范化、去除首尾空白和空行,并根据设置保留或合并重复项。顺序不会改变。

2. 种子与承诺

锁定时生成 32 字节安全随机种子,只公开其 SHA-256 承诺值。最终种子同时混入名单指纹、抽奖参数和未来的 drand 随机值。

VERSION      = "randnext-draw-v1"                       (ASCII)
entries_hash = SHA256(entries_bytes)                     32 bytes
server_seed  = 32 bytes from a CSPRNG                    sealed until the draw time
commitment   = SHA256(server_seed)                       published when the list is locked
params       = "id=" + draw_id + "\nwinners=" + k + "\nentries=" + n      (ASCII)
final_seed   = SHA256( UTF8(VERSION) || 0x00 || server_seed || entries_hash || UTF8(params) )

3. 公平抽取

算法使用 HMAC-SHA256、拒绝采样和部分 Fisher–Yates 洗牌,以无偏方式按排名选择中奖位置。

block(i) = HMAC-SHA256(key = final_seed, msg = UTF8(VERSION) || uint32_be(i)),  i = 0, 1, 2, …
stream   = block(0) || block(1) || …   read 4 bytes at a time as big-endian uint32

uniform(m):                            # an integer in [0, m), m ≤ 2^32
  limit = floor(2^32 / m) * m
  repeat u = next_uint32() until u < limit
  return u mod m

a = [0, 1, …, n-1]                     # partial Fisher–Yates
for i in 0 … k-1:
  j = i + uniform(n - i)
  swap(a[i], a[j])
winners = a[0 … k-1]                   # a[0] is 1st place

自行验证

可在浏览器验证器中输入抽奖 ID,也可以保存 JSON 记录和名单,使用 Python 参考脚本或自己的实现进行验证。

仍需信任什么

验证能证明中奖者来自锁定名单和公开算法,但不能证明组织者没有创建多次抽奖。因此参与者应在开奖前取得并保存记录链接。

算法版本

v2 使用未来的 drand 公共随机信标并验证签名;v3 用于多奖项现场抽取,每个奖项都有独立信标轮次,且同一人不能重复中奖。