VRF

1U夺宝开奖VRF验证说明

GET /api/v2/game/G0000000050/rounds/R0000000050/draw-proof

正在使用内置样例

用户只要拿到几个公开值,就能自己验证开奖

本页展示本期夺宝的开奖依据:随机数从哪里来,中奖票位怎么算出,链上交易如何核对。

本期已通过开奖验证:链上 VRF 给出随机数,系统按固定公式算出第 1 个票位中奖,对应票号 1,中奖用户为 63***95。

本期开奖为什么可信

本期开奖不是后台手动挑选中奖人。中奖位置由链上随机数和公开公式算出,任何人都可以用页面里的公开数据重新核对。

本期开奖可以复核:票号池先锁定,随机数后上链,中奖票位按固定公式算出,任何人都能用公开数据重新算一遍。
  • 平台先锁票 售罄时把有效票位封存成 ticketRoot,后面不能偷偷换票池。
  • 链上再给随机数 VRF 回调交易公开记录 randomWord,平台不能提前知道这个数。
  • 公式固定不变 winningIndex = randomWord % drawTotal + 1,不靠后台手填。
  • 结果能被外部复算 用户拿到 randomWord、drawTotal、winningIndex、callbackTxHash 就能独立检查。

一句话看懂开奖方式

本期开奖分为锁定票号、获取链上随机数、按公式计算中奖票位、映射中奖票号四个环节。

本期开奖不是后台选人,而是售罄后锁定票号快照,再由链上 VRF 产生随机数,并用公开公式计算中奖票位。
  • 随机数来自链上 VRF 随机数记录在回调交易里,可以在区块浏览器中核对。
  • 公式公开透明 中奖票位只按 randomWord % drawTotal + 1 计算,不使用隐藏规则。
  • 链上事件可核对 需要查看平台开奖合约的 DrawFulfilled 事件,不要停留在 Chainlink 的 RandomWordsFulfilled 事件。

完整开奖流程

从售罄到用户看到中奖结果,中间只有这 5 步。每一步都有对应的公开字段或校验项。

1

售罄锁定票位

系统确认本期有效票位数量,得到 drawTotal,并生成票号快照。

2

提交快照根

票号快照根 ticketRoot 进入开奖请求,后续用来证明票池没有被替换。

3

请求链上 VRF

合约请求 Chainlink VRF,产生 requestTxHash,等待随机数回调。

4

回调发出事件

callbackTxHash 里会有 DrawFulfilled,包含 randomWord 和中奖计算字段。

5

公式映射中奖票

用 randomWord % drawTotal + 1 得到 winningIndex,再从快照映射到中奖票号。

为什么这套机制可信

可信点不是“平台说通过”,而是用户能拿公开数据做同一套计算。

随机性来自链上

randomWord 来自 VRF fulfillment,不是平台开奖接口临时生成的数。

公式提前固定

中奖票位只由 randomWord 和 drawTotal 决定,公式简单且可复算。

票池先被封存

ticketRoot 对应售罄时的票号快照,避免随机数出来后再换票。

链上事件可查

callbackTxHash 能在区块浏览器里找到 DrawFulfilled 事件。

结果能被第三方验证

复制验证包后,不登录平台的人也能核对公式和链上交易。

后台不能改公式结果

如果展示的 winningIndex 和公式结果不一致,页面校验会失败。

如何自己核对

不需要会写代码,按这条路线看就够了;看不懂 Raw Logs 时再往下看高级排查。

1. 看结论 确认页面显示“验证通过”,并看到中奖票号和中奖用户脱敏信息。
2. 复制三个数 复制 randomWord、drawTotal、winningIndex,用公式复算。
3. 打开回调交易 用 callbackTxHash 到区块浏览器里找 DrawFulfilled。
4. 对齐字段 链上 randomWord、winningIndex、drawTotal、ticketRoot 要和本页一致。

第一步:复制复算需要的公开值

先看这 4 个值就够了:3 个数负责复算中奖票位,交易 hash 负责证明随机数来自链上。

本期只需要 randomWord、drawTotal、winningIndex、callbackTxHash;用 randomWord % drawTotal + 1 复算,结果必须等于 winningIndex。
1. 链上随机数 randomWord

VRF 回调给出的原始随机数,平台不能提前知道,用它来算中奖位置。

2. 总票位 drawTotal

售罄时封存的有效票位数量,本期只会在 1 到 drawTotal 之间抽一个位置。

3. 中奖票位 winningIndex

公式算出的第几个票位中奖,不一定等于票号,后面还要通过票号快照映射到 winningTicket。

4. 回调交易 callbackTxHash

链上开奖回调交易 hash,用它在区块浏览器里找到 DrawFulfilled 事件。

可直接运行

第二步:到区块浏览器核对事件

链上验证不是重新生成随机数,而是确认官方开奖合约确实公开发出了这些事件和值。

本期要打开的是下面这个完整 Sepolia Etherscan 地址,它对应 callbackTxHash 回调交易,不是 Ethereum 主网浏览器,也不是 requestTxHash 请求交易。

