区块链:最小可行区块链原理解析2

多方转移和验证

接下来,Bob无意中发现John有一枚邮票,他实在很喜欢。他告诉John他和Alice在使用的安全分类账簿,并问他是否愿意做个交易,Bob把Alice欠他的余额作为支付手段转移给John——即Bob从John那儿获得邮票,Alice之前欠Bob的金额将变成她欠John的。John同意了,但现在他们有个问题。Bob如何能以安全和可验证的方式把他的余额“转移”给John?经过一番协商,他们想出一个巧妙的计划:

区块链:最小可行区块链原理解析2

Bob按照与之前相同的步骤创建了一个新交易,不过他先计算出他想要转移的加密交易的SHA-256校验和(一个唯一的指纹),然后将校验和嵌入新收据中“是什么”一栏。事实上,他在将之前与Alice的交易与新的转移收据链接起来,这样就把它的价值转移给了John。

为了保持事物的简单性,我们假定所有的转移都会“消费掉”被转移交易的全部价值。要把这个系统扩展到使部分转移成为可能并不难,但此时没有必要考虑得那么复杂。

随着新交易到位,John为了安全起见,做了一个加密分类账簿的副本(现在有三个副本)并运行了一些检查来验证它的完整性:

1.John提取了Alice和Bob的公钥,验证前三个交易的真实性。

2.John证实了Bob转移的是一个“有效”交易:

  • 待转移交易的地址是Bob。
  • Bob此前没有把这个交易转移给任何其他人。

如果所有检查都通过了,他们就完成交易,我们可以通过遍历分类账簿来计算新的余额:Bob有一个净零数余额,Alice有2个chroma的借额,John有2个chroma的贷额(由Alice提供)。这样John现在就可以把他的新分类账簿拿给Alice并要求她支付,即使Alice没有出席交易,也没有问题:

  • Alice可以用Bob的公钥核实新转移交易的签名。
  • Alice可以核实转移的交易是对她和Bob一个有效交易的引用。

以上转移和验证过程是系统一个了不起的属性!注意要让它全部能工作,我们需要两个使能技术:一个是公钥基础设施的运用,使数字签名验证成为可能;另一个是收据账簿,使我们能够查看完整的交易记录以验证余额并链接先前的交易来进行转移。

John和Bob对这个巧妙的解决办法很满意,然后两人分头回家:Bob带着新邮票,John有了新的分类账簿。表面上看一切完美,但是他们刚刚把自己暴露在了一个极具挑战性的安全问题之下……你发现了吗?

重复消费和分布式一致性

在与John完成交易后不久,Bob意识到他们刚刚在他们的系统中引入了一个严重的漏洞,如果他迅速行动,就可以利用这个漏洞:Bob和John都更新了他们的分类账簿来包括新的交易,但是Alice和其他任何人都不知道交易已经发生。结果是,没有什么能阻止Bob接近网络中的其他人,给他们展示旧的账簿副本,而旧的账簿副本里没有他和John的交易!如果Bob说服他们进行交易,就像他和John做的那样,他就可以“重复消费”同一个交易,想进行多少次都可以!

区块链:最小可行区块链原理解析2

当然,一旦多人拿着新的分类帐簿要求Alice支付,欺诈行为将被检测到,但这已经无济于事了——Bob已经带着战利品跑掉了!

只有两个参与者的时候,不可能受到双重消费攻击,因为要完成交易,你要验证并同时更新两个分类账簿。 因此所有分类账簿始终保持同步。然而当我们再添加另外一个参与者时,我们就引入了各参与者之间账簿不完全和不一致的可能性,这就使双重消费成为可能。

在计算机科学语言中,双方分类账簿具有“强一致性”,超过两方的分类账簿则需要某种形式的分布式一致性以解决双重消费的问题。

这个问题最简单的可能的解决方案是要求分类账簿中列出的各方都必须在每个新交易发生时都在场,以便每个人可以同时更新他们的账簿。这个策略对小型的群组有效,但不能扩展到有大量参与者的情况。

分布式一致性网络的要求

我们来设想一下,我们想要将分类账簿扩展到全世界所有集邮者,这样任何人都可以用一种安全的方式交易他们喜欢的邮票。显然,由于地理位置,时区和其他限制,要求每个参与者在每个交易登记的时候都在场是不可能实现的。我们能建立一个不需要每个人都在场批准的系统吗?

1.地理位置算不上一个真正的问题:我们可以把交流转移到线上。

2.时区问题可以通过软件解决:我们不需要每个人手动更新分类账簿。相反,我们可以建立一个软件,它能在每个参与者的计算机上运行并代表他们自动接收、批准以及向分类账簿添加交易。

事实上,我们可以建立一个点对点(P2P)网络,负责分发新的交易并获得每个人的批准! 但很可惜,说起来容易做起来难。例如,虽然P2P网络可以解决我们的地理位置和时区问题,但试想即便只有一个参与者离线,会出现什么情况? 我们是不是要阻止所有交易,直到他们再次上线?

