字节雪球(Byteball):一种价值传输及数据存储的分布式系统(一)
字节雪球(Byteball):一种价值传输及数据存储的分布式系统
作者:Anton Churyumov(tonych@Byteball.org)
翻译:Alan During(https://bbfans.org)
2019年1月25日
PDF版本下载链接
摘要(Abstract)
Byteball是一种具有防篡改证明的分布式数据存储系统,它可以存储任何数据,包括用于记录价值传输信息的数据,比如数字资产、产权、债务、股份等。存储单元之间采用前向引用的方式进行连接,每个存储单元将包含一个或多个之前存储单元的哈希值,从而用来确认之前的单元并给出自身的偏序。所有的存储单元之间的连接构成有向无环图(DAG, Directed Acyclic Graph)。网络中不存在具有数据库管理权限的单点中心,所有人都可以在数据库中添加新的存储单元,只要他提供相应的数据签名并支付与数据大小对应的手续费。手续费将分配给后续包含该存储单元哈希值的那些存储单元。随着新的存储单元的不断加入,前面的存储单元将不断地被确认。
Byteball中的原生数字资产称为字节(bytes),它用来支付将数据添加至分布式数据库的手续费。用户也可以在Byteball中发布其它类型的数字资产,比如产权、债务、股份等。对于这些数字资产,用户可以直接进行转账从而用于购买商品或服务,用户还可以在不同资产之间进行交换;这类包含价值转移的交易数据将被添加到Byteball的数据库中。如果两笔交易尝试使用相同的交易输出(即发生双花),且两笔交易的存储单元之间不具有偏序,那么两笔交易都被允许添加至数据库中,但是最终只有总序靠前的那笔交易是有效的。总序是通过在DAG中选择一条单链(即主链)的方式来确定,主链的选择由见证人完成。更早被主链包含的那些交易单元将具有更靠前的总序。用户将在每个交易单元中包含他信任的见证人列表。见证人是现实世界中信誉良好的用户,且从来没有尝试过进行双花交易。只要大部分的见证人都正常工作,那么所有的双花交易将被及时地检测到并被标记无效。随着见证人不断地发出交易单元,它们将最终确认之前的交易单元,且该最终状态具有确定性而不是概率性。
用户可以使用多签名地址存储数字资产,该地址的资产需要多个用户同时签名才可以使用。除此之外,还可以附加其它的使用条件,包括是否查询到其它用户发出的特定数据(播报机)。
用户能够发布新的数字资产,并设置该类型资产的转移条件,比如每次转移都需要资产发布者进行签名(可以作为遵循现行金融机构规则的一种方式)。特别地,用户还可以发布隐私资产,它的转移并不会记录到公开数据库中,从而不会被第三方看到。这类资产的转移只在用户之间秘密地展开,只有交易的哈希值和花费证明(用来防止双花)被记录在公开数据库中。
1. 简介(Introduction)
在奥威尔的反乌托邦小说《1984》中,主角温斯顿·史密斯在负责宣传和修改历史的真理部工作,主要是重新编写过去的报纸,好让历史记录一如既往地支持政党的发展路线[1]。那些被除名的人不仅在真实世界中被杀害,而且在所有的历史存储中记录里“蒸发”。我们现在这里讨论的是一种不可篡改的数据存储方式。它是一类分布式去中心化的数据库,其中的数据不可任意修改或删除。
比特币(Bitcoin)是第一个引入价值转移记录的系统,它被设计用来追踪数字货币即比特币的所有权。在比特币中,所有的数字货币转移都记录在相应的交易记录中,交易记录由数字货币的所有者进行签名,并被打包在区块中。区块之间通过连接构成区块链,并采用工作量证明(PoW, Proof of Work)共识保证其安全性。任何需要修改已经被包含在区块链的数据都需要提供比已有更大的计算工作量才可以进行。
在比特币出现后,人们发现还有更多的共识机制来实现去中心化的点对点数字货币。相关的技术也不断涌现,并被用来解决其它的问题。与此同时,比特币的缺陷和不足也逐步显现出来。Byteball的设计初衷就是用来实现任意数据的防篡改存储,而不仅仅限于数字货币的交易记录,同时对比特币的扩展性进行优化和改进。
区块 在比特币中,交易记录被打包成区块,区块连接形成区块链。由于区块采用线性连接,区块的大小和出块的时间间隔都需要保证全网节点能够及时同步,即区块的传播速度需要比新区块的产生速度更快。这样各个节点都能够基于相同的最新区块打包下一个区块,从而尽量减小产生孤块的可能性。随着比特币的发展,区块的扩展性受到了限制。如果区块的大小保持不变,那么每个区块可以打包的交易数量将受限;如果区块的大小不断增大,那么全网节点可能无法及时同步到相同的最新区块,这样将增大产生孤块的可能性,从而浪费了计算资源。在Byteball中,不存在区块的概念,交易记录本身就是“区块”,并且不再连接形成单链结构,而是组成有向无环图(DAG, Directed Acyclic Graph)。基于DAG的设计思路近期得到了较多的关注[3-5]。
手续费 比特币交易的安全性是通过工作量证明保证的,因为修改交易需要完成的计算工作量是非常大的。同时,这也意味着需要支付手续费来保证有足够的计算能力来抵御各种攻击。这些费用用来支付进行工作量证明计算所需的电力费用。值得注意的是,这些费用将流出比特币的生态系统到电力公司,这意味着整个比特币社区在不断地给中心化部门提供资金。在Byteball中,不再有PoW,相应地我们使用了另一种在比特币出现之前就已经产生的共识机制。
最终确认性 在比特币中,交易的确认是概率性的。不存在严格而简单的标准来判断某个交易是否永远不会被逆转。相反地,你只能推测说,随着更多的区块被添加,交易被逆转的概率呈指数衰减。虽然这个概念对那些精通数学的人来说非常清楚,但对于那些习惯于在货币所有权问题上具有确定性的普通用户来说,这可能是一个难以接受的事情。使事情进一步复杂的是,交易的最终确认性还取决于其数量。如果金额很小,你可以合理地假定没有人会试图对你双花。但是,如果交易所涉及的金额大于块奖励(撰写本文时为12.5 BTC),付款人有可能会临时租用算力来挖掘另一个不包含该交易的区块。因此,在确定高价值交易是否确认时,您必须等待更多区块。但是,在Byteball中,无论交易额有多大,交易被视为最终确认是有确定性标准的。
价格 众所周知,比特币价格波动很大。更大的问题是,这个价格不仅不稳定,而且不受任何限制。股票和商品价格也非常波动,但它们背后有基本面。股价主要取决于公司收益、收入、债务与资本比率等。除其他因素外,商品价格还取决于各供应商的生产成本。例如,如果油价长期低于某些供应商的生产成本,这些供应商最终将倒闭,从而油价产量减少并导致价格上涨。这是一个负反馈循环。在比特币中,没有基本面,也没有负反馈循环。比特币500美元的价格不比5万美元或5美元的价格更为合理。如果比特币从现在的价格开始上涨,那么价格上涨本身不会产生任何会推动价格回落的经济力量。这很疯狂。在Byteball中,基础货币bytes用于支付将数据添加到Byteball数据库中的费用。您需要支付1000个字节才能添加1Kb的数据。它是对此数据库中存储效用的衡量标准,实际用户将对此的合理价格有所了解。如果bytes的价格高于您认为合理的价格,您将找到存储更少数据的方法,从而购买更少的bytes,需求减少,价格下降。这种负反馈循环而不是投机对所有需求驱动的商品/服务而言都是常见的。除了以bytes为单位进行支付外,还可以发行其他数字资产并将其用作支付手段。例如,这些资产可能代表以法定货币或商品(如千瓦时或石油桶)表示的债务。这些资产的价格自然与相关货币或商品有关。
隐私性 在比特币中,所有地址的交易记录和余额都在区块链中可见。尽管有一些方法可以对一个人的交易记录和余额进行混淆,但这并不是人们对货币的期望。Byteball中以bytes作为基础货币的交易是全部公开的,但另一种基础货币blackbytes是完全不可追踪的。
合规性 比特币被设计成一种匿名数字货币,人们可以绝对控制他们的钱。这一目标实现了;然而,这使得比特币与现有法规不相容,因此不适合在金融行业中使用。在Byteball中,用户可以发布具有转移性规则的数字资产,从没有任何限制(像比特币一样)到要求每次转移都必须由发行人(例如银行)签署或限制为一组有限的白名单用户。
2. 数据结构(Database structure)
当用户想要将数据添加到数据库时,他创建一个新的存储单元并将其广播给网络中其它节点。存储单元包括:
-
需要存储的数据。存储单元中可以包括多个数据包,数据包也称为消息。有许多不同类型的消息,每种消息都有自己的结构。其中一种消息类型是支付消息(payment),用于将bytes或其他资产发送给接收方。
-
创建该单元的一个或多个用户的签名。用户是通过其地址标识的。类似比特币,用户可以拥有多个地址。在最简单的情况下,地址通过公钥生成,这也跟比特币类似。
-
引用一个或多个先前单元的哈希值(父单元)。
对父单元的引用建立了单位的次序(目前为止只有偏序),并构成了图结构。由于我们并不限定对父单元只能是一对一的连接关系,因此我们不必努力实现节点间的近实时同步,从而可以安全地容忍大延迟和实现高吞吐量:我们将扩展单元的连接关系,使得每个单元可以有多个父单元和子单元。当我们沿着连接路径前进时,如果一个单元同时被多个子单元引用,那么我们将在这个单元上观察到多个分支;如果一个单元引用多个父单元,则我们将在这个单元上看到多个分支的合并(经常使用git的开发人员常常会看到这种现象)。在图论中,该结构称为有向无环图(DAG),存储单位作为图的顶点,父子单元之间的连接是图的边。
图1:存储单元连接形成DAG。连接箭头从子单元指向父单元,G为创世单元。
在新单元产生的数量极少这种特殊情况下,DAG看起来几乎就像一条链,只有非常偶尔的分叉并会快速合并。
与区块链类似,每个新区块确认所有先前的区块(以及其中的交易),DAG中的每个新的子单元确认其父单元,父单元的所有父单元,父单元的父单元的父单元等。如果一个人试图修改存储单位,那么他必须改变其哈希值。这样不可避免地会破坏所有通过哈希引用此单元的子单元,因为子单元的签名和哈希都依赖于父单元哈希。因此,如果没有与其所有子单元的用户合作或窃取他们的私钥,就不可能修改一个单元。反过来,如果没有与子单元的子单元(即孙单元)合作,子单元的用户就无法修改他们的单位,以此类推。一旦一个单元被广播到网络中,并且其他用户开始在它上面构建它们的单元(将其称为父单元),修改该单元所需修改的单元数量将像滚雪球一样增长。这就是为什么我们把这个设计称为Byteball(“雪花”就是存储数据花费的字节)。
在区块链中,产生区块是一个小概率事件,只有矿工实际参与此活动。与之不同的是,在Byteball中存储单元在发布后立即开始累积确认,并且确认可以来自任何用户,且每次产生一个新单元增加一次确认。普通用户和矿工没有严格的区分。相反,用户互相帮助:通过添加新单元,用户同时也确认自己所有以前的单元。
与比特币不同,比特币尝试修改过去的交易需要大量的计算工作,而Byteball尝试修改过去的记录需要与越来越多的其他用户协调,其中大多数是匿名陌生人。因此,记录的不可篡改性是基于与大量陌生人协调的复杂性,这些陌生人彼此不认识,无法相互合作,并且每个人都可以否决修改。
通过引用父单元,一个单元可以确认其父单元。但是,它并不需要包括父母的全部内容,它只要保存父单元的哈希值。同样地,该单元间接地依赖并包含父单元的父单元,以及更早的单元等等,每个单元最终将包含创世单元。
在引用父单元时,单元必须遵守不能引用冗余父单元的规则,即父单元之间不存在包含关系。例如,如果单元B引用单元A,则单元C不能同时引用单元A和B作为父单元。在某种程度上,A已经包含在B中。此规则去除了那些不能给DAG增加有效连接的不必要连接。
3. 基础货币:字节(Native currency: bytes)
接下来,我们需要设置一些条件来防止通过无用消息向数据库发送垃圾数据的行为。发送条件应该大致反映用户的存储效用和网络的存储成本。对这两者最简单的衡量方法是存储单元的大小。因此,要将数据存储在分布式数据库中,必须以基础货币字节(bytes)来支付费用,并且支付的金额等于需要存储的数据的大小(包括所有标题,签名等)。类似于英镑,当它首次引入时相当于一磅白银,货币的名称反映了它的价值。
为了使激励措施与网络的利益保持一致,在存储单元大小的计算规则中有一个特殊规则,即不论单元包含了多少个父单元,在计算数据大小时统一假定为2个父单元。因此,2个父单元哈希的大小始终是计算在单元大小中的。此特殊规则可确保用户不会为了尽量降低成本而仅包含一个父单元。因为无论包括多少父单元,费用都是相同的。
为了使DAG尽可能地变窄,我们鼓励用户尽可能包含更多的父单元(如前所述,这不会对发送费用的大小产生负面影响)。同时,尽可能将部分的发送费用支付给那些“首次包含”它作为父单元的子单元。我们稍后会定义什么是“首次包含”。
字节(bytes)不仅可用于支付存储费用(也称为手续费),还可以发送给其他用户用来支付商品或服务或换取其他资产。要使用bytes进行付款,用户会创建一个新单元,其中包含付款消息,例如以下内容(从现在开始,我们使用JSON来描述数据结构):
{ inputs: [ { unit: "hash of input unit", message_index: 2, // index of message where this utxo was created output_index: 0 // index of output where this utxo was created }, ... ], outputs: [ { address: "RECEIVER ADDRESS", amount: 15000 // in bytes }, ... ]}
该消息包含:
-
输出数组(outputs):一个或多个接收bytes的地址以及相应的金额。
-
输入数组(inputs):一个或多个先前的输出。这些是发送给用户但还未花费的输出。
输入的总和应该等于输出加上手续费的总和(输入需要从先前的输出中读取,在消费时并没有明确指出)。该单元将使用用户的私钥签名。
bytes的流通总量为10151015,这个总量是不变的。所有bytes都在创世单元中发出,然后通过用户发送给用户。其他用户收集手续费以帮助保持网络正常运转(稍后会详细介绍),所有bytes可以循环使用。选择数量10151015作为总量的原因是,这是可以用JavaScript表示的最大正整数。bytes的金额只能是整数。通过使用不同的前缀可以得出较大的货币单位:1千字节(KB)是1000字节(bytes),1兆字节(MB)是100万字节(bytes)等。
4. 双花交易(Double-spends)
如果用户尝试使用相同的输出进行花费,则有两种可能的情况:
-
试图花费相同输出的两个单元之间存在偏序,即其中一个单元(直接或间接)包含另一个单元。在这种情况下,很明显我们可以安全地拒绝后一个单元。
-
两个单元之间没有偏序。在这种情况下,我们先同时接受两者。随后当我们得到足够多的新单元时,我们将根据之后的单元建立一个总序(具体做法见下文)。总序中较早出现的单元视为有效,而另一单元视为无效。
有一个协议规则可以简化总序的计算。我们要求,如果同一地址发布一个以上的单元,它应该(直接或间接)包含该地址的所有之前的单元,即在同一地址的单元之间应该有偏序。换句话说,同一作者的所有单元都应该是连续的。
如果用户违反此规则并发布两个单元,使得它们之间没有偏序(即非连续单元),则即使它们没有使用相同的输出,这两个单元也会被视为双花交易,并按照上面第2种情况进行处理。
图2:双花交易。两个交易单元之间没有偏序。
如果用户遵循此规则但两次使用了相同的输出,则双花交易单元之间具有偏序,我们可以安全地拒绝后面的交易单元,如上面的情况1所示。因此,那些非连续的双花交易很容易识别出来。
事实上,这条规则很自然的。当用户构造新单元时,他选择最近的其它单元作为其父单元。通过把它们作为父单元,这意味着在他的视角下已经看到了这些单元。因此,他也见过这些单元的父单元,父单元的父单元等,直到创世单元。这个巨大的集合中显然应该包括他自己已经发出的所有单元。
对于那些没有直接或者间接包含的单元,用户实际上是在否认他已经看到过它。那么,当用户构造交易单元时,如果不包含他自己的单元,这意味着用户在否认他自己的交易单元,这看起来很奇怪,有些可疑的事情正在发生。我们不鼓励这种行为。
5. 主链(The main chain)
我们的DAG是一个特殊的DAG。在正常使用时,用户通常会将他们的新单元连接到最近出现的单位上,这意味着DAG只在一个方向上增长。可以将其想象成一条粗线,内部有许多交错线。这种属性表明我们可以在DAG中找到一条单链,然后将所有单元与此单链相关联。这条单链称为主链,所有单元要么直接位于主链上,或者可以通过相对较少的跳数来到达主链。主链就像一条连接多条侧路的高速公路。
构建主链的方式是设计一种算法,在给定单元的所有父单元的情况下,用于选择其中一个作为“最佳父单元”。选择算法应仅基于给定单元的可用知识,即单元本身及其所有父单元所包含的数据。从DAG的任何顶端单元(即没有子单元的单元)开始,我们沿着“最佳父单元”的连接向后回溯。以这种方式回溯,我们可以建立一条主链,并最终到达创世单元。请注意,从特定单元开始构建的主链在添加新单元时不会受到影响。这是因为每一步我们都是从子单元到父单元,而已有的单元的父单元是确定的。
如果我们从另一个顶端单元开始,我们将构建另一个主链。值得注意的是,如果这两条主链在向后回溯时相互交叉,它们将在交叉点之后沿着相同的路径行进。在最坏的情况下,主链只会在创世单元相交。然而,鉴于用户之间没有协商单元产生的过程,人们可能会发现主链通常在距离顶端单元不太远的地方相交。
图3:从不同顶端单元构建的主链相交并沿着相同的路径前进。在两个双花交易中,具有较小主链序号(5)的单元获胜,而另一个主链序号(6)的单元视为无效。
一旦我们确定一条主链(MC, Main Chain),我们就可以在两个相互冲突的非连续单元之间建立一个总序。让我们首先对主链上的单元进行排序。创世单元的序号为0,下一个位于主链的单元的序号为1,依此类推,沿着主链向前推进,我们将序号分配给位于主链上的单元。对于不在主链上的单元,我们可以找到首次包含该单元的主链序号(直接或间接)。通过这种方式,我们可以为每个单元分配主链序号(MCI, Main Chain Index)。
然后,双花交易中具有较小主链序号的单元被认为较早出现并且是有效的,而另一个是无效的。如果两个非连续单元碰巧具有相同的MCI,则具有较小哈希值的单元(如base64编码中所示)是有效的。请注意,我们会保留所有版本的双花交易,包括那些最终会丢弃的版本。DagCoin [3]是第一个提出:建议存储所有冲突的交易,并最终决定将哪一个视为有效。
从特定单元出发构建的主链代表了这个单元的作者对过去事件顺序的看法,即他对历史事件的看法。然后,该顺序用于决定哪个非连续单元被认为是有效的。请注意,通过在给定单元的所有父单元中选择“最佳父单元”,我们同时在他们对应的主链中做出了选择:当前给定单元的主链将是向前扩展“最佳父单元”的主链。
考虑到可能存在有(甚至所有)父单元是由攻击者创建的,同时记住“最佳父单元”的选择本质上是历史事件版本的选择,我们应该在“最佳父单元”选择算法中要求它倾向于选择从当前单元角度来看更接近真实的历史事件版本。因此,我们需要设计一个“真实性测试”算法,从而在所有候选主链中选择得分最高的主链。
6. 见证人(Witnesses)
已知网络中的一些用户是实名的信誉良好的人、或者具有长期声誉的公司、或者他们是对保持网络正常运转感兴趣的企业。在“真实性测试”的算法中,他们被称为见证人。虽然假定他们诚实工作是合理的,但完全信任任何一个见证人却是不合理的。如果我们知道见证人的地址,并且他们保持足够频繁地发送交易,那么我们可以通过计算见证人发送的单元数量来衡量候选主链的真实性(相同见证人发送的单元只计算一次)。在沿着候选主链回溯时,一旦遇到大多数见证人发送的单元,就停止回溯。然后,我们将计算图中从停止位置到创世单元的最长路径长度。并将这个长度称为停止位置的单元级别,以及具有当前候选主链的父单元的见证级别。具有更高见证级别的候选主链被认为更接近“真实”,并且具有该主链的父单元被选为“最佳父单元”。如果有存在多个父单元具有相同的见证级别,我们将选择单元级别最低的父单元。如果仍然存在多个父单元,我们将选择具有最小哈希值的父单元(使用base64编码)。
该算法通过见证人发送的单元来选择主链,见证人被认为是现实的代表。如果攻击者从网络的真实部分分叉,同时秘密地建立一个他自己发送的单元构成的长链(影子链),其中包含双花交易,然后将他的分叉合并回真实的DAG。那么,“最佳父单元”选择算法将在合并位置上从真实的DAG中选择主链,因为那里有更多见证人发送的单元。因为在合并之前,见证人无法在影子链上发送交易单元。主链的这种选择反映了见证人和选择他们的用户所看到的事件的顺序。攻击结束后,整个影子链将在同一个点落在MC上,影子链中包含的双花交易将被视为无效,因为其有效交易在合并点之前具有更小的主链序号。
图4:当攻击者将影子链接入真实DAG中,影子链中的单元将不具备最佳父单元的竞争力,因为其它路径上有更多见证人发送的单元(用w标记)
这个例子说明了为什么大多数证人必须被信任且只能发送连续交易单元。大多数见证人不应该与攻击者勾结并在他的影子链上发送交易。请注意,我们只是相信见证人作为真实性的标志,且不会在任何影子链上发布非连续单元。见证人不具有控制网络及其任何部分的权力。即使见证人只是具有发布真实交易的小职责,用户还保留了指定见证人的权利,他们可以随时改变他们的决定。
将一些已知实体视为真实性标志的想法并不新鲜。人们早就知道,并且一些公司已经参与了这样的活动。为了证明某些数据在特定日期之前存在,人们可以对数据进行哈希并在一些难以修改和广泛传播的媒体中发布哈希值,如印刷报纸[6]。Byteball中的见证人具有与报纸相同的功能。像报纸一样,它们是众所周知和值得信赖的。与报纸中我们仅信任他们如实地发布他们知道的数据一样,我们只是信任Byteball中的见证人发布连续交易单元,而没有其它更多的了。像报纸一样,见证人并不知道哈希值背后是什么,也没有理由关心。报纸难以修改(但可能,并且在《1984》中他们就这样做了),而见证人发布的所有单元都受到数字签名的保护,这使得任何修改都变得不可能。为了可靠性,我们有多个见证人,而不仅仅是一个;为了速度和方便,必须要求他们是保持在线的。
在确定了见证人名单之后,我们可以选择最符合真实性的父单元和相应的主链,即拥有更多见证人的路径。同时,父单元本身可能有不同的见证人名单,因此对真实性的定义也不同。我们希望真实性的定义和相应的主链能够围绕一些共同点展开。为此,我们引入了以下附加协议规则。
兼容性规则:最佳父单元只能在见证人名单与当前单元见证人名单不超过一个突变的父单元中选择。该规则确保主链上相邻单元的见证人列表足够相似,因此它们的历史大多彼此一致。见证人列表不超过一个突变的父单元将被称为兼容(与直接包含他们的单元),而其他父单元则不兼容。仍然允许不兼容的父单元,但他们没有机会成为最佳父单元。如果没有没有兼容的父单元(攻击者可能会通过洪泛攻击使得网络中出现一个完全不同的见证人名单),那么应该从更早的能够兼容的单元中选择父单元。
以上规则意味着每个单元必须列出其见证人列表才能进行比较。我们要求证人的数量正好为12,选择数字12是因为:
-
它足够大,可以防止少数见证人的偶然掉线(他们可能是不诚实、被黑、或长时间离线、或丢失私钥并永远离线);
-
它足够小,人们可以跟踪所有见证人,知道谁是谁,并在必要时更改列表;
-
与11名未改变的见证人相比,允许一个突变足够小。
如果用户认为任何见证人失去了他的可信度,或者有更好的候选人,用户可以用在他的名单中的用新见证人替换原来的见证人,同时保证他的见证人列表与其它单元的突变数量小于1个。这也意味着任何变化只能逐步发生,并且对于多于一个的见证人变更需要达成普遍共识。
7. 最终确认性(Finality)
当新交易单元到达时,每个用户都会尝试构建他的当前主链,在构建当前主链时假设他将根据当前所有顶端单元发布新单元。当前主链在不同网络节点处可能不同,因为它们可能看到不同的顶端单元集合。在建立当前主链时暂时不考虑见证人列表,即忽略用户自己的见证人列表,此时可以选择不兼容的父单元作为最佳父单元。这意味着如果两个用户拥有相同的顶端单元集合,但具有不同的见证人列表,则他们的当前主链仍然是相同的。当新交易单元到达时,当前主链将不断变化。但是,正如我们即将展示的那样,当前主链的一部分将稳定下来并保持不变。
我们希望见证人(或者更确切地说是其中的大多数见证人)诚实地工作,即同一见证人发送的交易单元必须包含他们以前所有的交易单元。这意味着当证人构建一个新单元时,只有最近的单元才能被选为父单元。因此,我们期望所有未来可能出现的主链将会在特定的稳定点之前收敛(当沿着主链回溯时)。实际上,创世单元是天然的初始稳定点。假设我们已根据当前的顶端单元组构建了一个当前主链,并且此主链上的某些点以前被认为是稳定的,即所有未来的主链都被认为在此点之前收敛,然后将沿着同一条路径前进。如果我们能够找到向前推进稳定点的方法(朝着远离创世单元的方向),我们就可以通过归纳证明存在稳定点。
请注意,当我们只保留交易单元与最佳父单元之间的连接时,DAG将退化为仅包含最佳父连接的树。显然,所有的主链都将沿着这棵树的分支前进。然后我们需要考虑两种情况:在当前稳定点发生分支以及不发生分支,并决定我们是否可以将稳定点提升到下一个MCI上。
图5:由最优父连接构成的树。在某个位置之后,只有一个分支继续生长。
首先,假设在当前稳定点不发生分支。我们需要考虑的是当前稳定点是否仍然可能添加新分支,并被见证人支持,使其超出现有分支。另一种可能性是,见证人已经在当前分支上发送了大量交易单元,由于存在需要包含之前所有的单元的限制条件,这使他们没有选择,只能继续支持现有的分支。我们需要量化后一种可能性。请记住,最佳父单元是具有最大见证级别的父单元。当从顶端单元沿着当前主链回溯,直到遇到大多数见证人的单元时停止(指的是由当前稳定点上的单元定义的那些见证人)。如果这些见证人的单元中至少有一个位于当前稳定点之前,我们不会试图提升稳定点,否则我们继续检查其它条件。在这种情况下,所有这些见证人已经开始支持当前主链。在这些见证人单元中,我们获取它们的最低见证级别min_wl。当这些见证人中的任何一个发布新单元时,该单元的父单元中有些是在当前分支上,有些是在其它竞争分支上,具有最高见证级别的父单元将被选为最佳父单元从而定义出当前主链的方向。由于见证人必须包括其先前的单元,因此通向当前分支的父单元的见证级别将至少为min_wl。任何通过竞争分支的父单元的见证级别将永远不会超过当前稳定点的单元级别,即使所有剩余的(少数)见证人都支持该竞争分支。因此,如果当前主链已经增长得足够长使得min_wl大于当前稳定点的单元级别,则大多数见证人将不得不继续增加对现有主链的支持,竞争分支也就失去了获胜的所有机会,因此我们可以将稳定点向前移动到下一个MCI。
接下来,假设当前稳定点发生分支。我们需要找到一个条件,保证竞争分支失去任何超过当前主链的机会。让我们从前一种情况中定义的min_wl开始。在竞争分支的所有单元中,我们选择那些使得见证级别增加的单元,即他们自己见证级别高于每个父单元的见证级别。选择这些单元中最高的单元级别。那么,即使所有其余(少数)见证人都聚集在竞争分支上,竞争分支上单元的见证级别也不会超过此最高单元级别。因此,如果此最高单元级别小于min_wl,则竞争分支的失去获胜机会,那么我们可以沿当前主链推进稳定点。
在当前主链的稳定点之前,主链将永远不会改变(假设大多数见证人不发布非连续单元)。因此,以该主链定义的总序也是最终的。如果存在非连续单元,我们将决定哪一个是有效的,且是最终决定。如果新的非连续单元出现且与稳定主链上已有的任何单元发生冲突,则新的非连续单元肯定会在排在原来单元之后,而被视为无效。因而,在稳定主链中包含的单元中的任何付款都已经不可逆转。与比特币不同,交易的确认只是概率性的,Byteball交易具有最终确认性。
每个用户根据他看到的单元构建自己的当前主链。由于新单元的传播不是即时的,并且它们可能以不同的顺序到达不同的用户,因此用户可能会在任何给定时间具有不同的当前主链和最后稳定点。然而,由于当前主链仅由用户已知的单元集合定义,如果用户B尚未将其稳定点提升到与用户A相同的MCI,则当他收到与用户A相同的单元时,它也将稳定点提升到相同MCI。因此,不同用户关于任何给定单元的状态的意见最终是一致的。
8. 非连续单元的存储(Storage of nonserial units)
当我们确定一个单位是非连续时,我们仍然需要存储它。但是,其部分数据将替换为数据的哈希值。此规则有两个目的:首先,减少单元所消耗的存储量(非序列单位的全部内容被视为无效,包括手续费);其次,降低非连续单元对发送者的作用,因为哈希替换了发送者想要存储的所有数据(免费),这可以防止攻击者滥用非法行为作为免费存储大量数据的方法。
存储哈希而不是完整内容仍然对攻击者有一些实用性,因为他可以自己存储原始数据并使用哈希来证明数据存在。但请记住:
-
他仍然需要为另一个被认为有效的单元支付费用;
-
如果攻击者已经在内部存储了解释哈希所必需的元数据,他可以通过将所有数据组合到Merkle树中并使用Byteball存储其Merkle根来减小存储成本。
根据这种设计,攻击者尝试发送非连续单元时无利可图。应该注意的是,我们不能在第一次看到非连续单元时就拒绝它。如果我们这样做了,攻击者可以将他的非连续单元以不同的顺序发送给不同的用户。然后,不同的用户会坚持他们最初收到的版本,并拒绝其他版本的所有内容,这样攻击者将成功分割网络。这就是为什么我们必须存储两个版本然后决定哪个单元是有效的。此外,用户应该像其他有效单元一样将非连续单元转发给其它网络节点,因为其它节点越早了解非连续单元就越好。
如果可能的话,我们仍然尽量避免包含非连续单元:只要它没有子单元,父单元选择算法会将它排除。因此,我们需要将非连续单元尽快扩散到网络中。
9. 雪球(Balls)
在一个单元变为稳定之后(即它被包含在主链的稳定部分中),我们将基于这个单元创建一个新结构,我们将其称为雪球(ball):
ball: { unit: "hash of unit", parent_balls: [array of hashes of balls based on parent units], is_nonserial: true, // this field included only if the unit is nonserial skiplist_balls: [array of earlier balls used to build skiplist]}
每个雪球都包含有关其所有祖先单元雪球的信息(通过父单元),因此它所依赖的信息量就像雪球一样增长。我们还在雪球中有一个标志告诉我们它是否最终无效(非连续)。雪球中还包含了跳跃列表,它将用来为轻客户端构建证据链。
我们只能在相应的单元变得稳定时才建立雪球,并且我们将确定它是否是连续的。由于不同用户的当前主链最终是一致的,因此它们将基于相同的单元构建完全相同的雪球。
10. 最近雪球(Last ball)
为了保护雪球(最重要的是is_nonserial标志)不被修改,我们要求每个新单元包含作者已知的最后一个雪球的哈希(这是从最后一个稳定单位建造的雪球,它位于主链上)。这样,最近雪球将受到作者签名的保护。之后,见证人将(直接或间接)包含新单元。
如果轻客户端(没有整个Byteball数据库)想知道某个特定单位是否是连续的,他会给定信任的见证人名单,用于建立一条包含大部分见证人的主链。从该主链中最早出现的单元中选择最近雪球,并以此构建哈希树。哈希树以最近雪球为树根,并在某个分支上包含客户端想要查询的单元。这个哈希树类似于非常高的Merkle树,但在每个节点都有额外的数据输入。此外,还可以使用跳跃列表来优化树的结构。
对最近雪球的引用也让用户可以看到网络中其它节点的稳定点位置,并将其与自己的进行比较。
我们还要求最近雪球的位置不早于其父单元最近雪球的位置。这可确保最近雪球沿着主链向前前进或保持在相同位置,但从不后退。
为了进一步降低攻击者的自由度,我们还增加了一个要求:单元的见证人列表必须与从该单元到最近雪球的单元之间的所有单元的见证人列表兼容。此要求确保在尝试再次更改见证人列表之前,对见证人列表的已有更改都要先达到稳定点。否则,攻击者可能会将恶意的见证人列表注入主链,并停止从新见证人的地址发布交易。这将导致稳定点停留在攻击者的见证人所占据的范围而无法前进。
同一段时间内发出的交易单元的见证人名单大多相似,这意味着在这段时间内所有用户对于当前可以信任谁作为见证人的看法大多相似。这类似于生物学,其中相同物种的生物必须具有大部分相同的基因。见证人列表的不可突变性允许小量的进化改变,同时保持系统的完整性。
11. 见证人列表单元(Witness list unit)
可以预见的是,大多数用户将希望使用完全相同的见证人列表。在这种情况下,为了节省空间,他们没有必要列出所有12个见证人的地址。相反,他们可以提供另一个早期单元哈希作为参考,使用那个单位里面明确列出的见证人。从引用单元的角度来看,见证人列表单元必须是稳定的,比如它被包含在最近雪球的单元中。
12. 单元结构(Unit structure)
这是一个单元的例子:
{ version: '1.0', alt: '1', messages: [ { app: 'payment', payload_location: 'inline', payload_hash: 'AegecfpDzh8xvdyIABdynrcP6CTd4Pt42gvRiv0Ftjg=', payload: { inputs: [{ unit: '7yctnKyuAk5P+mFgFQDdDLza88nkceXYjsTs4e3doQA=', message_index: 0, output_index: 1 } ], outputs: [ { address: 'DJ6LV5GPCLMGRW7ZB55IVGJRPDJPOQU6', amount: 208 }, { address: 'Z36JFFX2AH7X5JQ2V2C6AQUUOWFESKZ2', amount: 3505 } ] } } ], authors: [ { address: 'DJ6LV5GPCLMGRW7ZB55IVGJRPDJPOQU6', authentifiers: { r: '3eQPIFiPVLRwBwEzxUR5thqn+zlFfLXUrzAmgemAqOk35UvDpa4h79Fd6TbPbGfb8VMiJzqdNGHCKyAjl786mw==' } } ], parent_units: [ 'B63mnJ4yNNAE+6J+L6AhQ3EY7EO1Lj7QmAM9PS8X0pg=', 'D6O1/D9L8vCMhv+8f70JecF93UoLKDp3e2+b92Yh2mI=', 'ZxqzWP6q6hDNF50Wax8HUK212lH/KSIRdW5a6T9h3DM=' ], last_ball: '8S2ya9lULt5abF1Z4lIJ4x5zYY9MtEALCl+jPDLsnsw=', last_ball_unit: 'bhdxFqVUut6V3N2D6Tyt+/YD6X0W+QnC95dMcJJWdtw=', witness_list_unit: 'f252ZI2MN3xu8wFJ+LktVDGsay2Udzi/AUauE9ZaifY='}
这里:
-
version是协议版本号。该单元将根据该版本的协议进行解释;
-
alt是货币的标识符,我们稍后会讨论;
-
messages是包含数据的一个或多个消息的数组;
-
address是收款人的Byteball地址;
-
amount是他收到的金额;
-
unit是生成输入的单元哈希值。单元必须包含在last_ball_unit中才能花费;
-
message_index是输入单元的消息数组的索引。它表示货币生成的信息;
-
output_index是输入单元的第message_index条消息的outputs数组的索引。它表示货币生产的输出;
-
app是消息的类型,例如用于付款的payment,用于发送文本的text等;
-
payload_location表示在何处查找消息有效载荷。如果有效载荷包含在消息中,则可以是inline;如果有效载荷在互联网地址可用,则为uri;如果有效载荷尚未发布,则为none;有效载荷可以私有存储或公开共享,payload_hash用于证明它在特定时间存在;
-
payload_hash是base64编码的有效载荷哈希值;
-
payload是实际的有效载荷(在这个例子中它是inline)。该有效载荷结构是根据不同的消息类型有所不同。payment消息的描述如下:
-
inputs是付款消耗的输入数组。输入的所有者必须是该单元的签名者(作者);
-
outputs是一系列输出地址,表明谁接收付款;
-
authors是创建和签署此单元的作者数组。所有输入inputs必须属于authors;
-
author是作者的Byteball地址;
-
authentifiers是一个证明作者真实性的数据结构,最常见的是ECDSA签名;
-
parent_units是父单元的哈希数组。它必须按字母顺序排序;
-
last_ball和last_ball_unit分别是最近雪球及其单元的哈希值;
-
witness_list_unit是引用的见证人列表的单元哈希值。
所有哈希值都采用base64编码。
请注意,单元结构中没有时间戳字段。在Byteball中,没有任何依赖于时钟时间的协议规则。它根本不需要时间戳,因为仅仅依靠事件的顺序就足够了。
当交易单元从节点转发到节点时,时间戳仍会添加到单元中。然而,这仅仅是辅助性标识,可以用于轻客户端在钱包中显示单元生产的大致时间。这个时间可以与接收时间明显不同,因为轻客户端可能长时间离线。
13. 手续费(Commissions)
如前所述,存储单元的成本是以字节为单位的计算的。手续费分为两部分:单元头手续费和见证人手续费。见证人手续费等于消息的大小;单元头手续费等于除此之外其他部分的大小。这两种手续费的分配方式不同。
单元头手续费将转给以该单元作为父单元的单元。当该单元所在的MCI和它下一个MCI达到稳定之后,才选择子单元进行分发。为了确定接收单元,我们选择MCI等于或者比该单元MCI多1的子单元。这些子单元的哈希值分别与主链下一个MCI单元的哈希值合并,并再次进行哈希后,选择其中具有最小哈希值(十六进制)的子单元赢得单元头手续费。与下一个MCI单元的哈希值合并的设计是为了引入不可预测性(因为事先不知道下一个MCI单元),仅仅通过减小自己的单元哈希值来提高获得手续费的概率是无用的。同时,将候选人限制在那些MCI不超过单元自身MCI的子单元的情况,是为了鼓励选择最近的单元作为父单元。这对于尽可能缩紧DAG非常有用。
我们只向那些快速选择新单元作为父单元的人支付单元头手续费,而不是全部手续费,是出于以下原因考虑。如果我们支付了整个手续费,就会激励一些滥用行为:将一个人的数据拆分成几个单元并构建一个自己的单元长链,每个单元存储一部分数据,这样之前单元支付的所有手续费将由下一个单元的同一用户获得。但由于我们只支付单元头手续费,这种行为是无利可图的,因为要产生长链的每个单元,都必须花费额外的单元头手续费,这大致和他的收益相同。我们使用剩余的(见证人)手续费来激励其他人,他们的活动对于保持网络健康非常重要。
见证人手续费将发送给见证人。为了激励见证人足够频繁地发出交易,我们在所有见见证人之间平均分配见证人手续费,但要求这些见证人是在该单元之后的100个MCI之内发出了交易(发出得越快,该单元变得越稳定)。如果所有12名见证人都在此时间间隔内发出交易,则每人都会收到见证人手续费的1/12。如果只有一个见证人发出交易,他将收到整个见证人手续费。在这个时间间隔内没有见证人发送交易的特殊情况下,所有见证人都将收到1/12的见证人手续费。如果除法产生小数,则根据数学规则对其进行四舍五入。由于这种四舍五入,向见证人支付的总手续费可能不等于从该单元的作者收到的见证人手续费,这会导致总货币供应量略有变化。显然,只有在MCI+100的单元变得稳定之后才会发生分配,其中MCI是用于支付手续费的单元的MCI。
要花费所获得的手续费,请使用以下输入:
inputs: [ { type: "headers_commission", from_main_chain_index: 123, to_main_chain_index: 196 }, { type: "witnessing", from_main_chain_index: 60, to_main_chain_index: 142 }, ... ]
此类输入会扫描作者从主链索引from_main_chain_index到to_main_chain_index之间发出的单元中获得的所有单元头手续费和见证人手续费。当然,to_main_chain_index必须达到稳定。
当一个由多位作者签名的单元获得单元头手续费时,如何在这些作者之间分配是模糊不清的。要消除歧义,由多位作者签名的单元必须包含描述收益分享比例的数据结构:
unit: { ... earned_headers_commission_recipients: [ {address: "ADDRESS1", earned_headers_commission_share: 30}, {address: "ADDRESS2", earned_headers_commission_share: 70} ], ... }
接收手续费的地址不必和作者的地址相同。手续费可以发送到任何地址。即使该单元仅由单个作者签名,也可以包含此字段从而将其中的手续费发送给其它地址。
14. 确认时间(Confirmation time)
确认时间指的是单元从进入数据库开始到达稳定所经历的时间。这取决于见证人发布交易的频率,因为为了达到稳定,我们需要在新添加的单元之后在主链上积累足够的见证人发布的单元。为了最大限度地缩短确认时间,见证人应该经常性发布交易单元(已经通过手续费分配规则激励他们),但不要太频繁。如果两个或多个见证人几乎同时发布他们的单元(比将新单元传播给其他见证人的时间更快),这可能导致由最佳父连接组成的树产生不必要的分支,从而延迟稳定点的推进。因此,当见证人与网络连接良好且运行的机器足够快验证新单元时,可以获得最佳确认时间。我们估计最佳确认时间约为30秒;只有当新产生单元的流量足够大,使得见证人从手续费中获得的收入高于他们用于发布自己单元的费用时,才能达到这一点。
尽管最终确认的时间相当长,如果网络中其它节点在没有过滤交易的情况下转发所有的新单元,那么只要一个单元被至少一个见证人包含在内,再加上典型的等待时间已经过去(一个交易单元在节点间的转发时延),该单位将很可能被确认并视为有效。即使后来出现双花支出,那也很可能排序在此单元之后。
15. 分叉风险(Partitioning risk)
Byteball节点的网络永远不能被分成两个相互独立正常运行的部分,即这两部分会在没有被注意的情况下正常运行。即使在全球网络中断的情况下,例如切断连接欧洲和美国的电缆,那么至少有一方面会注意到它已经失去了大多数证人。这意味着它无法向前推进稳定点,也没有人可以花费主链不稳定部分的那些输出。即使有人试图进行双花,它仍将保持不确定状态(因此无法识别),直到连接恢复为止。具有大多数见证人的另一部分将继续照常进行。
16. 审查机制(Censorship)
按照我们的设计,Byteball中任何已经存在的记录无法修改或删除。但同时,阻止任何特定类型的数据进入数据库也是不可行的。
首先,数据本身可以隐藏,而只将其哈希值发布到数据库以证明数据存在。只有在哈希值所在单元已被其他单元包含,并使得其变得不可修改之后,数据才可以显示。
其次,即使数据是公开的,包含或不包含在数据库中的决定也被委托给许多匿名用户,他们可能(实际上是被激励)将新单元作为父母。试图审查单元的人不仅要避免直接包括他们(作为父单元),还要防止通过其它单元间接地包含他们。(这与比特币不同,矿工或矿池可以直接过滤单个交易。此外,比特币用户对于谁将成为矿工没有发言权。)因为包含“违规”单元的单元数量像滚雪球一样,任何避免它的企图都值得三思。只有大多数见证人才能有效地施加禁止的内容规则,前提是用户选择此类见证人。
17. 见证人选择机制(Choosing witnesses)
依靠见证人是使Byteball根植于现实世界的原因。与此同时,它将高度依赖人们自己的决策。整个系统的健康状况取决于用户是否负责地设置他们信任的见证人名单。此过程无法安全地自动实现,例如,如果大多数用户根据网络最近出现的交易单元自动更新见证人列表,攻击者能够轻易地实施洪泛攻击,基于兼容性原则,他们可以用自己的单元充满网络并逐渐将见证人名单修改为攻击者所选择的名单。
虽然最安全的方法是“仅能手动编辑见证人列表”,但这对大多数用户来说太麻烦。更实际的见证人列表管理的方法是跟踪某些特定用户的见证人列表,比如一些维护网络健康的行业领袖或在各种活动中赢得良好声誉的人,他们中的一些人可能本身就是见证人。与见证人列表不同,行业领袖的见证人列表不一定要求兼容,不频繁地更新列表并不会产生任何直接的负面影响,例如无法找到兼容的父单元用来发布新单元。我们预计大多数用户将使用最流行的钱包(数量相对较少),并且默认情况下将按照钱包提供商的见证人列表来设置,当然钱包提供商也可以跟踪其他用户的见证人列表。
见证人也有见证人列表。建议用户选出他们信任的见证人,以保证他们的见证人列表代表了普通用户的信任。这一点非常重要,因为未经大多数现有见证人的批准,对见证人名单的更改无法通过。见证人和潜在见证人需要公开宣布他们的见证人列表更新策略(例如跟踪其他信誉良好的用户的见证人名单),并且用户根据此策略以及其他因素来评估他们是否能够作为见证人。任何违反该策略的行为都将立即被发现,并可能被用户替换,对策略不合理的修改也会导致同样的结果。该策略促使他能够遵循公众舆论,即使他反对其他见证人或他的朋友。
如前所述,我们的协议规则要求:
-
最优父单元的见证人列表突变个数不能超过1个;
-
与最近雪球的交易单元的见证人列表相比,突变个数不能超过1个;
-
与沿着未稳定的主链到最近雪球的交易单元路径上的所有单元的见证人列表相比,突变个数不能超过1个;
-
稳定点仅在其见证人发布足够的交易单元后才能向前推进。
这些规则旨在防范恶意和意外的分叉。同时,它们表示见证人列表的任何变化都必须是渐进的,每一次更改都必须得到大多数现有见证人的批准。在进行另一次更改之前,前一次的更改必须首先达到稳定,并且被大多数老见证人收到。如果社区突然决定需要立即更换两名见证人,那么在一次更改开始之后,第二次更改将被上述规则3阻止,直到第一次更改达到稳定。
尽管有上述所有建议,但仍然存在这种可能性,这些见证人联合起来组成联盟,并集体阻止所有替换其中任何一个见证人的企图,从而保持他们的手续费收入。如果他们真的这样做的话,那么每个人都会发现这种情况,因为他们的见证人名单将始终保持不变,而大多数其他社区领袖的见证人名单将有一个不同(最大允许保持兼容)。如果那些老的见证人面临这样明显的压力仍然没有屈服,那么改变现状的唯一办法就是“革命”,即开启一个新的分支并继承所有余额、用户地址等。新的分支将从一个新的见证人列表开始,并添加一个特殊的协议规则来处理分裂时的这种不兼容的变化。为了与旧分支区分开,他们会为alt字段分配一个新值(这就是alt的用途),并且在新分支下发布的所有单元中使用它。结果,用户将持有两种货币(旧的alt =1,以及新的例如alt =2),并且将能够独立地花费这两种货币。如果新分支是合理的,那么旧分支可能会被放弃,但在分裂之前积累的所有数据将在新分支中正常可用。由于协议几乎相同(除了处理分裂和alt的更改的规则),因此在所有用户和商家设备上的安装更新软件将很容易。
如果有人只是想开启一个新分支来试验另一组协议规则,他也可以使用alt字段继承旧分支的所有内容,包括原有的用户地址和他们的余额。
18. 跳跃列表(Skiplist)
有些雪球包含一个跳跃列表,它可以用来更快地为轻客户端构建证据链(见下文)。只有那些直接位于主链上并且其主链序号能被10整除的雪球才有跳跃列表。跳跃列表列出了具有相似主链序号的雪球,即主链序号在末尾具有相同或更少的零数。例如,主链序号为190的雪球的跳跃列表包含主链序号为180的雪球,主链序号为3000的雪球的跳跃列表包含主链序号为2990、2900和2000的雪球。
19. 轻客户端(Light clients)
轻客户端不存储整个Byteball数据库。相反,他们仅下载他们感兴趣的数据子集,例如与他们的钱包地址相关的那些交易数据。
轻客户端需要连接到全节点以下载他们感兴趣的交易单元。轻客户端告诉全节点它所信任的见证人列表(不一定是它用于创建新单元的见证人)以及它自己的地址列表。全节点搜索轻客户端感兴趣的单元,并按以下方式为每个单元构建证据链:
-
沿着主链进行回溯,直到碰见大多数要求的见证人,收集所有这些主链上的交易单元。
-
选择这些交易单元中最早的一个单元,读取它的最近雪球。
-
从这个雪球开始,沿着主链继续回溯,直到遇到任何具有跳跃列表的雪球,收集所有这些雪球。
-
使用跳跃列表,跳转到跳跃列表中引用的雪球。这个雪球也有一个跳跃列表,再次进行跳转。当跳跃列表中有多个雪球时,选择跳跃最大距离的雪球。因此,我们将不断加速跳跃,开始加10,然后加100,然后加1000,等等。
-
如果跳跃列表的下一次跳跃到达目标单元之前,则开始跳跃一个较小的距离来减速。最后,不再使用跳跃列表,而是使用父连接进行回溯。
这个证据链在一开始就有足够多见证人发布的单元,从轻客户端的角度来看它是值得信赖的。证据链中的所有单元要么是通过父单元连接(在开始阶段累积见证人时),要么是通过最近雪球引用,要么是通过父雪球连接,要么是通过跳跃列表连接。在证据链的最后是目标单元,它的存在性将被证明。
20. 多签名(Multilateral signing)
一个单元可以由多方签名。在这种情况下,单元中的作者数组将具有两个或更多元素。
如果两方或多方想要签订合同(一个简单的普通合同,而不是智能合约),他们可以共同签署一个包含文本消息的单元(app =text)。他们不必将合同全文都存储在公共数据库中,而是存储合同文本的哈希值就足够了(payload_location =none),具体的文本内容由他们线下保存。
多签名的另一个应用是资产交换。假设用户A想要将资产X发送给用户B以换取资产Y(基础货币bytes也是资产)。然后他们将组成一个包含两个支付消息的单元:一个支付消息将资产X从A发送到B,另一个支付消息将资产Y从B发送到A。他们共同签署这个单元并发布。资产交换具有原子性,也就是说,两种付款要么同时执行要么两者都失败。如果其中一笔付款是双花支出,则整个单元将被视为无效,另一笔付款也被视为无效。
这种简单的结构允许用户直接交换资产,而无需信任任何第三方中心机构。