1. 字节雪球爱好者社区首页
  2. 字节雪球(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),存储单位作为图的顶点,父子单元之间的连接是图的边。

Byteball-whitepaper-fig1

图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的流通总量为$10^{15}$,这个总量是不变的。所有bytes都在创世单元中发出,然后通过用户发送给用户。其他用户收集手续费以帮助保持网络正常运转(稍后会详细介绍),所有bytes可以循环使用。选择数量$10^{15}$作为总量的原因是,这是可以用JavaScript表示的最大正整数。bytes的金额只能是整数。通过使用不同的前缀可以得出较大的货币单位:1千字节(KB)是1000字节(bytes),1兆字节(MB)是100万字节(bytes)等。

4. 双花交易(Double-spends)

如果用户尝试使用相同的输出进行花费,则有两种可能的情况:

  1. 试图花费相同输出的两个单元之间存在偏序,即其中一个单元(直接或间接)包含另一个单元。在这种情况下,很明显我们可以安全地拒绝后一个单元。
  2. 两个单元之间没有偏序。在这种情况下,我们先同时接受两者。随后当我们得到足够多的新单元时,我们将根据之后的单元建立一个总序(具体做法见下文)。总序中较早出现的单元视为有效,而另一单元视为无效。

有一个协议规则可以简化总序的计算。我们要求,如果同一地址发布一个以上的单元,它应该(直接或间接)包含该地址的所有之前的单元,即在同一地址的单元之间应该有偏序。换句话说,同一作者的所有单元都应该是连续的。

如果用户违反此规则并发布两个单元,使得它们之间没有偏序(即非连续单元),则即使它们没有使用相同的输出,这两个单元也会被视为双花交易,并按照上面第2种情况进行处理。

Byteball-whitepaper-fig2

图2:双花交易。两个交易单元之间没有偏序。

如果用户遵循此规则但两次使用了相同的输出,则双花交易单元之间具有偏序,我们可以安全地拒绝后面的交易单元,如上面的情况1所示。因此,那些非连续的双花交易很容易识别出来。

事实上,这条规则很自然的。当用户构造新单元时,他选择最近的其它单元作为其父单元。通过把它们作为父单元,这意味着在他的视角下已经看到了这些单元。因此,他也见过这些单元的父单元,父单元的父单元等,直到创世单元。这个巨大的集合中显然应该包括他自己已经发出的所有单元。

对于那些没有直接或者间接包含的单元,用户实际上是在否认他已经看到过它。那么,当用户构造交易单元时,如果不包含他自己的单元,这意味着用户在否认他自己的交易单元,这看起来很奇怪,有些可疑的事情正在发生。我们不鼓励这种行为。

5. 主链(The main chain)

我们的DAG是一个特殊的DAG。在正常使用时,用户通常会将他们的新单元连接到最近出现的单位上,这意味着DAG只在一个方向上增长。可以将其想象成一条粗线,内部有许多交错线。这种属性表明我们可以在DAG中找到一条单链,然后将所有单元与此单链相关联。这条单链称为主链,所有单元要么直接位于主链上,或者可以通过相对较少的跳数来到达主链。主链就像一条连接多条侧路的高速公路。

构建主链的方式是设计一种算法,在给定单元的所有父单元的情况下,用于选择其中一个作为“最佳父单元”。选择算法应仅基于给定单元的可用知识,即单元本身及其所有父单元所包含的数据。从DAG的任何顶端单元(即没有子单元的单元)开始,我们沿着“最佳父单元”的连接向后回溯。以这种方式回溯,我们可以建立一条主链,并最终到达创世单元。请注意,从特定单元开始构建的主链在添加新单元时不会受到影响。这是因为每一步我们都是从子单元到父单元,而已有的单元的父单元是确定的。

如果我们从另一个顶端单元开始,我们将构建另一个主链。值得注意的是,如果这两条主链在向后回溯时相互交叉,它们将在交叉点之后沿着相同的路径行进。在最坏的情况下,主链只会在创世单元相交。然而,鉴于用户之间没有协商单元产生的过程,人们可能会发现主链通常在距离顶端单元不太远的地方相交。

Byteball-whitepaper-fig3

图3:从不同顶端单元构建的主链相交并沿着相同的路径前进。在两个双花交易中,具有较小主链序号(5)的单元获胜,而另一个主链序号(6)的单元视为无效。

一旦我们确定一条主链(MC, Main Chain),我们就可以在两个相互冲突的非连续单元之间建立一个总序。让我们首先对主链上的单元进行排序。创世单元的序号为0,下一个位于主链的单元的序号为1,依此类推,沿着主链向前推进,我们将序号分配给位于主链上的单元。对于不在主链上的单元,我们可以找到首次包含该单元的主链序号(直接或间接)。通过这种方式,我们可以为每个单元分配主链序号(MCI, Main Chain Index)。

然后,双花交易中具有较小主链序号的单元被认为较早出现并且是有效的,而另一个是无效的。如果两个非连续单元碰巧具有相同的MCI,则具有较小哈希值的单元(如base64编码中所示)是有效的。请注意,我们会保留所有版本的双花交易,包括那些最终会丢弃的版本。DagCoin [3]是第一个提出:建议存储所有冲突的交易,并最终决定将哪一个视为有效。

从特定单元出发构建的主链代表了这个单元的作者对过去事件顺序的看法,即他对历史事件的看法。然后,该顺序用于决定哪个非连续单元被认为是有效的。请注意,通过在给定单元的所有父单元中选择“最佳父单元”,我们同时在他们对应的主链中做出了选择:当前给定单元的主链将是向前扩展“最佳父单元”的主链。

考虑到可能存在有(甚至所有)父单元是由攻击者创建的,同时记住“最佳父单元”的选择本质上是历史事件版本的选择,我们应该在“最佳父单元”选择算法中要求它倾向于选择从当前单元角度来看更接近真实的历史事件版本。因此,我们需要设计一个“真实性测试”算法,从而在所有候选主链中选择得分最高的主链。

6. 见证人(Witnesses)

已知网络中的一些用户是实名的信誉良好的人、或者具有长期声誉的公司、或者他们是对保持网络正常运转感兴趣的企业。在“真实性测试”的算法中,他们被称为见证人。虽然假定他们诚实工作是合理的,但完全信任任何一个见证人却是不合理的。如果我们知道见证人的地址,并且他们保持足够频繁地发送交易,那么我们可以通过计算见证人发送的单元数量来衡量候选主链的真实性(相同见证人发送的单元只计算一次)。在沿着候选主链回溯时,一旦遇到大多数见证人发送的单元,就停止回溯。然后,我们将计算图中从停止位置到创世单元的最长路径长度。并将这个长度称为停止位置的单元级别,以及具有当前候选主链的父单元的见证级别。具有更高见证级别的候选主链被认为更接近“真实”,并且具有该主链的父单元被选为“最佳父单元”。如果有存在多个父单元具有相同的见证级别,我们将选择单元级别最低的父单元。如果仍然存在多个父单元,我们将选择具有最小哈希值的父单元(使用base64编码)。

该算法通过见证人发送的单元来选择主链,见证人被认为是现实的代表。如果攻击者从网络的真实部分分叉,同时秘密地建立一个他自己发送的单元构成的长链(影子链),其中包含双花交易,然后将他的分叉合并回真实的DAG。那么,“最佳父单元”选择算法将在合并位置上从真实的DAG中选择主链,因为那里有更多见证人发送的单元。因为在合并之前,见证人无法在影子链上发送交易单元。主链的这种选择反映了见证人和选择他们的用户所看到的事件的顺序。攻击结束后,整个影子链将在同一个点落在MC上,影子链中包含的双花交易将被视为无效,因为其有效交易在合并点之前具有更小的主链序号。

Byteball-whitepaper-fig4

图4:当攻击者将影子链接入真实DAG中,影子链中的单元将不具备最佳父单元的竞争力,因为其它路径上有更多见证人发送的单元(用w标记)

这个例子说明了为什么大多数证人必须被信任且只能发送连续交易单元。大多数见证人不应该与攻击者勾结并在他的影子链上发送交易。请注意,我们只是相信见证人作为真实性的标志,且不会在任何影子链上发布非连续单元。见证人不具有控制网络及其任何部分的权力。即使见证人只是具有发布真实交易的小职责,用户还保留了指定见证人的权利,他们可以随时改变他们的决定。

将一些已知实体视为真实性标志的想法并不新鲜。人们早就知道,并且一些公司已经参与了这样的活动。为了证明某些数据在特定日期之前存在,人们可以对数据进行哈希并在一些难以修改和广泛传播的媒体中发布哈希值,如印刷报纸[6]。Byteball中的见证人具有与报纸相同的功能。像报纸一样,它们是众所周知和值得信赖的。与报纸中我们仅信任他们如实地发布他们知道的数据一样,我们只是信任Byteball中的见证人发布连续交易单元,而没有其它更多的了。像报纸一样,见证人并不知道哈希值背后是什么,也没有理由关心。报纸难以修改(但可能,并且在《1984》中他们就这样做了),而见证人发布的所有单元都受到数字签名的保护,这使得任何修改都变得不可能。为了可靠性,我们有多个见证人,而不仅仅是一个;为了速度和方便,必须要求他们是保持在线的。

在确定了见证人名单之后,我们可以选择最符合真实性的父单元和相应的主链,即拥有更多见证人的路径。同时,父单元本身可能有不同的见证人名单,因此对真实性的定义也不同。我们希望真实性的定义和相应的主链能够围绕一些共同点展开。为此,我们引入了以下附加协议规则。

