/Users/win/code2025/bindbox_game/docs/抽奖一致性方案/TECH_DESIGN_抽奖一致性.md # 抽奖一致性与离线验证技术方案 ## 1. 背景与目标 - 背景:当前系统具备活动/期/奖励配置与日志查询,但缺少“抽奖随机一致性与离线验证”的闭环 - 目标 - 离线验证:用户拿一份回执单 (`receipt.json`) 在本地无网络环境重算并比对抽奖结果 - 不可作假:平台在抽前预承诺随机源与奖池,抽后揭示并提供签名,用户可核验 - 不可预测:一次一密、不提前泄露服务端随机,用户无法事前预测下一次结果 - 便携性:单文件验证工具,零依赖、零配置,命令行运行即可 ## 2. 总体方案 - 方案A(MVP):Commit-Reveal + HMAC-SHA256 - 服务端为每个期 `issue_id` 预承诺高熵主种子 `serverSeed_master`,公开 `SHA256(serverSeed_master)`(记为 `serverSeedHash`) - 每次抽奖派生一次性子种子:`serverSubSeed = HMAC_SHA256(serverSeed_master, encode(issueId|drawId))`;抽后在回执揭示 `serverSubSeed`,不泄露主种子 - 抽奖随机:`entropy = HMAC_SHA256(serverSubSeed, encodedMessage)`;拒绝采样,映射到权重区间 - 权重与奖池:抽奖前对奖励配置做规范化快照并哈希承诺 `itemsRoot`,确保各用户池一致 - 优点:实现简单、使用标准库、双平台易构建;满足不可作假与便携校验 - 方案B(升级):VRF(可验证随机函数) - 服务端用 VRF 私钥对消息生成随机与证明;用户用公钥离线验真,无需揭示任何种子 - 优点:一次一密且天然不可预测;缺点:需引入并审计成熟库,跨平台打包复杂度更高 - 方案C(审计增强):透明日志与库存状态证明 - 使用追加型透明日志的 Merkle 根约束 `drawId` 顺序与库存变更;可提供单位级“包含/非包含证明” - 适合二期提升全局可追溯性与防“双花” ## 3. 核心数据与回执规范 - 回执字段(自包含模式) - `algoVersion`: 算法/编码版本 - `roundId`: 期ID(`issue_id`) - `drawId`: 抽奖唯一ID(递增或全局唯一) - `clientId`: 用户ID - `timestamp`: 抽奖时间戳 - `serverSeedHash`: `SHA256(serverSeed_master)` 的十六进制/Base64 - `serverSubSeed`: 本次子种子(十六进制/Base64) - `clientSeed`: 用户种子(可系统生成) - `nonce`: 用户在该期内的递增计数(防重) - `items`: 奖池快照(按 `id` 排序)`[{id, name, weight, quantity_before}]` - `itemsRoot`: `SHA256(canonical_items_json)` - `weightsTotal`: 权重总和(整数) - `selectedIndex`: 选中项索引 - `selectedItemId`: 选中奖励ID - `randProof`: 可选,记录一次性 `entropy` 的十六进制 - `signature`: 平台签名(覆盖整份回执,推荐 ed25519) - 双文件模式(大池可选) - 回执移除 `items`,仅保留 `itemsRoot` 与 `weightsTotal`;本地需提供 `pool.json`,其哈希必须等于 `itemsRoot` ## 4. 消息编码与随机选取规范 - 编码规则(用于 HMAC 消息) - 固定顺序:`algoVersion | roundId | drawId | clientId | clientSeed | nonce | itemsRoot | weightsTotal` - 字符串:`uint32`(大端)长度 + UTF-8 字节 - 整数:`uint64`(大端) - 哈希:原始 32 字节 - 随机选取(拒绝采样) - 取 `R = uint64(entropy[0:8])` - 设 `W = weightsTotal`,`M = floor(2^64 / W) * W` - 若 `R >= M`:以计数器扩展重新计算 `entropy = HMAC_SHA256(serverSubSeed, encodedMessage || counter++)`,直到命中 - 位置 `pos = R % W`;以权重累加区间定位 `selectedIndex` ## 5. 有库存的验证与扣减 - 抽奖前集合:仅在“`quantity_before > 0`”的奖励集合中参与权重抽取 - 两阶段(可选): - 阶段1:类别选择(权重拒绝采样) - 阶段2:单位选择(该类别剩余单位内均匀或约定次序) - 事务处理(服务端) - 扣减 `ActivityRewardSettings.Quantity`(乐观检查>0) - 写入 `ActivityDrawLogs`(抽奖记录) - 如需生成用户资产/订单,参考现有逻辑(`internal/service/user/reward_grant.go`) - 二期增强(Merkle证明) - 维护稀疏 Merkle 树(键=`unitId`,值=状态位),回执包含“抽前包含证明 + 抽后非包含/状态更新证明”与版本号单调 ## 6. 服务端接口与集成点 - APP端新增 - `POST /api/app/activities/:activity_id/issues/:issue_id/draw`:执行抽奖,返回回执 JSON - 集成位置:`internal/router/router.go` 的 APP认证组(与用户接口相同组) - 管理端新增 - `POST /api/admin/activities/:activity_id/issues/:issue_id/commit_random`:生成/轮换期的随机承诺 - `GET /api/admin/activities/:activity_id/issues/:issue_id/commit_random`:查看承诺 - 现有点位复用 - 奖励配置与权重:`ListIssueRewards`(`internal/service/activity/rewards_list.go`) - 抽奖日志查询:`ListDrawLogs`(`internal/api/activity/draw_logs_app.go`) - 用户资产/订单:参考系统发放(`internal/service/user/reward_grant.go`) ## 7. 随机承诺存储设计 - 新增表:`issue_random_commitments` - 字段:`issue_id, algo_version, server_seed_master, server_seed_hash, items_root, weights_total, state_version, created_at` - 用途:管理期的随机承诺与池快照,支持版本化(`state_version` 递增) - 过渡方案:若不立刻加表,可用配置存储(KV/配置表/文件),但建议最终落库便于审计 ## 8. 离线验证工具(CLI) - 用法:`bindbox_verify.exe receipt.json`(返回码 `0` 通过、`1` 失败) - 验证步骤 - 验签:用公钥验证 `signature` - 验承诺:`SHA256(serverSeed_master)` 是否等于 `serverSeedHash`;并校验子种子派生规则 - 验奖池:重算 `itemsRoot` 与 `weightsTotal` - 重算随机:按编码规范 HMAC 与拒绝采样 - 映射比对:`selectedIndex/selectedItemId` 是否一致 - 构建(参考已有命令) - Windows:`CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -trimpath -o build/bindbox_verify.exe ./cmd/verify` - macOS(amd64/arm64):同上构建;现有参考命令见 `README.md:17` ## 9. 安全与公平 - 不可预测 - 每次抽奖一次一密(`serverSubSeed`),不提前揭示 - `drawId/nonce` 单调递增并纳入消息编码 - 不可操控 - 抽前公开承诺:`serverSeedHash` 与 `itemsRoot` - 回执签名覆盖所有字段;日志保留顺序与上下文 - 无偏性 - 严格使用拒绝采样;权重与数量为非负整数;总和与整型范围校验 ## 10. 实施步骤与时间节点 - 第1周:协议固化与承诺表设计;管理端承诺接口;服务层 `ExecuteDraw` 实现(无库存/有库存) - 第2周:APP路由与处理器接入;离线验证 CLI 首版;测试向量与集成测试 - 第3周:文档与用户指引;压测极端权重/库存;可选VRF调研与二期规划 ## 11. 验收标准 - 抽前承诺已公开且与回执一致 - 同一回执在 Windows/macOS 离线复验结果一致 - 有库存时仅在剩余>0集合中抽取;事务扣减与日志记录完整 - 随机输出在所有构建版本中一致;拒绝采样性能达标 ## 12. 参考与代码位置 - 构建命令(Windows):`README.md:17` - 奖励配置查询:`internal/service/activity/rewards_list.go:7` - 抽奖日志查询:`internal/api/activity/draw_logs_app.go:35`、`internal/service/activity/draw_logs_list.go:7` - 用户资产发放参考:`internal/service/user/reward_grant.go:136`