在无需可信第三方的情况下,实现不可预测的伪随机数的方案有:
- VRF
- VDF
- Commit-reveal
当前,Celo项目采用的是简单的commit-reveal方案来随机选择Validator. 对于某特定Validator propose的第 n n n个区块,该Validator会在该区块内附加值 ( r n , s n ) (r_n,s_n) (rn,sn),使得 keccak256 ( r n ) = s n − 1 \text{keccak256}(r_n)=s_{n-1} keccak256(rn)=sn−1。对于该Validator propose的第一个区块 n = 1 n=1 n=1,则初始随机值 r 1 = 0 r_1=0 r1=0。
/**
* @notice Implements step of the randomness protocol.
* @param randomness Bytes that will be added to the entropy pool.
* @param newCommitment The hash of randomness that will be revealed in the future.
* @param proposer Address of the block proposer.
*/
function _revealAndCommit(bytes32 randomness, bytes32 newCommitment, address proposer) internal {
require(newCommitment != computeCommitment(0), "cannot commit zero randomness");
// ensure revealed randomness matches previous commitment
if (commitments[proposer] != 0) {
require(randomness != 0, "randomness cannot be zero if there is a previous commitment");
bytes32 expectedCommitment = computeCommitment(randomness);
require(
expectedCommitment == commitments[proposer],
"commitment didn't match the posted randomness"
);
} else {
require(randomness == 0, "randomness should be zero if there is no previous commitment");
}
// add entropy
uint256 blockNumber = block.number == 0 ? 0 : block.number.sub(1);
addRandomness(block.number, keccak256(abi.encodePacked(history[blockNumber], randomness)));
commitments[proposer] = newCommitment;
}
/**
* @notice Compute the commitment hash for a given randomness value.
* @param randomness The value for which the commitment hash is computed.
* @return Commitment parameter.
*/
function computeCommitment(bytes32 randomness) public pure returns (bytes32) {
return keccak256(abi.encodePacked(randomness));
}
在每一个区块会reveal该Validator之前区块所commit的随机值,reveal的随机值会存入一个entropy pool中。所有已reveal的历史随机值会通过keccak256
拼接在一起。
// add entropy
uint256 blockNumber = block.number == 0 ? 0 : block.number.sub(1);
// 所有已reveal的历史随机值会通过`keccak256`拼接在一起。
addRandomness(block.number, keccak256(abi.encodePacked(history[blockNumber], randomness)));
mapping(uint256 => bytes32) private history;
/**
* @notice Add a value to the randomness history.
* @param blockNumber Current block number.
* @param randomness The new randomness added to history.
* @dev The calls to this function should be made so that on the next call, blockNumber will
* be the previous one, incremented by one.
*/
function addRandomness(uint256 blockNumber, bytes32 randomness) internal {
history[blockNumber] = randomness;
if (blockNumber % getEpochSize() == 0) {
if (lastEpochBlock < historyFirst) {
delete history[lastEpochBlock];
}
lastEpochBlock = blockNumber;
} else {
if (historySize == 0) {
historyFirst = blockNumber;
historySize = 1;
} else if (historySize > randomnessBlockRetentionWindow) { // 控制history map存储的数据量,节约空间。
deleteHistoryIfNotLastEpochBlock(historyFirst);
deleteHistoryIfNotLastEpochBlock(historyFirst.add(1));
historyFirst = historyFirst.add(2);
historySize = historySize.sub(1);
} else if (historySize == randomnessBlockRetentionWindow) {
deleteHistoryIfNotLastEpochBlock(historyFirst);
historyFirst = historyFirst.add(1);
} else {
// historySize < randomnessBlockRetentionWindow
historySize = historySize.add(1);
}
}
}
获取特定区块对应的随机值:
/**
* @notice Get randomness values of previous blocks.
* @param blockNumber The number of block whose randomness value we want to know.
* @param cur Number of the current block.
* @return The associated randomness value.
*/
function _getBlockRandomness(uint256 blockNumber, uint256 cur) internal view returns (bytes32) {
require(blockNumber cur.sub(historySize) &&
(randomnessBlockRetentionWindow >= cur ||
blockNumber > cur.sub(randomnessBlockRetentionWindow))),
"Cannot query randomness older than the stored history"
);
return history[blockNumber];
}
参考资料
[1] Celo身份注册中所使用的随机数