兼容性规则:最佳父单元只能在见证人名单与当前单元见证人名单不超过一个突变的父单元中选择。该规则确保主链上相邻单元的见证人列表足够相似,因此它们的历史大多彼此一致。见证人列表不超过一个突变的父单元将被称为兼容(与直接包含他们的单元),而其他父单元则不兼容。仍然允许不兼容的父单元,但他们没有机会成为最佳父单元。如果没有没有兼容的父单元(攻击者可能会通过洪泛攻击使得网络中出现一个完全不同的见证人名单),那么应该从更早的能够兼容的单元中选择父单元。

以上规则意味着每个单元必须列出其见证人列表才能进行比较。我们要求证人的数量正好为12,选择数字12是因为:

  • 它足够大,可以防止少数见证人的偶然掉线(他们可能是不诚实、被黑、或长时间离线、或丢失私钥并永远离线);
  • 它足够小,人们可以跟踪所有见证人,知道谁是谁,并在必要时更改列表;
  • 与11名未改变的见证人相比,允许一个突变足够小。

如果用户认为任何见证人失去了他的可信度,或者有更好的候选人,用户可以用在他的名单中的用新见证人替换原来的见证人,同时保证他的见证人列表与其它单元的突变数量小于1个。这也意味着任何变化只能逐步发生,并且对于多于一个的见证人变更需要达成普遍共识。

7. 最终确认性(Finality)

当新交易单元到达时,每个用户都会尝试构建他的当前主链,在构建当前主链时假设他将根据当前所有顶端单元发布新单元。当前主链在不同网络节点处可能不同,因为它们可能看到不同的顶端单元集合。在建立当前主链时暂时不考虑见证人列表,即忽略用户自己的见证人列表,此时可以选择不兼容的父单元作为最佳父单元。这意味着如果两个用户拥有相同的顶端单元集合,但具有不同的见证人列表,则他们的当前主链仍然是相同的。当新交易单元到达时,当前主链将不断变化。但是,正如我们即将展示的那样,当前主链的一部分将稳定下来并保持不变。

我们希望见证人(或者更确切地说是其中的大多数见证人)诚实地工作,即同一见证人发送的交易单元必须包含他们以前所有的交易单元。这意味着当证人构建一个新单元时,只有最近的单元才能被选为父单元。因此,我们期望所有未来可能出现的主链将会在特定的稳定点之前收敛(当沿着主链回溯时)。实际上,创世单元是天然的初始稳定点。假设我们已根据当前的顶端单元组构建了一个当前主链,并且此主链上的某些点以前被认为是稳定的,即所有未来的主链都被认为在此点之前收敛,然后将沿着同一条路径前进。如果我们能够找到向前推进稳定点的方法(朝着远离创世单元的方向),我们就可以通过归纳证明存在稳定点。

请注意,当我们只保留交易单元与最佳父单元之间的连接时,DAG将退化为仅包含最佳父连接的树。显然,所有的主链都将沿着这棵树的分支前进。然后我们需要考虑两种情况:在当前稳定点发生分支以及不发生分支,并决定我们是否可以将稳定点提升到下一个MCI上。

Byteball-whitepaper-fig5

图5:由最优父连接构成的树。在某个位置之后,只有一个分支继续生长。

首先,假设在当前稳定点不发生分支。我们需要考虑的是当前稳定点是否仍然可能添加新分支,并被见证人支持,使其超出现有分支。另一种可能性是,见证人已经在当前分支上发送了大量交易单元,由于存在需要包含之前所有的单元的限制条件,这使他们没有选择,只能继续支持现有的分支。我们需要量化后一种可能性。请记住,最佳父单元是具有最大见证级别的父单元。当从顶端单元沿着当前主链回溯,直到遇到大多数见证人的单元时停止(指的是由当前稳定点上的单元定义的那些见证人)。如果这些见证人的单元中至少有一个位于当前稳定点之前,我们不会试图提升稳定点,否则我们继续检查其它条件。在这种情况下,所有这些见证人已经开始支持当前主链。在这些见证人单元中,我们获取它们的最低见证级别min_wl。当这些见证人中的任何一个发布新单元时,该单元的父单元中有些是在当前分支上,有些是在其它竞争分支上,具有最高见证级别的父单元将被选为最佳父单元从而定义出当前主链的方向。由于见证人必须包括其先前的单元,因此通向当前分支的父单元的见证级别将至少为min_wl。任何通过竞争分支的父单元的见证级别将永远不会超过当前稳定点的单元级别,即使所有剩余的(少数)见证人都支持该竞争分支。因此,如果当前主链已经增长得足够长使得min_wl大于当前稳定点的单元级别,则大多数见证人将不得不继续增加对现有主链的支持,竞争分支也就失去了获胜的所有机会,因此我们可以将稳定点向前移动到下一个MCI。

接下来,假设当前稳定点发生分支。我们需要找到一个条件,保证竞争分支失去任何超过当前主链的机会。让我们从前一种情况中定义的min_wl开始。在竞争分支的所有单元中,我们选择那些使得见证级别增加的单元,即他们自己见证级别高于每个父单元的见证级别。选择这些单元中最高的单元级别。那么,即使所有其余(少数)见证人都聚集在竞争分支上,竞争分支上单元的见证级别也不会超过此最高单元级别。因此,如果此最高单元级别小于min_wl,则竞争分支的失去获胜机会,那么我们可以沿当前主链推进稳定点。

在当前主链的稳定点之前,主链将永远不会改变(假设大多数见证人不发布非连续单元)。因此,以该主链定义的总序也是最终的。如果存在非连续单元,我们将决定哪一个是有效的,且是最终决定。如果新的非连续单元出现且与稳定主链上已有的任何单元发生冲突,则新的非连续单元肯定会在排在原来单元之后,而被视为无效。因而,在稳定主链中包含的单元中的任何付款都已经不可逆转。与比特币不同,交易的确认只是概率性的,Byteball交易具有最终确认性。

每个用户根据他看到的单元构建自己的当前主链。由于新单元的传播不是即时的,并且它们可能以不同的顺序到达不同的用户,因此用户可能会在任何给定时间具有不同的当前主链和最后稳定点。然而,由于当前主链仅由用户已知的单元集合定义,如果用户B尚未将其稳定点提升到与用户A相同的MCI,则当他收到与用户A相同的单元时,它也将稳定点提升到相同MCI。因此,不同用户关于任何给定单元的状态的意见最终是一致的。

8. 非连续单元的存储(Storage of nonserial units)

当我们确定一个单位是非连续时,我们仍然需要存储它。但是,其部分数据将替换为数据的哈希值。此规则有两个目的:首先,减少单元所消耗的存储量(非序列单位的全部内容被视为无效,包括手续费);其次,降低非连续单元对发送者的作用,因为哈希替换了发送者想要存储的所有数据(免费),这可以防止攻击者滥用非法行为作为免费存储大量数据的方法。

存储哈希而不是完整内容仍然对攻击者有一些实用性,因为他可以自己存储原始数据并使用哈希来证明数据存在。但请记住:

  1. 他仍然需要为另一个被认为有效的单元支付费用;
  2. 如果攻击者已经在内部存储了解释哈希所必需的元数据,他可以通过将所有数据组合到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是包含数据的一个或多个消息的数组;
    • app是消息的类型,例如用于付款的payment,用于发送文本的text等;
    • payload_location表示在何处查找消息有效载荷。如果有效载荷包含在消息中,则可以是inline;如果有效载荷在互联网地址可用,则为uri;如果有效载荷尚未发布,则为none;有效载荷可以私有存储或公开共享,payload_hash用于证明它在特定时间存在;
    • payload_hash是base64编码的有效载荷哈希值;
    • payload是实际的有效载荷(在这个例子中它是inline)。该有效载荷结构是根据不同的消息类型有所不同。payment消息的描述如下:
    • inputs是付款消耗的输入数组。输入的所有者必须是该单元的签名者(作者);
      • unit是生成输入的单元哈希值。单元必须包含在last_ball_unit中才能花费;
      • message_index是输入单元的消息数组的索引。它表示货币生成的信息;
      • output_index是输入单元的第message_index条消息的outputs数组的索引。它表示货币生产的输出;
    • outputs是一系列输出地址,表明谁接收付款;
      • address是收款人的Byteball地址;
      • amount是他收到的金额;
  • authors是创建和签署此单元的作者数组。所有输入inputs必须属于authors
    • author是作者的Byteball地址;
    • authentifiers是一个证明作者真实性的数据结构,最常见的是ECDSA签名;
  • parent_units是父单元的哈希数组。它必须按字母顺序排序;
  • last_balllast_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_indexto_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个;
  2. 与最近雪球的交易单元的见证人列表相比,突变个数不能超过1个;
  3. 与沿着未稳定的主链到最近雪球的交易单元路径上的所有单元的见证人列表相比,突变个数不能超过1个;
  4. 稳定点仅在其见证人发布足够的交易单元后才能向前推进。

这些规则旨在防范恶意和意外的分叉。同时,它们表示见证人列表的任何变化都必须是渐进的,每一次更改都必须得到大多数现有见证人的批准。在进行另一次更改之前,前一次的更改必须首先达到稳定,并且被大多数老见证人收到。如果社区突然决定需要立即更换两名见证人,那么在一次更改开始之后,第二次更改将被上述规则3阻止,直到第一次更改达到稳定。