本期应打开的完整浏览器地址(callbackTxHash)
  1. 打开回调交易 点下面的区块浏览器地址,进入交易详情页。注意要打开 callbackTxHash(Chainlink VRF Coordinator 回调平台 DrawCoordinator 的交易),不是 requestTxHash(平台 DrawCoordinator 发起 VRF 请求的交易)。
  2. 进入 Logs / Events 在 Etherscan 这类浏览器里找 Logs 或 Events 标签/区域;如果有 Decoded View,优先看解码后的事件名和字段名。不要只看页面顶部的交易状态,因为交易成功只表示回调执行成功,不等于开奖字段已经核对过。
  3. 找到 DrawFulfilled 事件名必须是 DrawFulfilled,事件发出地址必须是官方开奖合约地址。Decoded View 里应看到 roundKey、requestId、randomWord、winningIndex、drawTotal、ticketRoot,其中 roundKey、randomWord、winningIndex、drawTotal、ticketRoot 要与本页一致。
  4. 确认官方合约 交易里的事件发出地址要等于下面的“官方开奖合约地址”,这样才能确认不是其他合约发出的同名事件。
  5. 复算并判断 用 randomWord % drawTotal + 1,结果必须等于链上事件和本页展示的 winningIndex。

Decoded View 里逐项核对

优先看浏览器已经解码出的事件字段。字段顺序以合约事件为准:DrawFulfilled(bytes32 indexed roundKey, uint256 indexed requestId, uint256 randomWord, uint256 winningIndex, uint256 drawTotal, bytes32 ticketRoot)。

event name = DrawFulfilled 确认这是开奖完成事件,不是 DrawRequested 或其他合约的同名/相似日志。
address = drawCoordinator 事件发出地址要等于下方“官方开奖合约地址”。
roundKey 本期唯一开奖键,要和高级详情里的 roundKey 完全一致。
randomWord 链上 VRF 随机数,要和本页 randomWord 完全一致。
winningIndex 链上算出的中奖票位,要等于本页 winningIndex,也要等于公式复算结果。
drawTotal 开奖时锁定的总票位,要和本页 drawTotal 一致。
ticketRoot 售罄时封存的票号快照根,要和本页 ticketRoot 一致。
requestId 链上 VRF 请求编号,主要用于把请求交易和回调事件串起来。

不要把 Chainlink 事件当成开奖结果

同一笔回调交易里可能同时出现 Chainlink VRF Coordinator 的 RandomWordsFulfilled 和平台开奖合约的 DrawFulfilled。看到 outputSeedpaymentnativePaymentsuccessonlyPremium 时,说明你看到的是 Chainlink 的 fulfillment 事件;它证明 VRF 回调成功并记录扣费,不是最终开奖证明字段。

Chainlink 事件:RandomWordsFulfilled

topic[0] = 0xaeb4b4786571e184246d39587f659abf0e26f41f6a3358692250382c0cdb47b7
发出方 Chainlink VRF Coordinator,不是平台开奖合约。
outputSeed Chainlink 记录的 VRF 输出/seed,说明随机源已生成;不要把它当成本页要核对的 randomWord
payment 本次 fulfillment 扣费金额。
nativePayment true 表示用原生币支付费用,例如 Sepolia ETH;false 通常表示用 LINK。
success true 表示 Chainlink 回调 consumer 合约成功。
onlyPremium Chainlink 的计费标记,核对开奖结果时不需要使用这个字段。

平台事件:DrawFulfilled

topic[0] = 0x6fd04d048ef95eaca3a3264e7ee31b2107559ecaceab3f85c585b60f5810732a
发出方 平台公开的 DrawCoordinator 合约,地址要等于本页的 drawCoordinator
randomWord 本页要核对的 VRF 随机数,用来复算中奖票位。
winningIndex 合约按 randomWord % drawTotal + 1 得到的中奖票位。
drawTotal 开奖时锁定的有效票位总数。
ticketRoot 售罄时封存的票号快照根。
  1. 如果当前日志字段是 outputSeed/payment/nativePayment/success/onlyPremium,继续往下找,不要在这里停。
  2. 找到事件名 DrawFulfilled,并确认事件发出地址等于官方 drawCoordinator
  3. 再核对 DrawFulfilled 里的 randomWordwinningIndexdrawTotalticketRoot

三个区块浏览器入口分别看什么

开奖链上会留下两笔关键交易和一个官方合约地址。普通用户重点看“回调交易”;“请求交易”和“开奖合约”用来证明流程前后能串起来。

先区分两个合约:DrawCoordinator 是平台自己的开奖合约,不是 Chainlink 的合约;Chainlink VRF Coordinator 才是 Chainlink 的随机数服务合约。核对开奖结果时,要看平台 DrawCoordinator 发出的 DrawFulfilled

打开请求交易

这是平台开奖合约向 Chainlink VRF 发起随机数请求的交易,表示本轮开奖在链上登记了“我要随机数”。

