我希望我的智能合约在调用合约时返回 7 或 8 个UNIQUE随机数,范围从 1 到 100。获得这种结果的最佳方法是什么?
4 回答
可能如果您尝试使用以太坊区块链构建轮盘赌、彩票和纸牌游戏,因为以太坊区块链是确定性的,它给那些选择编写自己的伪随机数生成器 (PRNG) 的人带来了一定的困难。
目前使用的一些易受攻击的方法
如果您使用 block.coinbase、block.difficulty、block.timestamp 等块变量作为熵源,所有这些块变量都可以被矿工操纵,因此它们不能用作熵源,因为矿工的激励。由于块变量显然在同一个块内共享,您可以轻松地使用内部消息来产生相同的结果。
其他方法就像使用当前或过去块的块哈希或过去块的块哈希与私有种子相结合。在这些情况下使用 block.blockhash(block.number) 函数。然而,在 EVM 中执行交易的那一刻,由于显而易见的原因,正在创建的区块的区块哈希尚不清楚,并且 EVM 将始终产生零。如果我们使用前一个块的块哈希来尝试它,攻击者可以使用相同的代码制作漏洞利用合约,以便通过内部消息调用目标合约。两个合同的“随机”数字将是相同的。
即使我们将区块哈希与私有种子结合起来,本质上是透明的,区块链也不能用于以明文形式存储秘密。从合约存储中提取私有变量指针的值并将其作为参数提供是微不足道的剥削。
一些值得探索的领域
- 外部神谕
- 意义
- 提交 - 揭示方法
借助 Oraclize 等外部预言机,智能合约可以从 Web API 请求数据,例如货币汇率、天气预报和股票价格(如 random.org)。这种方法的主要缺点是它是集中式的。Oraclize 守护进程会篡改结果吗?我们可以信任 random.org 吗?
除了 Oraclize,我们还可以使用 BTCRelay,它是以太坊和比特币区块链之间的桥梁。使用 BTCRelay,以太坊区块链中的智能合约可以请求未来的比特币区块哈希并将它们用作熵的来源。
Signidice是一种基于加密签名的算法,可用于仅涉及两方的智能合约中的随机数生成:玩家和房屋。该算法的工作原理如下:
- 玩家通过调用智能合约进行投注。
- 房子看到赌注,用它的私钥签名,然后将签名发送给智能合约。
- 智能合约使用已知的公钥验证签名。
- 然后使用该签名生成一个随机数。
Commit-reveal 方法包括两个阶段:
- “提交”阶段,当各方将其受密码保护的秘密提交给智能合约时。
- 一个“揭示”阶段,当各方公布明文种子时,智能合约验证它们是否正确,种子用于生成随机数。
提交-显示方法的更好实现是Randao。Commit-reveal 可以与未来的区块哈希相结合,使其更加安全。
这几乎涵盖了使用以太坊生成随机数的所有方法。
就像 Raghav 所说,区块链上的随机数很难。网络的公共性质使得很难生成无法预先计算的数字。
话虽如此,最好的解决方案之一是使用从外部(阅读:非基于区块链)源获取随机数的预言机。看看这个指南。Ethtroll Dapp 就是一个很好的例子,所以看看这里的代码。他们使用 Oraclize 从 Random.org 获取随机数。
使用预言机的一个问题是中心化因素。如果您按照我上面描述的方式设置您的 Dapp,那么您将受制于两个不同的中心化服务(Oraclize 和 Random.org)的一名流氓员工。尽管有人不太可能操纵这些资源中的任何一个,但人们会为了潜在的经济利益而做出非理性的行为。
使用块哈希或类似方法作为随机播种方法存在许多问题。如果攻击者在你的合约之前知道区块哈希,他们可以使用该信息在你试图做的任何事情上获得恶意优势。预言机可以在这里提供帮助,但它们是失败的主要来源,并且必须能够证明它们是随机的。
你需要一个预言机网络,它可以:
- 证明生成的数字是随机的。
- 拥有足够多的预言机/节点,即使其中一个失败/损坏,您的智能合约也将持续存在。
这时,下面的例子展示了如何解决#1。您可以通过从足够数量的支持 Chainlink VRF 的节点中提取来解决 #2。
有关确切的实现,请参阅类似问题的此答案。
您需要使用一个函数向一个节点发出请求,该函数采用您生成的种子:
function rollDice(uint256 userProvidedSeed) public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) > fee, "Not enough LINK - fill contract with faucet");
uint256 seed = uint256(keccak256(abi.encode(userProvidedSeed, blockhash(block.number)))); // Hash user seed and blockhash
bytes32 _requestId = requestRandomness(keyHash, fee, seed);
emit RequestRandomness(_requestId, keyHash, seed);
return _requestId;
}
当返回值时,您将其修改 100 并加 1。如果您需要 7 或 8 个随机数,则需要调用 7 或 8 次。
function fulfillRandomness(bytes32 requestId, uint256 randomness) external override {
uint256 d6Result = randomness.mod(100).add(1);
emit RequestRandomnessFulfilled(requestId, randomness);
}
我有一个头脑风暴的想法,也许可以帮助某人。
这是一种简化的 Commit-reveal 方法,只有一个参与者。每个随机生成都需要一个标题。那个标题应该是标准的,容易审核的。
首先我在 smartContract 上 Commit("Alice's Lotery")。如果标题重复(检查哈希),它将被拒绝。并且为了显示需要等待至少 1 个额外的块确认,这 2 个块应该来自不同的矿工,以确保矿工不会攻击这个智能合约。
然后执行 Reveal("Alberto's Lottery")。魔法在这里发生;random 的来源将是提交块的标题、msg.sender、block.blockhash 和 block.blockhash(commitBlockNumber+1),因为没有人可以预测未来的哈希,也没有哪个矿工会发现它[你可以添加 coinbase 或时间戳也可以获得更多的随机值]。您还可以检查 commitBlockNumber 和 commitBlockNumber+1 的时间戳是否太接近或太分开,这可能表明某些矿工正试图强制某些块,因此您可以拒绝此彩票。
当然,如果您可以通过 ("Alice's Lottery") || 之类的提交观看太多关闭的交易。(“爱丽丝的彩票”)你可以探测到这个彩票被欺骗了。您也可以使用超过 2 个“间隔”块来执行此操作