尽管有上述所有建议,但仍然存在这种可能性,这些见证人联合起来组成联盟,并集体阻止所有替换其中任何一个见证人的企图,从而保持他们的手续费收入。如果他们真的这样做的话,那么每个人都会发现这种情况,因为他们的见证人名单将始终保持不变,而大多数其他社区领袖的见证人名单将有一个不同(最大允许保持兼容)。如果那些老的见证人面临这样明显的压力仍然没有屈服,那么改变现状的唯一办法就是“革命”,即开启一个新的分支并继承所有余额、用户地址等。新的分支将从一个新的见证人列表开始,并添加一个特殊的协议规则来处理分裂时的这种不兼容的变化。为了与旧分支区分开,他们会为alt字段分配一个新值(这就是alt的用途),并且在新分支下发布的所有单元中使用它。结果,用户将持有两种货币(旧的alt =1,以及新的例如alt =2),并且将能够独立地花费这两种货币。如果新分支是合理的,那么旧分支可能会被放弃,但在分裂之前积累的所有数据将在新分支中正常可用。由于协议几乎相同(除了处理分裂和alt的更改的规则),因此在所有用户和商家设备上的安装更新软件将很容易。

如果有人只是想开启一个新分支来试验另一组协议规则,他也可以使用alt字段继承旧分支的所有内容,包括原有的用户地址和他们的余额。

18. 跳跃列表(Skiplist)

有些雪球包含一个跳跃列表,它可以用来更快地为轻客户端构建证据链(见下文)。只有那些直接位于主链上并且其主链序号能被10整除的雪球才有跳跃列表。跳跃列表列出了具有相似主链序号的雪球,即主链序号在末尾具有相同或更少的零数。例如,主链序号为190的雪球的跳跃列表包含主链序号为180的雪球,主链序号为3000的雪球的跳跃列表包含主链序号为2990、2900和2000的雪球。

19. 轻客户端(Light clients)

轻客户端不存储整个Byteball数据库。相反,他们仅下载他们感兴趣的数据子集,例如与他们的钱包地址相关的那些交易数据。

轻客户端需要连接到全节点以下载他们感兴趣的交易单元。轻客户端告诉全节点它所信任的见证人列表(不一定是它用于创建新单元的见证人)以及它自己的地址列表。全节点搜索轻客户端感兴趣的单元,并按以下方式为每个单元构建证据链:

  1. 沿着主链进行回溯,直到碰见大多数要求的见证人,收集所有这些主链上的交易单元。
  2. 选择这些交易单元中最早的一个单元,读取它的最近雪球。
  3. 从这个雪球开始,沿着主链继续回溯,直到遇到任何具有跳跃列表的雪球,收集所有这些雪球。
  4. 使用跳跃列表,跳转到跳跃列表中引用的雪球。这个雪球也有一个跳跃列表,再次进行跳转。当跳跃列表中有多个雪球时,选择跳跃最大距离的雪球。因此,我们将不断加速跳跃,开始加10,然后加100,然后加1000,等等。
  5. 如果跳跃列表的下一次跳跃到达目标单元之前,则开始跳跃一个较小的距离来减速。最后,不再使用跳跃列表,而是使用父连接进行回溯。

这个证据链在一开始就有足够多见证人发布的单元,从轻客户端的角度来看它是值得信赖的。证据链中的所有单元要么是通过父单元连接(在开始阶段累积见证人时),要么是通过最近雪球引用,要么是通过父雪球连接,要么是通过跳跃列表连接。在证据链的最后是目标单元,它的存在性将被证明。

20. 多签名(Multilateral signing)

一个单元可以由多方签名。在这种情况下,单元中的作者数组将具有两个或更多元素。

如果两方或多方想要签订合同(一个简单的普通合同,而不是智能合约),他们可以共同签署一个包含文本消息的单元(app =text)。他们不必将合同全文都存储在公共数据库中,而是存储合同文本的哈希值就足够了(payload_location =none),具体的文本内容由他们线下保存。

多签名的另一个应用是资产交换。假设用户A想要将资产X发送给用户B以换取资产Y(基础货币bytes也是资产)。然后他们将组成一个包含两个支付消息的单元:一个支付消息将资产X从A发送到B,另一个支付消息将资产Y从B发送到A。他们共同签署这个单元并发布。资产交换具有原子性,也就是说,两种付款要么同时执行要么两者都失败。如果其中一笔付款是双花支出,则整个单元将被视为无效,另一笔付款也被视为无效。

这种简单的结构允许用户直接交换资产,而无需信任任何第三方中心机构。

21. 地址定义(Addresses)

Byteball中用户通过其地址来标识,交易输出发送到地址。从隐私角度考虑,与比特币一样,建议用户生成多个地址并避免重复使用。但是,在某些情况下,地址重用也是正常的。例如,见证人就是从同一地址不断地发出交易。

地址本质上对应的是一段具有特定含义的脚本,该脚本称为地址的定义。任何能够使地址定义脚本输出为真(也称作解锁脚本)的人具有使用该地址资产的权限。与Bitcoin类似,最常用的地址定义脚本是公钥(采用BASE64编码),即具有相应私钥的人可以使用该地址的资产,比如

["sig",{"pubkey":"Ald9tkgiUZQQ1djpZgv2ez7xf1ZvYAsTLhudhvn0931w"}]

该定义表明地址的所有者有一个私钥,其公钥在定义中给出(以base64编码),并且他将使用该私钥对所有单元进行签名。如果作者给出的签名有效,则上述定义的结果为真,否则为假。签名是在单元中除了签名之外的所有数据上计算的。

给定地址定义脚本,其对应的地址只是它的哈希值再加上校验和。添加校验和是为了避免输入错误。但是,与通常的校验和设计不同,Byteball地址的校验和位不是放在未校验数据的末尾。相反,它们被插入到未校验数据中的多个位置。这种设计使得在需要地址的字段中插入长串的非法数据变得十分困难。地址用采用base32编码。以上定义对应的地址为A2WWHN7755YZVMXCBLMFWRSLKSZJN3FU

当地址用来接收交易时,发送者仅指定支付输出中的地址(即地址定义脚本的哈希和校验)。而地址定义脚本并未公布,除了所有者之外,任何人都不知道该定义。

当用户从地址发送他的第一个单元时,他必须在authors数组中显示其定义(从而可以进行签名验证):

unit: {
  ...
  authors: [ {
    address: 'DJ6LV5GPCLMGRW7ZB55IVGJRPDJPOQU6',
    definition: [
      "sig", {"pubkey":"AsnvZ3w7N1lZGJ+P+bDZU0DgOwJcGJ51bjsWpEqfqBg6"}
    ],
    authentifiers: {
      r: '3eQPIFiPVLRwBwEzxUR5thqn+zlFfLXUrzAmgemAqOk35UvDpa4h79Fd6TbPbGfb8VMiJzqdNGHCKyAjl786mw=='
    }
  } ],
  ...
}

如果用户从同一地址再次发送交易单元,则必须省略该定义(其定义在Byteball上已经知道了)。此外,只有在第一个单元稳定后才能发送第二个单元,即显式定义的单元必须被第二个单元的最近雪球包含。

用户可以在保留旧地址的同时更新其地址的定义。例如,要更换地址的私钥,用户需要发布包含以下消息的单元:

unit: {
  ...
  messages: [
    ...
    {
      app: "address_definition_change",
      definition_chash: "I4Z7KFNIYTPHPJ5CA5OFC273JQFSZPOX"
    },
    ...
  ],
  ...
}

其中,definition_chash为新的地址定义脚本生成的地址(其定义还没有公布),且该单元必须用原私钥进行签名。同时,下一个从原地址发出的单元有以下两条要求:

  1. 必须把address_definition_change这个单元作为其last_ball
  2. 在修改地址定义脚本后发出第一个单元时,需要把新的定义脚本作为第一条message

修改后,地址不再等于其当前定义的哈希值和校验。相反,它仍然等于其初始定义的哈希和校验。

如果用户想要在保持旧地址不变的情况下同时改变私钥,就可以使用这种方式来修改地址定义脚本。例如,当用户迁移到新设备时,由于原地址已经被用于其它定义脚本中,那么他就需要保持地址不变。

21.1. 定义语法(Definition syntax)

21.1.1. 逻辑运算(Logical operators)

地址定义中可以包含“与运算”,比如

["and", [
  ["sig", {pubkey: "one pubkey in base64"}],
  ["sig", {pubkey: "another pubkey in base64"}]
]]

为了发送交易,需要两个私钥同时签名,比如一个私钥存储在电脑、另一个私钥存储在手机上。

地址定义也支持“或运算”,比如

["or", [
  ["sig", {pubkey: "laptop pubkey"}],
  ["sig", {pubkey: "smartphone pubkey"}],
  ["sig", {pubkey: "tablet pubkey"}]
]]

当多个条件中有一个满足时,脚本输出为真。比如,仅需要laptopsmartphone或者talet中某一个私钥就可以解锁脚本。

逻辑运算可以嵌套使用:

["and", [
  ["or", [
    ["sig", {pubkey: "laptop pubkey"}],
    ["sig", {pubkey: "tablet pubkey"}]
  ]],
  ["sig", {pubkey: "smartphone pubkey"}]
]]

比如,必须同时拥有smartphone私钥以及laptop或者tablet中某一个私钥就可以解锁脚本。

地址定义可以要求满足条件的个数超过门限时,脚本输出为真。比如,具有2个以上私钥就可以解锁的脚本:

["r of set", {
  required: 2,
  set: [
    ["sig", {pubkey: "laptop pubkey"}],
    ["sig", {pubkey: "smartphone pubkey"}],
    ["sig", {pubkey: "tablet pubkey"}]
  ]
}]

