bindbox-game/docs/lottery_algorithm.md

150 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 抽奖与公平性算法技术白皮书
## 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确保无人包括管理员能提前知晓排列。