AElf随机数合约标准ACS6
本文主要讨论区块链系统中随机数的常见方案,AElf中对于可提供随机数的智能合约提供的标准接口,以及AEDPoS合约对ACS6的实现。 关于ACS的说明可见这篇文章的开头。 区块链和随机数区块链系统中,与合约相关的随机数应用大致有几种场景:抽奖、验证码、密码相关等。 而由于区块链本质上是一个分布式系统,他要求各个节点的运算结果是可验证的,传统的随机数生成结果在不同机器上基本不会一致,让所有的节点产生同样的随机数又不造成过多的延时是不可能的。 好在区块链系统中生成一个可用的随机数,我们已知有几种方案。 中心化生成随机数。随机数由可信的第三方提供,如RANDOM.ORG。Commitment方案,或者hash-commit-reveal方案。如果读者有读过AElf白皮书会发现AElf主链共识用于确定每一轮区块生产者确定生产顺序的in_value和out_value便采用了这种方案:区块生产者在本地生成一个随机的哈希值in_value后,先公布承诺(即out_value),其中out_value = hash(in_value),到了合适的时机再公布随机哈希值in_value,其他节点只需要验证out_value == hash(in_value)就可以了。这里的in_value可以认为是一个随机数。采集区块链状态信息作为种子,在合约中生成随机数。万一被人知道了随机数的生成算法(智能合约的代码是公开的),再获取到正确的种子,这个方案生成的随机数就可以成功预测的。不敢相信还真有人用这种方式。 显然,站在去中心化的角度上考量,Commitment方案至少是一个可用的方案,只需要保证作出承诺(commitment)的人不会自己偷偷提前公开随机数,或者自己利用随机数作弊即可。 然而很不幸,其实在区块链系统中,这是无法保证的:我们无法保证生成随机数的人不会利用信息不对等来做出不公平的事情,比如当这个随机数被作为某次赌局开奖的依据时,随机数的生成者哪怕在赌局开始之前就做出了承诺,依然可以选择性地中止公开这个随机数——这样相当于他得到了“再玩一次”的机会,因为如果他不公开这个随机数,要么赌局会选择其他人公开的随机数,要么这个赌局会作废。 如果预防随机数生产者的选择中止攻击呢?有一系列成熟的方案,参看Secret Sharing。 简单解释一下:现在有五个人A~E,每人掌握一个公私钥对,此时A产生了一个随机数Random,生成对应的承诺Commitment,同时将随机数Random与B、C、D、E的公钥进行加密得到四个SharingPart,加密SharingPart时便保证只需要凑够B~E中两个人就可以恢复Random,将SharingPart和Commitment一起公开。这样哪怕他自己因故没有公开Random的值,B~E中任意两个人用自己的私钥分别对自己收到的SharingPart解密,凑齐两个解密后的数值(要按A加密出SharingPart的顺序),便可以恢复出Random。而万一两个SharingPart没能恢复出Random,只能认为A从一开始就决定作恶——这时候只需要在区块链经济系统的设计中,让A付出代价就行了。比如直接扣除A申请称为随机数生产者缴纳的保证金。(TODO: 画图) 此外,我们还可以选择不依赖某一个个体产生的随机数,而是选择多个个体的随机数进一步计算哈希值作为应用场景中的可用随机数。如此我们可以比较稳定、安全地在区块链系统中得到随机数。 ACS6 - Random Number Provider Contract Standard我在之前的一篇文章中解释了AElf区块链关于共识的合约标准,实际上是作为AElf主链开发者对合约开发者实现共识机制时推荐实现的接口。而关于随机数生成,我们制定了ACS6,作为对任何提供随机数的合约推荐要实现的接口。 不出意外地,ACS6是选择对Commitment方案进行抽象,得到的合约标准。 支持使用ACS6的场景如下: 用户对实现了ACS6的合约申请一个随机数,类似于发送了一个定单;实现了ACS6的合约给用户返回一些信息,这些信息包括用户可以在哪个区块高度(H)获取得到一个随机数,以及用户获取随机数可用的凭据T(也是一个哈希值);等待区块链高度到达指定高度H后,用户发送交易尝试获取随机数,凭据T需要作为该交易的参数;实现了ACS6的合约根据凭据T返回一个随机数。如果用户尝试在高度H之前获取随机数,本次获取随机数的交易会执行失败并抛出一个AssertionException,提示高度还没到。 基于以上场景,我们设计的ACS6如下: service RandomNumberProviderContract { rpc RequestRandomNumber (google.protobuf.Empty) returns (RandomNumberOrder) {}rpc GetRandomNumber (aelf.Hash) returns (aelf.Hash) {}} message RandomNumberOrder { sint64 block_height = 1;// Orderer can get a random number after this height.aelf.Hash token_hash = 2;}用户发送RequestRandomNumber交易来申请一个随机数,合约需要为本次请求生成一个凭据(token_hash),然后把该凭据和用户能够获取该随机数的区块高度一起返回给用户。高度达到以后,用户利用收到的凭据(token_hash)发送GetRandomNumber交易即可得到一个可用的随机数。作为合约,在实现该方法的时候应该缓存为用户生成的凭据,作为一个Map的key,这个Map的value则应该根据合约自己对随机数的实现自行定义数据结构。 比如,AEDPoS合约在实现ACS6的时候,可以将该Map的value定义为: message RandomNumberRequestInformation { sint64 round_number = 1;sint64 order = 2;sint64 expected_block_height = 3;}其中round_number指示为了生成该用户申请的随机数,应该使用哪一轮(及之后)各个CDC公布的previous_in_value值;order为这个用户申请随机数的RandomNumberProviderContract交易被该轮第几个CDC打包(所以需要使用该轮该次序以后公布的previous_in_value作为随机数生成的“原材料”);expected_block_height则是要告知给用户的需要等待到的区块高度。 ...