r代表“必需”),它具有两个签名的安全性和可靠性。同时,如果其中一个私钥丢失,该地址仍然可用,并可以更改其定义来替换第三个私钥。

此外,不同的条件可以赋予不同的权重,当满足条件的权重值超过门限时,脚本输出为真:

["weighted and", {
  required: 50,
  set: [
    {weight: 40, value: ["sig", {pubkey: "CEO pubkey"}] },
    {weight: 20, value: ["sig", {pubkey: "COO pubkey"}] },
    {weight: 20, value: ["sig", {pubkey: "CFO pubkey"}] },
    {weight: 20, value: ["sig", {pubkey: "CTO pubkey"}] }
  ]
}]

21.1.2. 地址代理(Delegation to other addresses)

可以授权使用其它地址来解锁脚本,其定义的语法为:

["and", [
  ["address", "ADDRESS 1 IN BASE32"],
  ["address", "ADDRESS 2 IN BASE32"]
]]

这可以很方便地用来构造共享控制的地址。此语法使用户可以随时灵活地更改自己地址的定义,而无需通知其他用户修改地址。

21.1.3. 地址签名(Signatures and authentifiers)

在大多数情况下,定义将包括至少一个签名验证条件(直接或间接):

["sig",{pubkey: "pubkey in base64"}]

但有的时候,定义可能需要提供原始的哈希值:

["hash",{"hash": " value of sha256 hash in base64"}]

这对跨链交换算法非常有用[7]。在这种个情况下,哈希值作为一个验证条件输入。

默认的签名算法是曲线secp256k1上的ECDSA(与比特币相同)。目前,它是唯一支持的算法。如果将来添加其他算法,可以在定义的相应部分添加算法标识符,例如量子安全NTRU算法:

["sig",{algo: "ntru", pubkey: "pubkey in base64"}]

通过使用多重签名,人们在将传统的签名方案和未经证实的签名方案相结合进行安全地试验。

交易单元中的authentifiers对象包含了按照地址定义中签名路径需要验证的签名或其他数据(例如哈希值)。对于单签名地址,例如:

["sig",{pubkey: "pubkey in base64"}]

签名路径为rr代表root)。如果需要签名的地址定义发生了嵌套(例如“与运算”和“或运算”)中,则签名路径需要通过索引扩展到包含此定义的数组中,并且签名路径由符号.分隔。例如,对于地址定义:

["and", [
  ["sig", {pubkey: "one pubkey in base64"}],
  ["sig", {pubkey: "another pubkey in base64"}] 
]]

签名路径是r.0r.1。对于更深层的嵌套定义:

["and", [
  ["or", [
    ["sig", {pubkey: "laptop pubkey"}],
    ["sig", {pubkey: "tablet pubkey"}] 
  ]],
  ["sig", {pubkey: "smartphone pubkey"}] 
]]

签名路径是r.0.0r.0.1r.1。当有多重签名时,例如2-of-3,签名路径告诉我们实际使用了哪些私钥。

21.1.4. 地址模板(Definition templates)

通过预先设定的脚本模板可以很方便地定义脚本,只需要对模板中的参数进行修改即可

["definition template", [
  "hash of unit where the template was defined",
  {param1: "value1", param2: "value2"}
]]

参数指的是要在模板中替换的变量的值。脚本模板需要在单元中发送app='definition_template'的消息,并且需要单元到达稳定状态后,脚本模板才可以使用。消息内容与普通的地址定义脚本相同,但可能包含对参数的引用,参数使用@param1@param2表示。地址模板支持代码重用。它们也可能引用其它模板。

21.1.5. 共同签名(Cosigning)

要求与另一个地址共同签名才可以解锁脚本:

["cosigned by", "ANOTHER ADDRESS IN BASE32"]

21.1.6. 地址查询(Querying whether an address was used)

要求由某个地址发出的单元至少有一个被last_ball_unit包含:

["seen address", "ANOTHER ADDRESS IN BASE32"]

21.1.7. 数据订阅(Data feeds)

通过订阅的数据是否符合条件来解锁脚本,其语法格式为:

["in data feed", [
  ["ADDRESS1", "ADDRESS2", ...],
   "data feed name",
   "=",
   "expected value"
]]

上述脚本表示:当数据源地址ADDRESS1ADDRESS2等中某个地址发出的消息中数据data feed name等于expected value时,脚本输出为真。

数据源地址发出的数据订阅消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "data_feed",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        "data feed name": "value",
        "another data feed name": "value2",
        ...
      }
    },
    ...
  ],
  ...
}

数据字段可用来使用播报机(oracles),播报机是被多方共同信任的实体。通过播报机,多个用户可以设置共享控制地址,该地址根据播报机发布的数据为各方提供不同的权限。例如,下面的地址定义表示了一种选项:

["or", [
  ["and", [
    ["address", "ADDRESS 1"],
    ["in data feed", [["EXCHANGE ADDRESS"], "EURUSD", ">", "1.1500"]]
  ]], 
  ["and", [
    ["address", "ADDRESS 2"],
    ["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2016-10-01 00:00:00"]]
  ]] 
]]

最初,双方为此脚本所定义的地址进行充值(为了消除任何信任要求,他们使用多签名并将资金发送到双方签署的同一单元中)。然后,如果EXCHANGE ADDRESS公布的欧元/美元汇率超过1.1500,ADDRESS 1可以清算资金。如果在TIMESTAMPER ADDRESS发布的时间2016年10月1日之前没有发生这种情况,则ADDRESS 2可以获得存储在该地址上的所有资金。如果两个条件都为真并且地址余额仍然是非空的,则双方都可以尝试从中获取资金,并且根据双花的方式进行解决。

比较运算符可以是“=”,“!=”,“>”,“> =”,“<”和“<=”。数据订阅的交易单元必须达到稳定。为了降低任何单个播报机突然脱机时出现的风险,可以同时列出多个播报机地址。

另一个例子是从商家那里购买商品的顾客,但是他并不完全信任该商家,需要在商品没有送达时取回他的钱。顾客可以支付到以下定义的共享地址:

["or", [
  ["and", [
    ["address", "MERCHANT ADDRESS"],
    ["in data feed", [["FEDEX ADDRESS"], "tracking", "=", "123456"]]
  ]], 
  ["and", [
    ["address", "BUYER ADDRESS"],
    ["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2016-10-01 00:00:00"]]
  ]] 
]]

该地址定义需要FEDEX ADDRESS对它所有已经成功送达的货物发布消息。如果货物已经送达,商家将能够使用第一个条件解锁货币。如果在指定日期之前没有交付,客户可以退回他的钱。

这个例子有点疯狂,因为它要求FedEx对每一批货物都要发布一笔交易。

21.1.8. Merkle根查询(Merkle data feeds)

为了实现相同目标,更现实的方法是使用另一种定义脚本:

["in merkle", [
  ["ADDRESS1", "ADDRESS2", ...], 
   "data feed name",
   "hash of expected value"
]]

如果指定的哈希值包含在来自地址ADDRESS1ADDRESS2等的数据源中发布的任何merkle根中,则计算结果为真。使用这种定义,FedEx仅需要在完成一批货物发送后定期发布所有的merkle根。要从这个地址提取资金,商家必须提供证明表明指定的哈希值确实包含在相应的merkle树的merkle路径中。merkle路径需要作为一个签名提供。

21.1.9. 输入输出条件(Self-inspection)

脚本可以对相应地址发出的单元数据进行条件约束,其定义格式为:

['has', {
  what: 'input'|'output',
  asset: 'assetID in base64 or "base" for bytes',
  type: 'transfer'|'issue',
  own_funds: true,
  amount_at_least: 123,
  amount_at_most: 123,
  amount: 123,
  address: 'INPUT OR OUTPUT ADDRESS IN BASE32'
}]

上述脚本要求单元至少有一个输入/输出(由what指定)满足后续定义所有的条件。

特别地,可以使用has one来强制要求有且仅有一个输入/输出满足后续所有条件。

has条件可用于实现去中心化交易。在此之前,我们讨论过使用多签名来交换资产。但是,多签名并不包括任何价格谈判机制。假设用户想要购买1200个另一种资产,但他愿意支付不超过1000 bytes。同时,他也不希望一直在线等待卖家的同意。最好他只需要在交易所挂单,并由相应的卖家来执行。他可以通过向下面定义的地址发送1000 bytes来创建买单:

["or", [
  ["address", "USER ADDRESS"], 
  ["and", [
    ["address", "EXCHANGE ADDRESS"], 
    ["has", {
      what: "output",
      asset: "ID of alternative asset", 
      amount_at_least: 1200,
      address: "USER ADDRESS"
    }] 
  ]]
]]

或运算的第一个条件允许用户随时收回他的资金,从而取消订单。或运算的第二个条件允许EXCHANGE ADDRESS取走资金,只要在同一个交易单元中USER ADDRESS收到1200个单位的另一种资产。交易所将公开列出买单,卖方会找到它,构造一个交换资产的单元,并与交易所进行多签名。

人们也可以使用has条件进行抵押贷款。假设借款人持有一些非流动资产并需要一些bytes(或另一种流动资产)。借款人和贷款人然后可以同时签名一个单元。该单元的一部分将需要的bytes发送给借款人,该单元的另一部分将非流动资产锁定到由以下定义的地址:

