bindbox-game/docs/lottery_algorithm.md

5.4 KiB
Raw Blame History

抽奖与公平性算法技术白皮书

1. 概述

本系统采用 「承诺机制 (Commitment Scheme)」 结合 HMAC-SHA256 算法,确保抽奖过程的不可预测性可验证性不可篡改性

核心原则:

  1. 事前承诺活动开始前生成随机种子并公布其哈希值Commitment
  2. 事后验证活动结束后公布种子明文Reveal用户可复算验证。
  3. 确定性算法:输入(种子 + 上下文)确定,输出必然唯一。

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)

适用于奖品无限库存或按权重概率抽取的模式。

算法流程

  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 落在哪个区间。

代码逻辑

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 生成的随机序列对奖品位置进行打乱。

    对于 iTotalSlots-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)}

    交换索引 ij 的元素。

  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 运行验证工具

无限赏验证命令示例

./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. 安全性声明

  1. 种子保密ServerSeed 在存储层加密保存,仅在活动结束或特定审计时刻解密公开。
  2. 结果不可逆:无法通过哈希值反推种子。
  3. 防预测
    • 无限赏:引入了 Salt(真随机生成),即使用户猜到了种子,也无法预测下一次抽奖结果(因为 Salt 每次不同)。
    • 一番赏:种子一旦确定,序列即确定。我们在活动开始前才生成种子和 Commitment确保无人包括管理员能提前知晓排列。