注意,“如何”构建P2P网络本身就是一个庞大的课题:协议和信令,穿越防火墙和非授权终端系统(NAT),自我启动,优化传播更新的方式,安全性等。 也就是说,构建这样一个网络的底层机制也远超出我们讨论的范围……我们把它作为一个练习留给读者。

区块链:最小可行区块链原理解析2

原来分布式一致性的问题在计算机科学中已经被深入研究过,并且已经提出了一些有希望成功的解决方案。例如,两阶段提交(2PC)和Paxos都使这样一种机制成为可能,即我们只需要参与者的大多数法定人数(50%以上)就能安全地提交新的交易:只要大多数人已经接受交易,就能保证群组中剩下的人最终汇合在同一个交易历史。

即便如此,单单有2PC或Paxos是不够的。比如,在每天都有新参与者加入而其他人不预先通知就消失的情况下,2PC或Paxos如何知道我们P2P集邮者网络中的参与者总数?如果有一个先前的参与者离线,他们是临时还是永久离线? 相似地,还有另一个我们必须考虑的更具挑战性的“Sybil攻击”:没有办法阻止一个恶意参与者创建许多档案,在我们的P2P网络中获取不公平的投票权份额。

如果系统中的参与者数量是固定的,并且已经验证他们的身份真实有效(也就是说,这是一个可信网络),那么2PC和Paxos都会工作得很好。唉,但我们不断变化的集邮者P2P网络并不是这样的情况。我们走进死胡同了吗? 嗯,并不尽然……

这个问题有个明显的解决方案是从问题陈述中消除“分布的”部分。我们可以不建立一个P2P分布式系统,而是建立一个所有集邮者的全局注册表,记录他们的帐户信息,对他们进行验证并(尝试)确保没人能通过创建多个身份作弊,最重要的是,保证有一个共享的分类账簿副本!具体来说,我们可以建立一个网站,这些交易在网站上进行,网站将在它的集中式数据库里记录所有的交易,以此确保交易的完整性和正确排序。

以上是一个实用的解决方案,但我们得承认,它不尽如人意,因为它迫使我们失去了分类账簿系统点对点的性质。它将所有的信任置于一个单一的集中式系统,这就带来了一组全新的问题:什么是系统的正常运行时间,安全性和冗余; 谁来维护系统,他们维护系统的动因是什么; 谁有管理访问权限,等等。集中式带来了它自己的一系列挑战。

让我们回顾一下在P2P设计中遇到的一些问题:

  • 确保每个参与者始终保持更新状态(强一致性系统)会产生很高的协调成本,影响可用性:如果单个点不可达,整个系统都无法提交新交易。
  • 在实践中,我们不知道P2P网络的全局状态:参与者人数,个体是暂时离线还是决定离开网络等。
  • 假设我们可以解决上述限制,系统仍然可能受到Sybil攻击,恶意用户可以伪造许多身份行使不公平的投票权。

不幸的是,解决上述所有限制是不可能的,除非我们放松一些要求: CAP定理告诉我们,我们的分布式系统不能有很强的一致性,可用性和分区容忍性。因此在实践中,我们的P2P系统必须在(更)弱一致性的假设下操作并克服它可能带来的影响:

  • 我们必须接受一些分类账簿不同步(至少是暂时不同步)。
  • 系统最终必须收敛于所有交易的整体序(线性一致性)。
  • 系统必须以可预测的方式解决分类账簿冲突。
  • 系统必须强制执行全局不变量——例如,没有重复消费。
  • 系统应该免受Sybil和类似的攻击。

保护网络免受Sybil攻击

在分布式系统中实现一致性,比如通过对每个参与者的投票计数,会出现很多关于各节点“投票权”的问题:允许谁参与,某些节点是否有更多的投票权,是否每个人都平等,以及我们如何强制执行这些规则?

为了保持简单,我们假定每个人的投票是平等的。第一步,我们可以要求每个参与者用私钥在他们的投票上签名,就像在他们的交易收据上签名一样,并将投票传播到他们的节点上—— 在投票上签名确保了别人不能代表他们投票。然后我们可以制定一个规则,只允许提交一票。如果同一个钥匙签名了多个投票,那么所有的投票都作废——已经下定决心! 到目前为止还好,现在难的部分来了……

区块链:最小可行区块链原理解析2

最开始我们怎么知道允许哪个特定的节点参与?如果只需要一个独特的私钥来签名投票,那么恶意用户可以简单地生成无限的新密钥充斥网络。根本问题是,当生成和使用伪造身份很便宜时,任何投票系统都很容易被颠覆。