["or", [
  ["and", [
    ["address", "LENDER ADDRESS"],
    ["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2017-06-01 00:00:00"]]
  ]], 
  ["and", [
    ["address", "BORROWER ADDRESS"], ["has", {
      what: "output",
      asset: "base",
      amount: 10000,
      address: "LENDER ADDRESS"
    }] 
  ]],
  ["and", [
    ["address", "LENDER ADDRESS"], 
    ["address", "BORROWER ADDRESS"]
  ]] 
]]

如果贷款没有及时偿还,或运算的第一个条件允许贷方扣押抵押品。如果借款人还向贷方支付10000 bytes(含利息),或运算的第二个条件选择允许借款人收回抵押品。或运算的第三个条件允许双方在双方同意的情况下修改条款。

以下要求也可以包含在定义中:

['has equal', {
  equal_fields: ['address', 'amount'], 
  search_criteria: [
    {what: 'output', asset: 'asset1', address: 'BASE32'}, 
    {what: 'input', asset: 'asset2', type: 'issue', own_funds: true, address: 'ANOTHERBASE32'}
  ] 
}]

如果存在至少一对满足搜索条件的输入或输出(第一组过滤器搜索输出,第二组过滤器搜索输入),并且它们的某些字段相等,则结果为真。

类似的条件还有has one equal,要求仅存在一个满足要求的输入输出。

其它条件还包括求和约束条件,要求输入/输出之和满足特定条件,其格式为:

['sum', {
  filter: {
    what: 'input'|'output',
    asset: 'asset or base',
    type: 'transfer'|'issue',
    own_funds: true,
    address: 'ADDRESS IN BASE32'
  },
  at_least: 120,
  at_most: 130,
  equals: 123
}]

21.1.10. 非运算(Negation)

任何地址定义脚本中不含sighashaddresscosigned by或者in merkle的条件可以进行非运算,比如:

["not", [
  "in data feed",
  [["NOAA ADDRESS"], "wind_speed", ">", "200"]
]]

由于选择非常老的父单元(没有看到最近的数据)是合法的,因此通常将上述的“非运算”与时间戳在特定日期之后的要求相结合。

21.2. 基本要求(General requirements)

地址定义脚本中必须显式地(使用sig)或隐式地(使用address)包含至少一个sig

为避免消耗太多资源进行验证,地址定义的操作总数限制为100,包括引用定义中的操作,如addressdefinition template

这个数字是我们在Byteball中仅有的9个常数之一。另外8个是包括,见证人总数:12;见证人列表最大允许突变数:1;见证人获得支付的最大主链长度:100;用于计算单元头大小的父单元个数:2;每个单元的最大消息数:128;每条消息的最大输入或输出数量:128;每个单元作者的最大数量:16;bytes总供应量:$10^{15}$。相比之下,比特币至少有17个常数[8],而以太坊仅在手续费计算中就定义了30个常数[9]。

请注意,上面描述的定义语言是声明性的,完全由布尔语句组成,这使得它更接近传统法律合同的语言。然而,就其表达能力而言,并没有以太坊智能合约语言的强。事实上,它甚至不允许编写一个简单的Hello world程序。但这不是我们的目标。Byteball定义的语言不是图灵完备的;相反,它被设计为可以被尽可能多的人理解,这些人不一定是程序员。其简单的语法允许每个人在没有开发人员(智能合约时代的律师)的帮助下解释和撰写简单定义,并且最大限度地减少错误的可能性。

22. 用户档案(Profiles)

用户可以通过Byteball发布和存储个人信息,消息格式为

unit: {
  ...
  messages: [
    ...
    {
      app: "profile",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        name: "Joe Average",
        emails: ["joe@example.com", "joe@domain.com"],
        twitter: "joe"
      }
    },
    ...
  ],
  ...
}

用户披露的关于他们自己的信息以及其真实性取决于用户自己。为了确保关于用户的任何特定信息是真实的,只有通过认证的信息才是可信的。

23. 用户认证(Attestations)

用户认证用于确定用户发布的个人信息的真实性,其消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "attestation",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        address: "ADDRESS OF THE SUBJECT"
        profile: {
          name: "Joe Average",
          emails: ["joe@example.com"]
        }
      }
    },
    ...
  ],
  ...
}

认证消息中的个人信息不一定与用户自己发布的信息完全一致,事实上,用户可能甚至没有自己发布过个人信息。

Byteball中的认证者类似于现实世界中的实名认证机构,认证某个地址是归属于某个个人或组织。认证方可以向被认证方收取少量费用。一般来讲,见证人节点是需要通过认证的,这样可以提高受信任度。被认证方可以选择不公布认证信息,而只在Byteball中保存认证证据,并在合适的时机公布。

对于需要通过认证但具体信息不重要的应用场合,可以在认证的消息中省略名称或其他个人身份信息。认证的内容甚至可能根本不包括关于用户的任何有意义的信息。因此除了认证者之外,用户信息对其他人都是匿名的。认证者仍将保留有关该用户的记录,并可在某些情况下披露,如在认证的具体条款或法律要求的约束下。

24. 数字资产(Assets)

Byteball本质上是基于DAG的分布式数据库,数据状态一旦确定则不可逆转。在各种类型的数据中,具有社会普遍意义的数据是比较有价值的,比如个人资产数据。资产可以由任何人拥有,而我们在Byteball中拥有的不可变性和事件总序的属性对于确定资产所有权转移的有效性非常重要。在Byteball中,资产可以发布、转移以及交换,类似于Byteball的基础货币bytes。资产可以代表任何有价值的东西,比如债务、股票、会员积分、通话时间、商品、其它加密货币等。

定义新资产的消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "asset",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        cap: 1000000,
        is_private: false,
        is_transferrable: true,
        auto_destroy: false,
        fixed_denominations: false,
        issued_by_definer_only: true,
        cosigned_by_definer: false,
        spender_name_attested: true,
        attestors: [
          "2QLYLKHMUG237QG36Z6AWLVH4KQ4MEY6",
          "X5ZHWBYBF4TUYS35HU3ROVDQJC772ZMG"
        ]
      }
    },
    ...
  ],
  ...
}

在定义新资产时,可以设置以下属性:

  • cap:资产总量,比如bytes的总量为$10^{15}$;
  • is_private:资产转移是否公开,比如bytes为公开;
  • is_transferrable:资产是否可以在无发行方允许的条件下进行流通,如果不可流通,则资产的收发方中必须有发行方,比如bytes为可流通;
  • auto_destroy:资产在发送回发行方时是否自动销毁,比如bytes为不自动销毁;
  • fixed_denominations:资产是否以固定面额进行流通(类似纸币),比如bytes可以以任意金额流通;
  • issued_by_definer_only:资产是否仅由发行方发布,比如bytes均在创世单元中发布;
  • cosigned_by_definer:资产在每次转移时是否必须由发行方共同签名,比如bytes是不需要的;
  • spender_attested:资产在使用时用户是否需要通过认证。如果用户碰巧收到了资产但尚未通过认证,他必须通过与定义中列出的认证机构的认证,以便能够使用该资产。此要求对受监管资产也很有用。比如bytes是不需要的;
  • attestors:受资产发行方认可的认证机构,可以在后续过程中通过attest_attestors消息进行修改;
  • denominations:如果资产具有固定面额,定义面额种类以及各类别总量;
  • transfer_condition:资产转移需要的额外条件,语法与地址定义脚本相同(除了不使用sig之外)。默认情况下,除了已经由其字段定义的限制之外没有任何限制;;
  • issue_condition:与资产转移需要的额外条件类似,资产发布需要的额外条件。

在定义资产时,每个单元中最多只能有一条asset消息。当资产定义单元发布后,后续都通过引用该单元的哈希来引用该资产。

资产的发送看起来像和bytes相同,不同之处在于有一个额外的资产ID字段:

unit: {
  ...
  messages: [ 
    ...
    {
      app: "payment",
      payload_location: "inline", 
      payload_hash: "hash of payload", 
      payload: {
        asset: "hash of unit where the asset was defined",
        inputs: [
          {
            unit: "hash of source unit", 
            message_index: 0, 
            output_index: 1
          },
          ...
        ],
        outputs: [ 
          {
            address: "BENEFICIARY ADDRESS",
            amount: 12345 
          },
          ...
        ],
      } 
    },
    ... 
  ],
  ...
}

发布资产的消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "payment",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        asset: "hash of unit where the asset was defined",
        inputs: [
          {
            type: "issue",
            amount: 1000000,
            serial_number: 1,
            address: "ISSUER ADDRESS" // only when multi-authored
          },
          ...
        ],
        outputs: [
          {
            address: "BENEFICIARY ADDRESS",
            amount: 12345
           },
          ...
        ]
      }
    },
    ...
  ],
  ...
}

总量有限的资产必须在一个交易中全部发布,比如,所有的bytes都是在创世单元中发布的。如果资产总量有限,发布时serial_number必须为1;如果资产总量不受限,每次发布时serial_number必须保证不同。

转移资产与bytes类似,只是需要加上资产的ID,其消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "payment",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        asset: "hash of unit where the asset was defined",
        inputs: [
          {
            unit: "hash of source unit",
            message_index: 0,
            output_index: 1
          },
          ...
        ],
        outputs: [
          {
            address: "BENEFICIARY ADDRESS",
            amount: 12345
          },
          ...
        ]
      }
    },
    ...
  ],
  ...
}

对于有总供应量的资产必须在单笔交易中全部发行。特别地,所有的bytes都在创世单元中发布的。如果资产有总供应量,则发布单元的serial_number必须为1。如果没有总供应量,则同一地址的不同的发布单元的serial_number必须是唯一的。

资产只能定义一次,除了attestors之外均不能进行修改。

