我们如何在以太坊中生成多个随机数?

How can we generate multiple random number in ethereum?

我希望我的智能合约在调用合约时 return 7 或 8 UNIQUE 范围从 1 到 100 的随机数。获得这种结果的最佳方法是什么?

正如Raghav所说,区块链上的随机数是很难的。 public 网络的性质使得生成无法预先计算的数字非常困难。

话虽如此,最好的解决方案之一是使用从外部(阅读:基于非区块链的)来源获取随机数的预言机。看看this guide. The Ethtroll Dapp is a good example of this, so take a look at the code here。他们使用 Oraclize 从 Random.org.

中获取随机数

使用预言机的一个问题是中心化因素。如果你按照我上面描述的方式设置你的 Dapp,你将受到两个不同中心化服务的流氓员工的摆布——Oraclize 和 Random.org。尽管有人不太可能操纵这些来源中的任何一个,但人们会为了潜在的经济利益而采取非理性行为。

可能如果您尝试使用以太坊区块链构建轮盘赌、彩票和纸牌游戏,由于以太坊区块链是确定性的,它会给那些选择编写自己的伪随机数生成器的人带来一定的困难( PRNG).

目前使用的一些易受攻击的方法

如果你使用block.coinbase、block.difficulty、block.timestamp等块变量作为熵源,所有这些块变量都可以被矿工操纵,所以由于矿工的激励,它们不能用作熵的来源。由于块变量显然在同一块内共享,您可以轻松地使用内部消息产生相同的结果。

其他方法比如使用当前或过去区块的区块哈希 或过去区块的区块哈希与私有种子相结合。 block.blockhash(block.number) 函数用于这些情况。然而,在 EVM 中执行交易时,由于显而易见的原因,尚不知道正在创建的区块的区块哈希,EVM 将始终产生零。如果我们尝试使用前一个区块的区块哈希,攻击者可以使用相同的代码创建漏洞利用合约,以便通过内部消息调用目标合约。两份合约的“随机”数字将相同。

即使我们将 blockhash 与私有种子结合起来,在本质上是透明的,区块链也不能用于以明文形式存储秘密。从合约存储中提取私有变量指针的值并将其作为参数提供给漏洞利用是微不足道的。

一些值得探索的领域

  • 外部神谕
  • 符号
  • 提交-揭示方法

借助 Oraclize 等外部预言机,智能合约可以从 Web API 请求数据,例如货币汇率、天气预报和股票价格(如 random.org)。这种方法的主要缺点是它是集中式的。 Oraclize 守护进程会篡改结果吗?我们可以信任 random.org 吗?

除了 Oraclize,我们还可以使用 BTCRelay,它是以太坊和比特币区块链之间的桥梁。使用 BTCRelay,以太坊区块链中的智能合约可以请求未来的比特币区块哈希并将它们用作熵的来源。

Signidice 是一种基于加密签名的算法,可用于在仅涉及两方的智能合约中生成随机数:玩家和庄家。该算法的工作原理如下:

  • 玩家通过调用智能合约进行下注
  • 房子看到赌注,用私钥签名,并将签名发送到智能合约。
  • 智能合约使用已知的 public 密钥验证签名。
  • 然后使用此签名生成随机数。

提交-显示方法包括两个阶段:

  • “提交”阶段,当各方将其加密保护的秘密提交给智能合约时。
  • 一个“揭示”阶段,当双方宣布明文种子时,智能合约验证它们是正确的,并且种子用于生成随机数。

提交-显示方法的更好实现是Randao。 Commit–reveal 可以与未来的 blockhashes 相结合,以使其更安全。

这几乎涵盖了使用以太坊生成随机数的所有方法。

使用 Chainlink VRF.

使用 blockhash 或类似的随机播种方法存在许多问题。如果攻击者在您的合同之前就知道了区块哈希,他们就可以使用该信息在您尝试做的任何事情上获得恶意优势。预言机可以提供帮助,但它们是失败的核心来源,必须能够证明它们是随机的。

您需要一个预言机网络能够:

  1. 证明生成的数字是随机的。
  2. 有足够的 oracles/nodes 即使一个 fails/is 损坏,您的智能合约也会持续存在。

此时,下面的例子展示了如何解决#1。您可以通过从足够数量的支持 Chainlink VRF 的节点中拉取来解决 #2。

对于确切的实施,see this answer from a similar question.

您需要向一个节点发出请求,该节点的函数需要您生成的种子:

 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;
    }

当返回值时,您将 mod 它乘以 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 方法,只有一个参与者。 每个随机生成都需要一个标题。 该标题应该是标准的,并且易于审核

首先,我在智能合约上提交(“爱丽丝的彩票”)。如果标题重复(检查哈希值),它将被拒绝。 并且对于 reveal 需要至少等待 1 个额外的块确认,这 2 个块应该来自不同的矿工,以确保矿工不会攻击这个智能合约。

然后执行 Reveal("Alberto's Lottery")。 魔术发生在这里;随机源将是提交块的标题、msg.sender、block.blockhash 和 block.blockhash(commitBlockNumber+1),因为没有人可以预测未来的哈希值,也没有哪个矿工会发现它[你也可以添加 coinbase 或 timestamp 以获得更多随机值]。 您还可以检查 commitBlockNumber 和 commitBlockNumber+1 的时间戳是否太接近或太分离,这可能表明某些矿工试图强制某个区块,因此您可以拒绝该彩票。

当然,如果您可以通过提交(“Alice's Lottery”)之类的方式观看太多接近的 tx || ("AAlice's Lottery") 你可以探测到这个彩票被骗了。 你也可以用超过 2 个“间隔”块来做到这一点