# 抽奖与公平性算法技术白皮书 ## 1. 概述 本系统采用 **「承诺机制 (Commitment Scheme)」** 结合 **HMAC-SHA256** 算法,确保抽奖过程的**不可预测性**、**可验证性**和**不可篡改性**。 核心原则: 1. **事前承诺**:活动开始前生成随机种子并公布其哈希值(Commitment)。 2. **事后验证**:活动结束后公布种子明文(Reveal),用户可复算验证。 3. **确定性算法**:输入(种子 + 上下文)确定,输出必然唯一。 --- ## 2. 核心机制:承诺方案 ### 2.1 种子生成 每个活动 (`Activity`) 在创建或发布时,系统服务器端会生成一个高质量的 32 字节随机种子 (`ServerSeed`)。 ```go // 伪代码示例 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) 适用于奖品无限库存或按权重概率抽取的模式。 **算法流程**: 1. **输入**: - $Seed$: 全局活动种子 - $IssueID$: 期号 - $UserID$: 用户ID - $Salt$: 每次请求生成的 16 字节随机盐值 - $Rewards$: 奖品列表,包含权重 $w_i$ 2. **随机数生成**: 使用 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} $$ 3. **结果选择**: 遍历奖品列表,累加权重查找 $R$ 落在哪个区间。 **代码逻辑**: ```go 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) 适用于“箱内抽赏”模式,奖品总量固定,位置固定,采用先洗牌后抽取的逻辑。 **算法流程**: 1. **输入**: - $Seed$: 全局活动种子 - $IssueID$: 期号(每一个箱子是一个 Issue) - $TotalSlots$: 总格位数(例如 80 发) - $Rewards$: 初始有序的奖品列表(填充后的平铺列表) 2. **确定性洗牌 (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$ 的元素。 3. **结果获取**: 用户选择的格位号 $k$ (1-based) 对应洗牌后数组的索引 $k-1$ 处的奖品。 $$ Reward = ShuffledRewards[SelectedSlot - 1] $$ **特性**: - **预定性**:只要种子确定,箱子那一刻的奖品排列就已注定,不论谁来抽、何时抽,第 N 格永远是那个奖品。 - **公平性**:HMAC 的均匀分布保证了洗牌的随机性。 --- ## 4. 验证指南 为了验证系统的公平性,用户或监管方可以使用官方提供的验证工具(`VerifyTool.exe`)进行独立计算。 ### 4.1 获取验证参数 从 API 或页面获取以下信息(活动结束后公开): 1. `server_seed_hex`: 服务器种子(十六进制) 2. `issue_id`: 期号 3. `user_id` & `salt`: (仅无限赏需要) 4. `slot_index`: (仅一番赏需要) 5. `reward_config`: 奖品配置列表(验证前需要构建相同的初始列表) ### 4.2 运行验证工具 **无限赏验证命令示例**: ```bash ./VerifyTool verify-unlimited \ --seed "WaitToReveal32BytesHex..." \ --issue 1001 \ --user 12345 \ --salt "RandomSaltHex..." \ --weights "10,50,200,500" ``` **一番赏验证命令示例**: ```bash ./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. 安全性声明 1. **种子保密**:`ServerSeed` 在存储层加密保存,仅在活动结束或特定审计时刻解密公开。 2. **结果不可逆**:无法通过哈希值反推种子。 3. **防预测**: - 无限赏:引入了 `Salt`(真随机生成),即使用户猜到了种子,也无法预测下一次抽奖结果(因为 Salt 每次不同)。 - 一番赏:种子一旦确定,序列即确定。我们在活动开始前才生成种子和 Commitment,确保无人(包括管理员)能提前知晓排列。