可验证抽奖如何运作
每次抽奖都会公开足够的数据,让任何人都能重新计算中奖者。以下说明算法、信任模型以及它能够和不能够证明什么。
简要流程
- 组织者锁定参赛名单,RandNext 发布名单指纹和随机种子的承诺值。
- 组织者在抽奖前分享公开记录,参与者可以保存指纹和承诺值。
- 抽奖时公开种子和 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 用于多奖项现场抽取,每个奖项都有独立信标轮次,且同一人不能重复中奖。