5.4 KiB
抽奖与公平性算法技术白皮书
1. 概述
本系统采用 「承诺机制 (Commitment Scheme)」 结合 HMAC-SHA256 算法,确保抽奖过程的不可预测性、可验证性和不可篡改性。
核心原则:
- 事前承诺:活动开始前生成随机种子并公布其哈希值(Commitment)。
- 事后验证:活动结束后公布种子明文(Reveal),用户可复算验证。
- 确定性算法:输入(种子 + 上下文)确定,输出必然唯一。
2. 核心机制:承诺方案
2.1 种子生成
每个活动 (Activity) 在创建或发布时,系统服务器端会生成一个高质量的 32 字节随机种子 (ServerSeed)。
// 伪代码示例
seed := make([]byte, 32)
rand.Read(seed) // 使用 crypto/rand 生成强随机数
2.2 承诺哈希 (Commitment Hash)
在数据库确立活动数据的一瞬间,系统计算种子的 SHA256 哈希值,作为承诺存储并对用户可见(虽然前端可能选择性展示)。
SeedHash = \text{SHA256}(ServerSeed)
此哈希值一经生成不可更改,确保了服务器无法在后续过程中偷偷替换种子来操纵结果。
2.3 验证凭据 (Receipt)
每次抽奖完成后,系统会生成一份数字凭据 (ActivityDrawReceipts),其中包含:
issue_id: 期号seed_hash: 对应的种子哈希nonce/salt: 随机盐值或防重随机数snapshot: 当时的奖池状态快照(权重/格位)
3. 算法实现
3.1 无限赏 (Weighted Random)
适用于奖品无限库存或按权重概率抽取的模式。
算法流程:
-
输入:
Seed: 全局活动种子IssueID: 期号UserID: 用户IDSalt: 每次请求生成的 16 字节随机盐值Rewards: 奖品列表,包含权重w_i
-
随机数生成: 使用 HMAC-SHA256 派生出一个确定性的随机数 $R$。
\text{payload} = \text{fmt.Sprintf("draw:issue:\%d|user:\%d|salt:\%x", IssueID, UserID, Salt)}H = \text{HMAC-SHA256}(Seed, \text{payload})R = \text{BigEndianUint64}(H[0:8]) \pmod {\sum w_i} -
结果选择: 遍历奖品列表,累加权重查找
R落在哪个区间。
代码逻辑:
mac := hmac.New(sha256.New, seedKey)
mac.Write([]byte(fmt.Sprintf("draw:issue:%d|user:%d|salt:%x", issueID, userID, salt)))
sum := mac.Sum(nil)
rnd := int64(binary.BigEndian.Uint64(sum[:8]) % uint64(totalWeight))
3.2 一番赏 (Ichiban / Shuffle)
适用于“箱内抽赏”模式,奖品总量固定,位置固定,采用先洗牌后抽取的逻辑。
算法流程:
-
输入:
Seed: 全局活动种子IssueID: 期号(每一个箱子是一个 Issue)TotalSlots: 总格位数(例如 80 发)Rewards: 初始有序的奖品列表(填充后的平铺列表)
-
确定性洗牌 (Deterministic Shuffle): 使用 Fisher-Yates 洗牌算法,配合 HMAC-SHA256 生成的随机序列对奖品位置进行打乱。
对于
i从TotalSlots-1到 $1$:\text{payload}_i = \text{fmt.Sprintf("shuffle:\%d|issue:\%d", i, IssueID)}H_i = \text{HMAC-SHA256}(Seed, \text{payload}_i)j = \text{BigEndianUint64}(H_i[0:8]) \pmod {(i+1)}交换索引
i和j的元素。 -
结果获取: 用户选择的格位号
k(1-based) 对应洗牌后数组的索引k-1处的奖品。Reward = ShuffledRewards[SelectedSlot - 1]
特性:
- 预定性:只要种子确定,箱子那一刻的奖品排列就已注定,不论谁来抽、何时抽,第 N 格永远是那个奖品。
- 公平性:HMAC 的均匀分布保证了洗牌的随机性。
4. 验证指南
为了验证系统的公平性,用户或监管方可以使用官方提供的验证工具(VerifyTool.exe)进行独立计算。
4.1 获取验证参数
从 API 或页面获取以下信息(活动结束后公开):
server_seed_hex: 服务器种子(十六进制)issue_id: 期号user_id&salt: (仅无限赏需要)slot_index: (仅一番赏需要)reward_config: 奖品配置列表(验证前需要构建相同的初始列表)
4.2 运行验证工具
无限赏验证命令示例:
./VerifyTool verify-unlimited \
--seed "WaitToReveal32BytesHex..." \
--issue 1001 \
--user 12345 \
--salt "RandomSaltHex..." \
--weights "10,50,200,500"
一番赏验证命令示例:
./VerifyTool verify-ichiban \
--seed "WaitToReveal32BytesHex..." \
--issue 2002 \
--slot 5 \
--rewards "A:2,B:4,C:10,D:64"
(注:rewards 格式为 奖项:数量,如 A赏2个, B赏4个...)
4.3 验证原理
验证工具内置了与服务器完全相同的算法逻辑(Go 源码编译)。输入相同的种子和上下文,必将输出相同的中奖结果。
5. 安全性声明
- 种子保密:
ServerSeed在存储层加密保存,仅在活动结束或特定审计时刻解密公开。 - 结果不可逆:无法通过哈希值反推种子。
- 防预测:
- 无限赏:引入了
Salt(真随机生成),即使用户猜到了种子,也无法预测下一次抽奖结果(因为 Salt 每次不同)。 - 一番赏:种子一旦确定,序列即确定。我们在活动开始前才生成种子和 Commitment,确保无人(包括管理员)能提前知晓排列。
- 无限赏:引入了