为了解决这个问题,我们需要使提交投票的过程变得“昂贵”。或者提高生成新身份的成本,或者提交投票的过程必须产生足够高的成本。为了让问题更明确,我们来想几个现实世界的例子:

  • 你在当地政府选举中投票时,会要求你出示身份证件(例如护照),而伪造身份证件的成本很高(希望如此)。理论上,没什么能阻止你生成多个伪造的身份证件,但如果成本足够高(伪造的货币成本,被抓的风险等),那么运行Sybil攻击的成本将远大于其收益。

  • 或者,假设提交投票给你带来了一些其他成本(例如支付费用)。如果成本足够高,那么再次的,运行大规模Sybil攻击的障碍也增强了。

注意,上述例子都不能完全“解决”Sybil攻击,但它们也不需要被完全解决:只要我们将攻击的成本提高到大于成功破坏系统所能得到的值,那么系统就是安全的,会按照预期运行。

注意,我们所使用的“安全”的定义是很宽松的。系统仍然会受到操纵,确切的投票计数会受到影响,但关键是恶意参与者不能影响最终的结果。

参与所要求的工作量证明

任何用户都可以通过生成新的私钥—公钥对来轻易地(并且花很少的钱)在我们的P2P网络中生成新的“身份”。同样,任何用户都可以用他们的私钥签名投票并将其发送到P2P网络——这也很便宜,我们的收件箱中大量的垃圾邮件清楚地说明了这一点。 因此,提交新的投票很便宜,恶意用户可以轻易地用尽可能多的投票淹没网络。

但是,如果将以上其中一个步骤变得昂贵,使你不得不消耗更多的精力、时间或金钱,情况会怎样呢? 这就是需要工作量证明背后的核心思想:

1.工作量证明步骤对于发送者来说应该是“昂贵的”。

2.他人验证工作量证明的步骤应该是“便宜的”。

这样一种方法有很多种可能的执行方式,但是为了达到我们的目的,我们可以再次使用之前遇到的密码散列函数的属性:

区块链:最小可行区块链原理解析2

1.很容易计算任何给定消息的散列值。

2.生成具有给定散列值的消息很昂贵。

我们可以在我们的系统中施加一个新规则,要求每个签名投票必须具有以特定子串开始的散列值,即需要部分散列冲突,比如两个零的前缀。如果这看起来完全是任意的,那是因为它确实是任意的——跟着我的思路。我们通过几个步骤来看看这是如何生效的:

1.我们假定一个有效的投票陈述是个简单的字符串:”I vote for Bob” (“我投票给Bob”)。

2.我们可以用同样的SHA-256算法来为我们的投票生成一个散列值。sha256(“I vote for Bob”) → b28bfa35bcd071a321589fb3a95cac…

3.生成的散列值无效因为它没有以我们要求的两个零子串开头。

4.我们修改一下投票陈述,附加一个任意字符串再试一下: sha256(“I vote for Bob – hash attempt #2”) → 7305f4c1b1e7…

5.生成的散列值也不满足我们的条件,我们更新值,一次又一次地尝试…… 155次尝试之后我们最终得到了:

  • sha256(“I vote for Bob – hash attempt #155”) → 008d08b8fe…

上述工作流程的关键属性是,每次我们修改完输入,加密散列函数(在这种情况下是SHA-256)的输出是完全不同的:当我们增加计数时,前一次尝试的散列值并不能透露下一次尝试所得到的散列值的任何信息。因此,生成有效投票并不仅仅是个“难的问题”,我们可以把它比喻成彩票,每次新尝试都会给你一个随机的输出。同时我们也可以通过更改所需前缀的长度来调整彩票的赔率:

1.SHA-256校验和中的每个字符都有16个可能的值: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f.

2.为了生成有两个零前缀的有效散列,发送者平均需要256 (162)次尝试。

3.将要求变为5个零平均会需要1,000,000 (165) 多次尝试……关键是,我们可以轻易提高成本,让发送者找到一个有效散列需要耗费更多CPU周期。

我们可以在一个现代CPU上计算多少个SHA256校验和?它的成本取决于信息大小,CPU 架构和其他变量。如果你对此感到好奇,可以打开控制台,运行一个基准测试程序:

$> openssl speed sha.

最终结果是,生成有效投票对于发送者来说是“昂贵的”,但对于接收者验证仍然是微不足道的:接收者散列交易(一次运算)并且核实校验和中包含所需的散列冲突前缀……太好了,那么这对我们的P2P系统有什么用呢?上述工作证明机制使我们能够调整提交投票的成本,从而使破坏系统的总成本(即假冒足够多的有效投票来确保特定结果)高于攻击系统能够获得的价值。

注意,“生成消息的高成本”在很多其他环境中是个有用的属性。例如,垃圾邮件能够运作恰恰是因为生成信息特别便宜。如果我们可以提高发送电子邮件的成本——例如,要求工作量证明签名——那么我们可以通过使成本高于利润来打破垃圾邮件的商业模式。

下一篇文章我们将会介绍《区块链:最小可行区块链原理解析3》