详解Neoverse NFT设计逻辑与规则

This article is not available in the current language yet. Showing the original version.
本文分享Neoverse NFT算法设计和合约开发的一些有趣细节。

近期,我们宣布了Neoverse NFT收集活动,通过开盲盒生成N3元素碎片,而合成9款不同的元素碎片,即可合成一款由国际知名NFT艺术家专门打造的N3典藏版NFT。

那么这项收集活动的玩法设计逻辑是什么?共9款N3典藏版的生成规则又是怎样的呢?

这篇文章中,NGD参与此次活动的开发者将与大家分享Neoverse NFT算法设计和合约开发的一些有趣细节。

算法设计

  1. 开盲盒游戏怎么玩?

一共有27000个盲盒,一个盲盒的价格为2GAS,买十送二。开盲盒的意思是将盲盒销毁,随机获得N3元素碎片。碎片共有9种,各自编号独立,都是从1到3000。例如Fragment A #733、Fragment I #2314等。

用户合成全部9种碎片后,可以获得一个N3典藏版NFT,该典藏版NFT的种类由合成它的9种碎片的序列号之和决定。序列号之和小于4816则为N系列,大于4816且小于6411为E系列,其余为O系列。

  1. 为什么是9个碎片合成一个NFT?

九种碎片分别代表了Neo的9个属性:Interoperability, Native oracles, Self-sovereign ID, Decentralized storage, Neo name service, One block finality, Best-in-class tooling, Smart contracts, Multi-language。

九种N3元素碎片合成一个N3典藏版NFT,体现了Neo“All in ONE”的理念。

  1. 4816和6411是怎么算出来的?

在讨论碎片背后的数学逻辑之前,我们先介绍一下其市场逻辑。

一级市场:碎片将通过空投和拍卖进行随机分发。

二级市场:碎片可以在NFT交易平台上交易。

其中,市场活跃度的大小将会直接影响凑出的碎片序列号之和的大小。根据算法模拟,在市场交换最充分的时候,N系列号为900;在市场交换最不充分的时候,N系列号为8731。因此,如果我们假设市场活跃度为中等,则其均值为4816。同理,可以算出E系列号的均值为6411。

合约开发

盲盒游戏最重要的部分是随机性。既然涉及到随机性,一定有人问你们的随机性是怎么实现的,是否公平,能不能被预测,能不能被黑客利用等。

我们通过以下几个版本的合约来逐步分析,一步步找到最佳的开盲盒方案。

青铜版本:

在开盲盒的时候,取当前区块的Nonce(在N3的区块中,有一个字段叫Nonce,它是随机的,但是是固定不变的),作为随机数种子,再对3000取模+1,获得1~3000的随机数。

这样操作看起来很简单直接,但是存在很多问题:

1、同一个区块所开的盲盒都是相同的结果

2、取出的随机数可能有重复

白银版本:

在这个版本中,针对上个版本的1号问题进行了修复。首先想到的是将区块的Nonce和交易ID进行异或操作,获得随机数的种子,但这样在一笔交易中开出的盲盒又是相同的结果,显然也是不行的。然后想到对盲盒的TokenId进行哈希运算,将其转为大整数,并与当前区块进行异或操作,获得随机数的种子。因为每个盲盒的TokenId是不同的,哈希自然也是不同的,与Nonce进行异或操作,可避免每次都产生同一个随机数。但是NGD工程师黎工表示直接使用当前区块的Nonce会有一些被人利用的风险:

1、黑客可以发布一个合约A,在合约A中调用开盲盒的方法,并且对开盲盒的结果进行判断,如果发现开盲盒的结果不满意则抛出异常,中断合约执行。

2、黑客可以通过在开盲盒的脚本后面追加精心构造的OpCode,完成上面的步骤。

这两点在前一版本中也同样存在。

黄金版本:

在这个版本里,我们将随机数放在买盲盒之后,开盲盒之前。这样当你买盲盒的时候无法预测将来的区块Nonce,当你开盲盒的时候,结果是确定的。

具体操作如下。购买盲盒时,记录下下一个区块的索引。当你开盲盒时,取那个区块的Nonce并和TokenId的哈希进行异或。