资产定义的解释权在发行方,其具体含义由其进行解释。如果是发行人的债务,可以合理地预期发行人应该需要通过实名认证以赢得债权人的信任。

虽然最终用户可以自由使用或不使用资产,但资产定义者可以对涉及资产的交易施加任何要求。

资产定义中的不同属性的组合可以适用不同的场景,发行者可以设计满足各种要求的资产,包括受监管的金融机构必须遵循的要求。例如,通过要求每笔转让由发行者签署,金融机构可以有效地否决所有违反任何监管或合同规则的付款。在核准每笔付款之前,金融机构(也是定义者和发行人)将检查用户确实是其客户,资金的接收者也是客户,两个客户都已通过所有了解您的客户( KYC)程序。从而确定资金可以接受法院命令,以及不断变化的法律,法规和内部规则所要求的任何其他检查,包括在资产定义后引入的那些规则。

24.1. 银行类资产(Bank issued assets)

由于具有完全合规的安全性(并且所有资金转移的最终确定性也得到保证),银行可以发行与本国货币挂钩并由银行作为担保的资产(由中央银行进行适当审计和监督) 。此类资产任何业务的法律性质与所有其它银行资金完全相同,并且对每个人都很熟悉。唯一不同的是,转账和余额是在Byteball数据库而不是银行的内部数据库中进行操作。在Byteball数据库中进行操作有两个后果:

  • (不太受欢迎的方面)类似比特币,所有操作都是公开的,这可以通过使用多个半匿名地址来缓解,而只有银行知道地址背后的真实人。保护隐私的另一种更有效的方法是隐私支付,我们将在后面讨论;
  • (比较好的方面)银行发行的资产可以以点对点的方式在链上交换bytes或其它资产,而不必信任任何第三方,比如交易所。这里的银行类似于Ripple网关。

在上面的资产交换场景中,交易所用来实现将银行发行的资产从一个用户转移到另一个用户。如果两个用户都是同一个银行的客户,则此过程非常简单。当用户在不同的银行持有账户时,银行可以通过相互开立代理账户的方式来实现银行间转账。假设在用户U1持有银行B1的账户并且用户U2持有银行B2的账户的情况下,用户U1想要将钱转移到用户U2。B2银行需要在B1开立账户。然后U1将资金转移到B2在B1的账户(这是B1内部的银行转账,由B1签署)。与此同时,B2(刚刚获得了B1的资产)向其用户U2转移资金。这一切都必须具有原子性。因此,所有三个参与者:(U1,B1和B2)必须同时签署一个交易单元,将B1的资金从U1转移到B2,并将B2的资金转发给U2。

最终结果是U1在B1处减少了他的余额,U2在B2处增加了他的余额,B2在B1处增加了他的余额。银行B1还将在B2处拥有一个代理账户,其余额将随着从B2用户向B1用户的转账而增长。相互的账务(B2处的B1和B1处的B2)可以通过银行相互签署一笔交易来部分抵消,该交易向相应的发行方发送相等的金额(通过将其发送给发行人来自动销毁这些资产是方便的)。未抵消的部分可以通过传统的银行间支付定期结算。为了进行结算,具有余额的银行将其余额发送给发行方,由于在同一交易中没有反向转账,这需要发行方向持有人进行法定货币的传统支付。

当多家银行时,与每个银行之间建立直接的对应关系可能很麻烦。在这种情况下,银行可以同意建立一个中央机构C(大型会员银行或新机构),并通过该中央机构完全支付所有款项,并仅与其结算。那么,从U1到U2的资金转移将包含3个交易:

  1. U1向B1的C账户汇款;
  2. C将自己的钱发给B2(或者C通过减少B2的余额);
  3. B2向U2转账。

所有3个事务都捆绑在一个交易单元中,并由U1,B1(作为所有U1事务所需的协同者),C和B2签名。

24.2. 非金融资产(Non-financial assets)

其它非金融应用的场景也可以使用Byteball资产。例如,用户积分奖励可以将积分作为数字资产发布,并使用Byteball的现有基础设施来允许人们点对点交易积分(如果规则允许)。对于游戏开发者也是一样,可以在Byteball上发布游戏资产。

24.3. 债券(Bonds)

企业可以在Byteball上发行债券。该债券的法律结构与传统债券的法律结构相同,唯一的区别是现在使用Byteball而非内部数据库(类似于上述银行)跟踪债券所有权。在Byteball中拥有债券使其持有人能够直接进行交易,而无需使用中心化交易所。当银行资金也在Byteball上时,即时交付与付款(在这种情况下的法定付款)变得可能,没有交易对手风险且没有任何中央机构。债券和资金的所有权同时交换,只要双方签署同一交易单元进行转账。

债券如果具有足够的流动性,也可以被第三方用作支付手段。

发行债券时,发行人和投资者将共同签署一个交易单元,将新发行的债券发送给投资者,同时投资者向借款人发送bytes(或用于购买债券的其它资产,如银行发行的法定货币)。当债券被赎回时,他们又共同签署另一个赎回交易(很可能是以不同的汇率)。在赎回期间支付的债券价格是其面值,而在发行时售出的价格必须低于面值以反映利息(为简单起见假设零息票债券)。在其生命周期中,债券的二级市场价格保持低于面值并逐渐接近它。

在一个不断发展的经济中,有许多项目可以融资,在Byteball上发行的债券和其他债务融资投资将比赎回更频繁。当经济放缓时,所有债券的总供应量减少,因为融资项目较少。因此,如果债券被积极地用作支付手段,它们的总供应量可以自我调节,这是很重要的。

如果两家企业以net-30条款进行交易,则买方和卖方都可以选择在30天期限内对贸易信贷进行证券化。例如,买方可以发行30天的债券并使用它们立即向卖方付款。然后卖方可以等待30天通过并赎回债券,或者使用债券作为支付给自己供应商的手段。在这种情况下,供应商会在债券到期时赎回债券。

24.4. 商品债券(Commodity bonds)

债券可以使用商品为单位进行发行,而不仅仅是货币。例如,100桶石油的债券使其持有人有权在债券到期时获得100桶石油;1千瓦时电力的债券使持有人有权获得1千瓦时的电力。持有人还可以选择以到期日当前价格获得100桶或1千瓦时的等值货币。

这种债券(商品债券)实际上对套期保值风险非常有用。考虑一个新的石油项目,该项目在开始商业运营之前需要很多年和大量投资。如果仅以本国货币寻求融资,由于新设施开始出售石油时油价不确定,该项目可能永远不会获得融资。债权人必须考虑价格过低的风险,这将导致借款人必须违约。债权人希望风险定价为利率,这意味着利率变得过高,项目永远不会发生。

但是,如果项目经营者可以用石油进行借款,则违约风险会急剧下降。现在,该项目可能会按计划开始,并可能产生计划的石油量。因此,它将能够在指定时间内生产和偿还所有借来的石油。虽然还有其他风险,但一个巨大的风险(即市场风险)被消除。风险从借款人中移除,但转移到贷方。他们现在必须考虑油价下跌的可能性,他们收到的(以货币计算)比投资少。另一方面,如果价格上涨,贷款人可以从价格差异中获得额外的利润(请注意,通过借入石油,借款人放弃了这种上行潜力),并且总有投资者愿意在商品中占据一席之地。由于债券在Byteball上交易,贷款人可以随时轻松卖出。与石油期货不同,其交易是零和游戏,对商品债券的投资确实为该行业提供资金。此外,石油期货是一种短期工具,而商品债券允许买入和持有,这更适合长期投资者。

还有另一类潜在的贷款人,那些对冲相反风险的人。例如,航空公司希望对冲油价的上涨,而这样做的一种方法是购买石油生产公司的商品债券,而人们期望与石油价格相关联。

以上情况适用于任何商品,例如:电力,铁矿石,黄金,其他金属,农作物等。

从借款人的角度来看,商品债券可以被视为以今天的价格出售未来生产的一种方式。对于贷款人来说,这是以今天的价格购买未来供应品的一种方式。

如果经济的很大一部分依赖商品债券,即使没有政府干预,杠杆周期也会自然平滑,因为在经济衰退期间,商品价格会自动降低债务。

24.5. 基金(Funds)

对于个人用户,很难持续跟踪市场上可用的大量债券。相反,他们宁愿选择专业投资管理的基金并持有大量多元化债券组合。基金将发行自己的资产,对标基金投资组合的总价值。每当投资者购买该基金的新发行资产时,该基金将使用所得款项购买债券。当用户退出时,基金会出售其持有的部分债券并销毁用户返回的基金发行资产。基金的资产没有上限;随着投资者进出,其总供应量会有所不同。它的价值很容易被审计,因为基金持有的所有债券都在Byteball上可见。由于基金比债券更具流动性,因此基金资产成为支付手段的可能性更高。

24.6. 清算(Settlements)

银行之间可以使用数字资产进行银行间结算。一些较大的银行发行的与法定货币挂钩的资产只能由经过认证的用户使用,而且只有集团成员才能得到认证。该资产由发行银行的储备资金支持。当一家规模较小的银行希望与另一家较小的银行进行结算时,它只需要发送相应的数字资产。收款银行可以以同样的方式使用该资产与其他银行结算,或者将其兑换为法定货币。银行还可以用与欧元挂钩的资产或类似资产交换美元挂钩资产。所有此类转账和交易均立即结算,最终且不可撤销。在SWIFT,银行只交换有关付款的信息,而实际的资金转移是一个单独的步骤。在Byteball中,信息就是转账。

25. 隐私支付(Private payments)

