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

## 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，确保无人（包括管理员）能提前知晓排列。