这样就避免了黑客的攻击,但是针对青铜版本的第二个问题,仍没有解决。两个盲盒可以开出同样的碎片,比如 Fragment A #33

铂金版本:

在这个版本里,我们主要解决随机数重复的问题,有几种解决方案:

1、存储区中存储所有未被使用的随机数(最多27000个),每用到一个,就将其删除。下个人取随机数时,读取存储区中的随机数数组,从中取随机数。

2、存储区中存储所有已使用的随机数(最多27000个),每取一个随机数,就将其存到存储区里。下个人取随机数时,读取存储区中的随机数数组,找到未使用的随机数,从中取随机数。

以上两种方案其实是等价的,无论存储区中存储已使用的,还是未使用的随机数,都要大量使用存储区。按每个随机数2字节计算,一共54KB的数据,写入一次约13.5GAS,手续费是相当高昂的。

那么如果用位(Bit)存储呢,一个长度为3375的字节数组,每一位的下标表示随机数本身,每一位的值(0或1)表示该随机数是否使用。再结合分片操作,将9种碎片的随机数分开存储。这样读取写入的费用会大大降低,但考虑到将位还原为数组,会有大量计算,手续费仍然不很乐观。NGD工程师印工提出了一种新的解决方案。针对每种碎片的1~3000的随机数,存储区中存储最后一个随机数的下标,和抽取替换的过程。具体来说,假设用户抽取了下标为500的随机数,则把500给他,并在存储区中记录k:500,v:3000,表示下标500的位置存放的是随机数3000。然后将随机数下标的最大值3000更改为2999。第二个用户又抽取到下标为500的随机数,检查存储区,将下标500位置的随机数3000给用户,并更新存储区k:500,v:2999。然后将随机数下标的最大值2999更改为2998。依此类推。

随机数生成的描述为:第一个人,Nonce模3000+1第二个人,Nonce模2999+1;第三个人,Nonce模2998+1;

细心的用户又会发现,在黄金版本中,开盲盒的结果是确定的,不随着你开的先后顺序而变化,但在这个版本中,你先开和后开,结果可能会变化。这样又暴漏了另一个问题:

1、黑客可以在本地对合约进行预执行,如果结果满意,则广播交易,如果不满意,则等待其它人开出该类碎片后再进行预执行操作,直到它满意了,发送交易。

2、白银版本中的两个问题又出现了。

星钻版本:

到这里我就不得不吹一下Neo底层中的随机数生成算法的厉害之处了。其实这个随机数算法是在N3上线前加入的,我在写前几个版本的合约的时候还不能使用。NGD工程师刘工给我介绍了这个随机数算法。它能保证真随机、不可预测(预执行和链上执行的随机数不同)、每使用一次就会更改。在合约中也可以很方便地用互操作服务(Runtime.GetRandom())来使用这个随机数。结合铂金版本中印工的方案,即可每次获得1-3000中不重复的随机数。到这里,随机数部分已经近乎完美了。但是白银版本中提到的两个问题是真正在链上执行,只是增加对结果的判断,这是随机数算法所不能干预的,要通过其它安全限制来规避。我计算了一下,黑客通过这种方式开盲盒的成本比较高,初始成本为10.1GAS,之后每尝试开一次盲盒成本为1.05GAS。尝试两次都不满意的话,相当于直接浪费了一个盲盒(1个盲盒的价格为2GAS)。但即使这样,即使黑客亏本攻击,我们仍然希望他无从下手,所以有了下一个版本。

皇冠版本:

我和Neo社区工程师廖工在星钻版本上进行了改进。

1、在这个版本中添加了对合约调用的限制,只允许通过交易来调用Neoverse合约进行开盲盒操作,不允许通过其它合约进行调用。

2、针对开单个盲盒以及批量开盲盒的方法,根据实参长度精确计算所需脚本长度。不允许在标准调用脚本中附加任何一个字节的脚本。

到此合约近乎完美。

Share to:

Author: Joy

Opinions belong to the column author and do not represent PANews.

This content is not investment advice.

Image source: Joy. If there is any infringement, please contact the author for removal.

Follow PANews official accounts, navigate bull and bear markets together
PANews APP
FIFA has designated ADI Predictstreet as the official prediction market for the 2026 World Cup.
PANews Newsflash