到目前为止,我们只考虑了公开发送的支付消息,即他们的交易数据包含在交易单元中并且对每个人都可见。请记住,Byteball允许发布私有交易:用户将交易数据保持为私有(payload_location ='none'),仅发布其哈希值以便能够证明数据在特定时间存在。为了将其应用于支付,资金的发送者还需要通过专用通信渠道将私有交易数据发送给接收者。收件人需要在Byteball中查找哈希值以确认它是否存在。然而,由于向其他Byteball节点隐藏了交易内容,我们也丧失了验证相同输出是否双花的能力,这显然是不能接受的。要恢复此功能,我们会在单元中添加一个额外的公共字段。该字段称为花费证明,它的构造方式如下:

  • 它仅取决于消耗的输出,因此尝试再次使用相同的输出将产生相同的花费证明;
  • 它没有透露有关所用输出的任何信息。

很容易看出这种结构满足上述要求:

spend_proof = hash({
  asset: payload.asset,
  unit: input.unit,
  message_index: input.message_index,
  output_index: input.output_index,
  address: src_output.address,
  amount: src_output.amount,
  blinding: src_output.blinding
})

这里,payload.asset是隐私资产的ID,input是指消耗先前输出src_output的输入。隐私资产的输出应该有一个名为blinding的额外字段,它只是一个随机字符串,旨在在知道其花费证明时无法预知已消耗的输出(所有其他字段来自可以在合理的时间范围内迭代的一组相当窄的可能值)。

上述花费证明的结构适用于转账。对于隐私资产的发行:

spend_proof = hash({
  asset: payload.asset,
  address: "ISSUER ADDRESS",
  serial_number: input.serial_number, // always 1 for capped assets
  amount: input.amount, // issue amount
  denomination: 1 // always 1 for arbitrary-amounts payments
})

在发行隐私资产时,由于需要公开表明已发行该资产,因此不需要添加扰乱因子。在资产转移过程中,发送者已知扰乱因子,虽然他可以知道接收者是否花费了这笔资产。但是他无法知道这笔资产的下一个接收者是谁以及他的扰乱因子,也就无法继续跟踪该笔资产的进一步流向了。

花费证明需要添加到交易单元中,其格式为:

unit: {
  ...
  spend_proofs: [
    {
      spend_proof: "the above hash in base64",
      address: "SPENDING ADDRESS" // only if multi-authored
    },
    ...
  ],
  ...
}

在发送隐私资产时,发送者需要完成以下几件事情:

  • 对每个输出添加扰乱因子;
  • 将隐私资产通过私有通道发送给接收者,以及发送该资产所在的交易单元;
  • 对于单元中每个输入,计算相应的spend_proof并加入单元中;

如果验证者看到从同一地址发布的相同花费证明,则他们应拒绝该单位(前提是该地址是连续发布的)。接收者需要检查两件事情:(1)检查收到的隐私资产的hash值是否与区块链单元中的payload_hash相同;(2)检查通过收到的隐私资产计算得到的spend_proof是否与交易单元中的匹配。

当收到隐私资产的用户想要花费其输出时,他必须将他收到的交易数据转发给新的收款人,以便新的收款人可以验证整个所有权链转移(历史),从而追溯到资产发行的地方。所有权链的长度将随着每次转移而增长。

请注意,到目前为止,按照我们考虑的支付格式,每个单元可以合并来自几个先前单元的输出并产生几个新输出(最常见的是两个)。反过来,每个先前的单元依赖于几个甚至更早的单元,并且每个输出稍后将被分成几个新的输出。因此,具有资产发行单元的部分输出的单元数量随时间呈指数增长。相反,对单元输入作出贡献的单元数量随着历史长度呈指数增长。为了避免历史记录的快速增长,我们需要限制资产的可分割性,这就是将fixed_denominations属性设置为true的资产的应用场合。

26. 固定面值资产(Fixed denominations assets)

固定面值资产作为一组不可分割和不可合并的资产存在,非常类似于每个人都熟悉的硬币和纸币。

资产的面值被限定在一组固定面额中的一种,这样一来,可以方便地用最大的精度和最合理的数量来表示大小。大多数现代货币系统的面值遵循1-2-5模式:1, 2, 5, 10, 20, 50, 100, 200, 500等。这种模式也推荐用于Byteball上的固定面值资产。

资产最初被分组成包,类似于纸币包。包可以拆分成较小的子包或单个资产,但不能重新合并。这意味着每次转移必须只有一个输入(因为不允许合并),输出量必须是资产面值的倍数(因为面值是最小的不可分割的数量)。

每笔发行或转移交易,只处理一种面额的资产。它不能同时发行或转移不同面额的资产(但每个存储单元可以包括多个这样的交易)。固定面值资产的交易格式与具有任意金额资产的交易几乎相同,不同之处在于只允许一个输入,金额必须是其中一个面额的倍数,且需要添加一个面额字段:

payload: {
  asset: "hash of unit where the asset was defined", denomination: 100,
  inputs: [ // exactly one input
    {
      type: "issue",
    } 
  ],
  amount: 1000000,
  serial_number: 1, // always 1 for capped assets address: "ISSUER ADDRESS" // only when multi-authored
  outputs: [ 
    {
      address: "BENEFICIARY ADDRESS",
      amount: 800 // multiple of 100 
    },
    {
      address: "CHANGE ADDRESS", 
      amount: 999200 // multiple of 100
    } 
  ]
}

如果资产有总供应量,则每种面额的所有资产都必须在单个交易中发行。因此,如果资产具有16个面额,则资产发行的交易单元中共需要16个定义。如果资产没有总供应量,则同一地址的相同面额的发布交易的序列号必须是唯一的。

如果需要发行或转移多种面额的资产(通常是这种情况),付款人在同一单元中需要包括多个这样的消息。在资产转移消息中,消息由单元哈希,消息索引和输出索引标识,这是付款人在之前接收到的支付消息。

对于隐私资产的支付,交易数据需要单独进行发送,并且需要隐藏所有输出的接收者,但收款数量除外:

payload: {
  asset: "hash of unit where the asset was defined", denomination: 200,
  inputs: [{
    unit: "hash of source unit", 
    message_index: 2, 
    output_index: 0
  }],
  outputs: [
    {
      output_hash: "hash of hidden part of output that includes address and blinding factor",
      amount: 800
    },
    ...
  ]
}

输出中公开的信息允许收款人验证所有输出的总和是否与输入匹配。收款人的得到的输出格式为:

output: {
  address: "BENEFICIARY ADDRESS", 
  blinding: "some random string"
}

这使得收款人能够在他决定花费输出时验证output_hash以及构建花费证明。

在Byteball中,我们有一个由这些属性定义的私有固定面额资产黑字节(blackbytes):

{
  cap: 2,111,100,000,000,000,
  is_private: true,
  is_transferrable: true,
  auto_destroy: false,
  fixed_denominations: true,
  issued_by_definer_only: true,
  cosigned_by_definer: false,
  spender_name_attested: false,
  denominations: [
    {denomination: 1, count_coins: 10,000,000,000},
    {denomination: 2, count_coins: 20,000,000,000},
    {denomination: 5, count_coins: 10,000,000,000},
    {denomination: 10, count_coins: 10,000,000,000},
    {denomination: 20, count_coins: 20,000,000,000},
    {denomination: 50, count_coins: 10,000,000,000},
    {denomination: 100, count_coins: 10,000,000,000},
    {denomination: 200, count_coins: 20,000,000,000},
    {denomination: 500, count_coins: 10,000,000,000},
    {denomination: 1000, count_coins: 10,000,000,000},
    {denomination: 2000, count_coins: 20,000,000,000},
    {denomination: 5000, count_coins: 10,000,000,000},
    {denomination: 10000, count_coins: 10,000,000,000},
    {denomination: 20000, count_coins: 20,000,000,000},
    {denomination: 50000, count_coins: 10,000,000,000},
    {denomination: 100000, count_coins: 10,000,000,000}
  ]
}

请注意,我们将以2开始面值的数量增加一倍,因为我们更经常需要它们。例如,在金额4(2 + 2)和9(5 + 2 + 2)中,我们需要两个2。

隐私不可分割(固定面值)资产的转移和发行证明与任意金额的资产完全相同,除了在发行时,面值不一定是1。

与可分割资产的支付不同,每个固定面值资产永远不会与其它资产合并。因此,当隐私资产在转移时,其支付历史随着时间不是指数而是线性增长,并且仍然可管理(假设存储、带宽和CPU等计算资源在可预见的未来继续呈指数级增长)。

随着支付历史的增长,隐私交易数据对可能作为未来收款人的第三方暴露的风险增加。如前所述,由于支付历史增长得相当缓慢,隐私交易数据对第三方的价值可能会随着时间的推移而减少。但是,应该记住,每天发送和接收许多付款的大型商家和交易所可能会累积非常大(但仍然是零散的)的支付历史。因此,即使是隐私支付,也应该避免地址重用。

请注意,在某些情况下,第三方甚至可以通过隐私支付来推断重要信息。例如,在大多数资产包已经拆分成单个资产之后,当用户在同一单元中发送大量隐私支付消息时,观察者可能会推断说用户正在发送最大面额的硬币,因为发送的金额如果大于最大面额,就可能会发送多个最大面额硬币。由此,观察者可以推断出大致的支付额(但仅此而已)。为避免泄露此类信息,建议在多个地址之间传播大量数据时,将它们分开发送。