主要核对:交易成功、发出事件是 DrawRequested 或 VRF 请求相关事件,里面的 requestId 要能和回调交易里的 requestId 对上。

打开回调交易

这是 Chainlink VRF 把随机数回传给平台开奖合约的交易,也是核对开奖结果最重要的一笔。

主要核对:在 Logs / Events 里找到平台合约发出的 DrawFulfilled,确认 randomWordwinningIndexdrawTotalticketRoot 与本页一致。

打开开奖合约

这是官方 DrawCoordinator 合约地址,不是一笔交易,也不是 Chainlink 地址。它用来确认事件确实由平台公开的开奖合约发出。

主要核对:回调交易里的 DrawFulfilled 事件发出地址,要等于这里的合约地址;不要把 Chainlink Coordinator 地址当成平台开奖合约。

没有 Decoded View 时看 Raw Logs

如果浏览器没有合约 ABI,只显示 Topics 和 Data,也能核对。看不到 randomWord 字段名是正常的;它藏在 data 的第一段 32-byte 数据里。

这是 EVM Event 的标准编码规则,不是平台故意把字段切开。带 indexed 的字段会进入 topics,没有 indexed 的字段会按 ABI 顺序进入 data

topic[0] 0x6fd04d048ef95eaca3a3264e7ee31b2107559ecaceab3f85c585b60f5810732a,代表 DrawFulfilled(bytes32,uint256,uint256,uint256,uint256,bytes32)。
topic[1] indexed roundKey,应等于本页 roundKey。
topic[2] indexed requestId,链上 VRF 请求编号。
data 0x 后面的内容每 64 个十六进制字符切一段。第 1 段是 randomWord,第 2 段是 winningIndex,第 3 段是 drawTotal,第 4 段是 ticketRoot

用本页数据对照 Raw Logs

data[0] randomWord
转成十进制
data[1] winningIndex
data[2] drawTotal
data[3] ticketRoot

怎么把 data[0] 转成十进制

randomWord 很大,不能用普通 JavaScript Number;要用 BigInt 或链上工具按 uint256 转。

浏览器控制台
Foundry cast
区块浏览器地址
官方开奖合约地址

接口字段名是 drawCoordinator。它是平台自己的 DrawCoordinator 开奖合约,不是 Chainlink VRF Coordinator。它用来确认 DrawFulfilled 是由平台公开的开奖合约发出,不是其他合约。

如果区块浏览器链接未配置,就复制 callbackTxHash,在对应链的浏览器里搜索交易 hash。
如果链上 DrawFulfilled 里的 randomWord、drawTotal、winningIndex 和本页一致,并且公式结果也一致,就说明中奖位置不是平台手填的。
一键复制验证包

把这些公开值发给别人,对方不登录平台也能复核计算和链上交易。

验证结论 通过 全部校验项通过才算通过
随机来源 VRF 链上随机数服务返回
参与票位 2 售罄时锁定的有效票位数
中奖票位 1 由随机数公式算出
中奖票号 1 从票号快照映射得到
中奖用户 63***95 只展示脱敏 UID

公式复算区

把 randomWord、drawTotal、winningIndex 放在一起,就能看到中奖票位是怎么算出来的。

固定公式
winningIndex = (randomWord % drawTotal) + 1
本期代入
randomWord % 2 = 0; 0 + 1 = 1
票位映射
第 1 个票位 -> 票号 1 -> 用户 63***95
票号 1 中奖

本期一共有 2 个有效票位。链上随机数除以 2 的余数是 0,按公式加 1 后得到第 1 个票位,票号快照显示第 1 个票位对应票号 1。

校验闭环

从锁定票号到产生中奖票号,每一步都有可以核对的公开信息。

1

售罄时锁定票号快照

系统把所有有效票位封存成快照,并生成 ticketRoot。后面不能随意换票。

2

链上 VRF 返回随机数

randomWord 来自 VRF 回调,接口同时给出 requestTxHash 和 callbackTxHash。

3

按固定公式算中奖票位

winningIndex = randomWord % drawTotal + 1。本期算出来是第 1 个票位。

4

用快照查出中奖票号

第 1 个票位在封存快照里对应票号 1,并且归属用户 63***95。

接口返回的 6 项验证

这些校验项全部通过,才表示本期开奖证明完整成立。

字段说明

下面解释每个关键字段代表什么,以及它在页面中对应哪一类信息。

接口字段 含义 页面展示

技术详情

这些值用于进一步复核链上事件、票号快照和交易记录。

VRF 随机数 randomWord
票号快照根 ticketRoot
开奖轮次键 roundKey
VRF 请求 ID
请求交易 hash
回调交易 hash
高级数据 用于核对接口地址、重新拉取证明数据和查看完整 JSON;普通核对不需要展开。

数据来源

页面会读取本期开奖证明数据;如果网络暂时不可用,会显示内置样例。

接口原始返回

完整 JSON 可用于核对页面展示值和接口返回值是否一致。