链上随机数生成
核心知识点讲解
区块链随机数的挑战
区块链的确定性和透明性使得生成安全的随机数变得困难:
- 确定性:相同的输入总是产生相同的输出
- 透明性:所有交易和状态对网络中的所有节点可见
- 可预测性:攻击者可以预测或操纵随机数生成过程
链上随机数生成方法
1. 伪随机数生成
基于区块信息的伪随机数:
- 使用
block.timestamp、block.number、block.difficulty等区块变量 - 结合用户输入或交易哈希
- 优点:简单直接,不需要外部依赖
- 缺点:可预测性高,容易被操纵
2. 承诺-揭示模式
使用多轮交互生成随机数:
- 参与者提交加密后的随机数(承诺)
- 所有参与者提交后,揭示各自的随机数
- 组合所有揭示的随机数生成最终结果
- 优点:安全性较高
- 缺点:需要多轮交互,实现复杂
3. 预言机解决方案
使用外部预言机提供随机数:
- Chainlink VRF:Verifiable Random Function,可验证的随机函数
- API3 QRNG:Quantum Random Number Generator,量子随机数生成器
- 优点:安全性高,不可预测
- 缺点:需要支付预言机费用,有一定的延迟
Chainlink VRF 工作原理
- 请求随机数:智能合约向 Chainlink VRF 发送请求
- 生成随机数:Chainlink 节点使用 VRF 生成随机数和证明
- 验证随机数:智能合约验证证明的有效性
- 使用随机数:验证通过后,智能合约使用生成的随机数
实用案例分析
伪随机数实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract PseudoRandom {
uint256 private nonce;
function getRandomNumber() public returns (uint256) {
// 使用区块信息和nonce生成伪随机数
uint256 random = uint256(keccak256(abi.encodePacked(
block.timestamp,
block.difficulty,
msg.sender,
nonce
)));
nonce++;
return random;
}
// 注意:此方法不安全,仅用于演示
function getRandomInRange(uint256 min, uint256 max) public returns (uint256) {
uint256 random = getRandomNumber();
return min + (random % (max - min + 1));
}
}Chainlink VRF 实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
contract RandomNumberConsumer is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
// 测试网络的VRF协调器地址
address vrfCoordinator = 0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed;
// 测试网络的LINK代币地址
address linkToken = 0x326C977E6efc84E512bB9C30f76E30c160eD06FB;
// 订阅ID,需要在Chainlink VRF界面创建
uint64 subscriptionId;
// 燃气限额
uint32 callbackGasLimit = 100000;
// 请求确认数
uint16 requestConfirmations = 3;
// 一次请求的随机数数量
uint32 numWords = 1;
// 存储生成的随机数
uint256[] public randomWords;
constructor(uint64 _subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
subscriptionId = _subscriptionId;
}
// 请求随机数
function requestRandomWords() external {
// 向VRF协调器发送随机数请求
uint256 requestId = COORDINATOR.requestRandomWords(
subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords,
1 // 额外参数
);
}
// VRF协调器的回调函数
function fulfillRandomWords(
uint256 /* requestId */,
uint256[] memory _randomWords
) internal override {
randomWords = _randomWords;
}
// 获取随机数范围
function getRandomInRange(uint256 min, uint256 max) public view returns (uint256) {
require(randomWords.length > 0, "No random words yet");
uint256 random = randomWords[0];
return min + (random % (max - min + 1));
}
}随机数应用场景
1. 彩票合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
contract Lottery is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
address vrfCoordinator = 0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed;
address linkToken = 0x326C977E6efc84E512bB9C30f76E30c160eD06FB;
uint64 subscriptionId;
uint32 callbackGasLimit = 100000;
uint16 requestConfirmations = 3;
uint32 numWords = 1;
address[] public participants;
address public winner;
bool public lotteryClosed;
constructor(uint64 _subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
subscriptionId = _subscriptionId;
}
function enter() public payable {
require(msg.value > 0, "Must send ETH to enter");
require(!lotteryClosed, "Lottery is closed");
participants.push(msg.sender);
}
function closeLottery() public {
require(!lotteryClosed, "Lottery is already closed");
lotteryClosed = true;
requestRandomWords();
}
function requestRandomWords() internal {
COORDINATOR.requestRandomWords(
subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords,
1
);
}
function fulfillRandomWords(
uint256 /* requestId */,
uint256[] memory _randomWords
) internal override {
uint256 winnerIndex = _randomWords[0] % participants.length;
winner = participants[winnerIndex];
payable(winner).transfer(address(this).balance);
}
function getParticipantsCount() public view returns (uint256) {
return participants.length;
}
}2. NFT随机属性生成
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract RandomNFT is ERC721, VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
address vrfCoordinator = 0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed;
address linkToken = 0x326C977E6efc84E512bB9C30f76E30c160eD06FB;
uint64 subscriptionId;
uint32 callbackGasLimit = 100000;
uint16 requestConfirmations = 3;
uint32 numWords = 3; // 生成3个随机数用于3个属性
uint256 public tokenCounter;
mapping(uint256 => uint256[]) public tokenAttributes;
mapping(uint256 => uint256) public requestIdToTokenId;
constructor(uint64 _subscriptionId)
ERC721("RandomNFT", "RNFT")
VRFConsumerBaseV2(vrfCoordinator)
{
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
subscriptionId = _subscriptionId;
}
function mint() public {
uint256 tokenId = tokenCounter;
_safeMint(msg.sender, tokenId);
uint256 requestId = COORDINATOR.requestRandomWords(
subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords,
tokenId
);
requestIdToTokenId[requestId] = tokenId;
tokenCounter++;
}
function fulfillRandomWords(
uint256 requestId,
uint256[] memory _randomWords
) internal override {
uint256 tokenId = requestIdToTokenId[requestId];
// 生成3个属性值(0-99)
uint256[] memory attributes = new uint256[](3);
attributes[0] = _randomWords[0] % 100;
attributes[1] = _randomWords[1] % 100;
attributes[2] = _randomWords[2] % 100;
tokenAttributes[tokenId] = attributes;
}
function getTokenAttributes(uint256 tokenId) public view returns (uint256[] memory) {
return tokenAttributes[tokenId];
}
}实践练习
部署伪随机数合约:
- 部署上面的
PseudoRandom合约 - 调用
getRandomNumber方法生成随机数 - 分析其安全性问题
- 部署上面的
使用 Chainlink VRF:
- 在 Chainlink 网站上创建 VRF 订阅
- 部署
RandomNumberConsumer合约 - 为合约充值 LINK 代币
- 调用
requestRandomWords方法 - 等待回调并获取随机数
开发彩票合约:
- 部署
Lottery合约 - 多个账户参与彩票
- 关闭彩票并等待随机数生成
- 验证 winner 地址和奖金发放
- 部署
创建随机属性 NFT:
- 部署
RandomNFT合约 - 铸造 NFT
- 查看生成的随机属性
- 部署
总结
链上随机数生成是Web3开发中的一个重要挑战,因为区块链的确定性和透明性特性。在选择随机数生成方法时,需要根据应用场景的安全性要求进行权衡:
- 伪随机数:适合对安全性要求不高的场景,如游戏内的非关键随机事件
- 承诺-揭示模式:适合需要多方参与的场景,如DAO投票
- Chainlink VRF:适合对安全性要求高的场景,如彩票、NFT minting、DeFi协议
Chainlink VRF 是目前最安全可靠的链上随机数解决方案,它通过密码学证明确保随机数的不可预测性和可验证性。虽然使用 Chainlink VRF 需要支付一定的费用,但对于需要高安全性的应用来说,这是值得的投资。
通过本集的学习,你应该能够理解链上随机数生成的挑战和解决方案,为开发需要随机数的Web3应用做好准备。