我们选择的花费证明方法并不是唯一可行的方法。为了向收款人证明他收到的钱以前没有用过,付款人可以向他发送从他的地址发出的所有交易数据。然后收款人可以检查每一个并确认没有双花。我们选择不采用这种方式,因为它涉及不必要的隐私泄漏,并增加了轻客户端代码的复杂性。相反,我们选择在一定程度上增加空间使用,但使验证更简单。

27. 文本数据(Texts)

用户可以在Byteball中存储文本信息,text消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "text",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: "any text"
    },
    ...
  ],
  ...
}

对文本内容的解释取决于作者及其接收者;Byteball节点不会验证文本内容,而只是仅仅检查它是否为字符串。例如,可以使用此消息类型发送不可删除的推文。文本内容可以是隐私的,这也是有用的,例如,用于存储用户知识产权的哈希或用于存储仅少数方需要知道的合同文本的哈希。

28. 结构化数据(Arbitrary structured data)

用户也可以使用Byteball存储任意的结构化数据,data消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "data",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        key: "value",
        another_key: {
          subkey: "other value",
          another_subkey: 232
        }
      }
    },
    ...
  ],
  ...
}

对这些数据的解释取决于需要查看数据的作者及其合作伙伴,Byteball节点除了检查它是否为对象外不会对其进行验证。例如,此类型消息可用于向特定节点集合发布以太坊代码,但请记住,即使代码因以太坊规则无效,他们也无法拒绝该单元。

paymenttext类似,data类型的消息也可以是隐私的,在这种情况下,只存储其哈希值。继续我们以太坊的例子,如果在必要时也设计相应的花费证明,则可以秘密地运行以太坊合约。

29. 投票应用(Voting)

用户可以使用Byteball发起投票,poll消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "poll",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        question: "Should the United Kingdom remain a member of the European Union or leave the European Union?",
        choices: ["Leave", "Remain"]
      }
    },
    ...
  ],
  ...
}

同时,用户可以响应投票,vote消息格式为:

unit: {
  ...
  messages: [
    ...
    {
      app: "vote",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        unit: "hash of the unit where the poll was defined",
        choice: "Leave"
      }
    },
    ...
  ],
  ...
}

投票的有效性需要由发起投票的组织者来决定,除了检查投票选项是否在给定集合内之外,Byteball不会强制执行任何操作。例如,组织者可能只接受来自认证用户的投票或来自白名单用户的投票。因此,虽然Byteball会记录无效的投票,但主办方在计票时应将其排除在外。

权衡投票和解释结果也取决于投票的组织者。如果用户按其余额进行投票,则应考虑到他们可以将余额转移至另一个地址并再次投票这种情况。这些投票应妥善处理。

30. 加密通信(Private messaging)

要使隐私支付正常工作,用户需要一种安全地相互发送隐私交易数据的方法。用户,或者更确切地说是他们的设备,还需要一种通信方法以进行多签名地址的签名。

由于我们不能指望用户设备始终在线且能被外网访问(大多数用户设备在NAT后面),我们需要一个存储转发节点,它始终在线,可被外网访问,并能够临时存储任何发送到用户设备的数据。

在Byteball中,这样的存储转发节点被称为hub,其操作类似于电子邮件。hub是Byteball节点,额外提供存储转发用户设备消息的服务。网络中可以存在多个hub。用户设备可以选择自己信任的hub(本地hub),其他设备可以通过这个hub连接到它。用户设备可以随时更换本地hub。每个设备都有一个永久私钥,该私钥对于设备是唯一的。相应公钥的哈希(更准确地说,基于该公钥的单sig定义的哈希)称为设备地址,并且它与支付地址一样采用base32编码。完整的设备地址(包括其当前hub)可以写为DEVICEADDRESSINBASE32@hubdomainname.com。如果用户设备切换到另一个hub,则设备地址@之前部分保持不变。与电子邮件不同,该名称是唯一的。

每个设备都使用websockets连接到其本地hub。hub会将新消息转发到设备且保持与设备的连接,当hub接收到设备的新消息时,则会立即转发新消息。hub不保留设备已经成功接受的消息的副本。用户设备与hub的连接是TLS加密的。

当设备想要发送消息到另一台设备时,它会连接到收件人的hub并发送消息。与电子邮件不同,hub之间不会中继消息,发件人需要直接连接到收件人的hub。设备之间的所有通信都采用端到端加密和数字签名,因此即使hub(唯一的中间人)也无法查看或修改它。我们使用ECDSA进行签名,使用ECDH + AES进行加密。

在收发加密消息之前,设备必须配对,即获取彼此的公钥。用户能够通过多种方式进行配对,例如,扫描包含设备公钥和hub的二维码,可以用电子邮件发送此信息,或单击安全网站上的byteball://链接。

从通信安全的角度来看,用户设备会生成一个临时私钥,并将相应的公钥上传到hub上。之后,用户设备会不时地更新密钥,但同时保留上一个密钥的副本,以防止有人在更换密钥时用前一个密钥发送消息。hub仅为每个设备保留一个版本的临时公钥。发送方按照如下步骤发送消息:

  1. 连接到接收方的hub;
  2. 从hub获取接收方的临时公钥(使用hub/get_temp_pubkey);
  3. 发送方生成一次性的消息加密钥匙对(包含私钥及公钥);
  4. 通过接收方临时公钥和刚生成的一次性私钥得到ECDH共享密钥;
  5. 利用ECDH共享密钥对消息内容进行AES加密;
  6. 发送方将加密后的消息内容、初始向量、一次性公钥打包为数据包;
  7. 采用设备永久公钥对数据包进行签名;
  8. 采用hub/deliver向hub发送数据包。

接收方设备需要验证签名,通过对方的临时公钥和自己的临时私钥计算出ECDH密钥,并解密该消息。

如果发送设备无法连接到收件人的hub,它会将用收件人的永久公钥对消息进行加密(此加密不是转发安全的,因为它使用了永久密钥),并在本地存储加密消息等待重发。加密消息的目的是避免出现未加密的消息。当连接到收件人的hub成功后,设备会发送此加密消息,并再次对其进行加密(这次具有转发安全性),因此消息是双重加密的。请注意,这不是因为单次加密不够充分,而是因为我们不希望在重新连接成功前无限期地存储未加密的内容。

请注意,通信是设备之间,而不是用户。用户可以(并且建议)持有多个设备,例如笔记本电脑,智能手机和平板电脑,并设置具有冗余(例如2-3)的多签名地址,这些地址需要存储在多个设备上的密钥。当用户需要签署交易时,他会在他的某个设备上启动它。然后,此设备使用加密消息将部分签名的交易发送到其他设备,收集所有签名并发布交易。存储在每个设备上的私钥不应该从该设备删除。当用户用2-3地址替换他的一个设备时,他只使用其他2个设备来更改地址定义,并用新设备的密钥替换旧设备的密钥。

加密消息还可用于设备之间的加密聊天。这些消息是严格的点对点消息,永远不会进入Byteball数据库,并且可以在读取后安全地丢弃。

当用户使用黑字节或其他隐私资产支付时,他们必须发送隐私交易数据,也就需要可以进行加密通信的设备。他们要先知道彼此的设备地址,然后才能知道彼此的支付地址。一旦他们的设备建立了通信,收款人就可以通过聊天消息将他的支付地址发送给付款人。这种支付方案还使得为每个付款生成唯一的支付地址变得容易。商家可以运行聊天机器人,通过文本消息与用户通信。当用户准备付费时,机器人生成新的支付地址并通过聊天消息将其发送给用户。

31. 总结(Conclusion)

我们提出了一种具有防篡改功能的去中心化数据存储系统,可存储包括诸如货币之类的价值型数据。每个新的数据单元都隐含地确认了所有先前单元的存在。类似于《1984》修改过去的记录变得不可能,因为每个新单位也隐含地保护所有以前的单位免于修改和删除。有一种基础货币用于支付在数据库中存储数据的费用。付款金额等于要存储的数据的大小,除此之外,对数据库的访问没有限制。Byteball还可以发布其它资产,并在数据库中跟踪其所有权。在跟踪基础货币和其它资产的付款时,需要选择已知信誉用户见证的历史版本来解决双花问题。交易的最终状态是确定性的。资产可以发布任何管理其可转移性的规则,允许受监管机构发行符合监管要求的资产。同时,通过加密通信,可以直接从付款人向收款人发送隐私交易数据,并发布花费证明以确保相应资产仅花费一次,同时可以向第三方隐藏交易内容。

参考文献(References)

  1. Quoted from Wikipedia https://en.wikipedia.org/wiki/Nineteen_Eighty-Four.
  2. Satoshi Nakamoto. Bitcoin: A Peer-to-Peer Electronic Cash System, https://bitcoin.org/bitcoin.pdf, 2008.
  3. Sergio Demian Lerner. DagCoin, https://bitslog.files.wordpress.com/2015/09/dagcoin-v41.pdf, 2015.
  4. Serguei Popov. The Tangle, http://iotatoken.com/IOTA_Whitepaper.pdf, 2016.
  5. TomHolden. Transaction-Directed Acyclic Graphs, https://bitcointalk.org/index.php?topic=1504649.0, 2016.
  6. Linked timestamping, https://en.wikipedia.org/wiki/Linked_timestamping.
  7. Atomic cross-chain trading, https://en.bitcoin.it/wiki/Atomic_cross-chain_trading.
  8. https://github.com/bitcoin/bitcoin
  9. Gavin Wood. Ethereum: A Secure Decentralised Generalised Transaction Ledger, http://gavwood.com/Paper.pdf.

联系我们

加入ByteBall技术群请添加

QR code