上一篇文章中我们讲述了传统的Pow已经Pos相关的延伸共识算法。而在这篇文章中,我们讲述一下区块链的精髓PBFT以及其延伸算法。
在安全方面,我们给大家科普一下日蚀攻击已经贿赂攻击。并且一步一步带大家复现一下前些日子爆出的区块链整数溢出攻击。希望大家能够从文章中了解到区块链的共识算法,并对相应的攻击手段有所掌握。
为了便于初学者接受,文章选择从基础开始讲起。有需要讨论的同学下方留言即可。
学习区块链的同学都知道“挖矿”这个名词,而根据我们前文讲述的Pow机制我们也了解了其挖矿的原理。然而,比特币的发起人-中本聪为什么选择使用Pow作为其共识算法?而Pow是被用来解决什么样子的问题呢?下面我们就来讲述一下区块链的原始思想——拜占庭将军问题。
根据维基百科:拜占庭将军问题(Byzantine Generals Problem),是由莱斯利·兰波特在其同名论文中提出的分布式对等网络通信容错问题。在分布式计算中,不同的计算机通过通讯交换信息达成共识而按照同一套协作策略行动。但有時候,系统中的成员计算机可能出错而发送错误的信息,用于传递信息的通讯网络也可能导致信息损坏,使得网络中不同的成员关于全体协作的策略得出不同结论,从而破坏系统一致性。拜占庭将军问题被认为是容错性问题中最难的问题类型之一。
而简单来说,下面我们假设一个场景。假设班级里有五个人,其中一个是小霸王(就是块头又大有喜欢欺负别人的熊孩子),而其他四个人饱受那个小霸王的欺负。然而他们四个加起来也许可以打过小霸王。有一天他们约好了一起去找小霸王说理,也就是意味着只要四个人中有一个人不到场那他们就输了,而他们其中任何一个人都可以选择去或者不去,那我们如何让这四个人针对这一个问题达成共识呢?这其实就是著名的拜占庭将军问题,分析一下,它有几个特点:1、敌人很强大;2、作为弱者的几个人实力均等(以上四个小伙伴);3、明确弱者联合起来可以打败强大的敌人;4、只要有一个弱者退出,合作势必失败。简单来说,拜占庭将军问题就是“如何让众多完全平等的节点针对某一状态达成共识”。
而拜占庭将军问题的原型就是在战争中的相关方案。问题的原型如下:拜占庭帝国为了扩张领域需要攻打一个强大的敌人,为此他们派去了10支队伍,然而这个帝国虽然打不过10支队伍,但是也足以抵抗5支军队。然而拜占庭帝国的军队必须要同时开始攻击才能够攻破城池。那么拜占庭如何让他们10支队伍中的半数以上(6+)同时进攻呢?假设通信兵没有任何阻碍,但是对于这些将军来说,他们不确定他们中是否有叛徒,叛徒可能擅自变更进攻意向或者进攻时间。在这种状态下,拜占庭将军们如何才能保证有多于6支军队在同一时间一起发起进攻,从而赢取战斗?
假设团队中没有出现叛徒,那么假设将军A提议早上8点攻击,此时通信兵通知了其他将军,并获得了全部统一,那战役成功。然而其中部分将军不同意并同时提出了其他的提议。(例如早上9点攻击、11点攻击、下午3点攻击等......)。除此之外,由于时间上并不是同步到,不同将军收到的提议的时间也是不同的。上述情况是相对理想一些的,分布式中的时间延迟也是可以理解的。倘若我们将问题复杂一些看待,其中的节点中出现了叛徒(恶意节点),并且他故意捣乱向将军发出了不同的进攻提议(例如在同一了其中一个时间后又同一了其他时间;或者随意发送提议等)。
而叛徒发送前后不一致的进攻提议,被称为“拜占庭错误”,而能够处理拜占庭错误的这种容错性称为「Byzantine fault tolerance」,简称为BFT。
而此时我们就很容易进行解释为何中本聪选择了使用 Pow了作为区块链的共识,我们从上述内容了解到,由于恶意节点在某个时间段的不断作恶行为,导致了节点中存在了大量的错误信息,所以我们引入了工作量证明来增加发送信息的成本,并降低发送信息的速率以达到在一个时间只有一个节点(或是很少)在进行广播。
而运用算法机制已经密码学知识。当将军A提出一个提议后,他就要先发布了一个消息“进攻”并附上了自己的签名“将军A” 。例如【3点进攻+将军A签名】。其他人看到后也要在其后面附上内容,例如将军B看到后回复:【3点进攻 + 将军B签名】。当消息达到6个后他们便可以达成进攻共识进行进攻。
这时也会出现一种情况,将军A发出消息后,可能会有两个或多个将军同时跟上“进攻+签名”的消息,这时,各个节点会严格按照广播的精确时间进行排序,确保一条链的完整性。
而这就是区块链存在的分叉机制,在比特币中我们采用了在最长节点后添加区块的共识来解决分叉问题。
一个分布式节点中假设存在“3f+1”节点,而最大恶意节点数的容忍数量则是f个。而整个算法的流程如下:
1客户端向主节点请求服务
2主节点广播给副节点
3副节点执行内容并反馈
4客户端收到f+1个相同答案的数据
DBFT称为delegated BFT,它是在Castro 和 Liskov提出的“实用拜占庭容错算法”的基础上经过改进的算法。下面我们看一下算法的详细过程:
1获取议员名单:这种共识机制与议会制度十分相似,而每个区块的生成都是在议长的主持下由议会成员共同协商而生成的。
2确定议长:确定议长的算法是当前区块高度+1 再减去当前的视图编号,结果mod上当前的议员人数,结果就是议长的下标。
3议长发起共识:议长在更新完视图编号后,如果当前时间距离上次写入新区块的时间超过了预定的每轮共识的间隔时间(15s)则立即开始新一轮的共识,否则等到间隔时间后再发起共识。
4议员参与共识:当议长发起共识后会广播给议员节点,此时议员收到消息后对消息进行公钥验证,之后进行共识。
5视图更新:一个视图生存周期完成的时候,如果共识还没有被达成,则议员会发送广播请求进入下一个视图周期并重新选择议长,当请求更新视图的请求大于议员数量的2/3的时候,全网达成共识进入下一个视图周期重新开始共识过程。
详细源码分析见:从NEO源码分析看DBFT共识协议
相比其他算法,DBFT在以下方面做了改进:
1将C/S架构的请求响应模式,改进为适合P2P网络的对等节点模式
2将静态的共识参与节点改进为可动态进入、退出的动态共识参与节点
3为共识参与节点的产生设计了一套基于持有权益比例的投票机制,通过投票决定共识参与节点(记账节点)
4在区块链中引入数字证书,解决了投票中对记账节点真实身份的认证问题
根据DBFT的白皮书陈述,总节点数量的要求: n >= 3f + 1 (f为作恶节点)。而在区块链中需要一位议长并且负责提议过程,而议员的工作是对提议进行投票和验证,如果投票数量 >= 2f + 1,就代表该区块最终被确定下来。即投票数是恶意节点的两倍多。在区块链系统的正常运转过程中可能存在议长离线的情况。此时议员需要重新选举议长。然而此时威胁就已经存在了。
我们知道区块链作为分布式系统其信息在传播的过程中肯定存在延迟的现象。例如一些议员收到2f + 1个投票时,部分节点未必也能收到2f+1这么多的投票消息。而节点与节点之间并不知道对方是否收到了相应的消息。此时当部分议员收到、部分议员没有收到消息时,超时的一方就会发起change-view过程,而此过程被用于重新选举议长。此时就会存在两方信息不匹配的情况(会去不同的区块上添加消息,即分叉为两个部分)。
单单是网络延迟就会导致这个问题产生,假设有黑客从中作乱呢?
当一个作恶的人或者黑客混入了小蚁的记帐人节点时,那黑客就可以故意改写源代码,在快要超时重选议长的时候,黑客再发起提议,此时就有很大的概率复现上面说的分叉现象。
日蚀攻击是对区块链的一种网络级攻击,它与我前文层提到的P2P网络相关连,并且属于P2P网络中的一种攻击。这种攻击的对象相对比较丰富,在比特币与以太坊中均存在此类攻击。
简单来说,在日蚀攻击中,攻击者控制所有来自以目标受害者的节点及将要连接目标受害者的节点。这样,攻击者就可以防止受害者获得关于网络其他部分的完整信息。而如果能够成功利用日蚀攻击,那么攻击者只需40%的算力就可以达到51%攻击的效果。
攻击方法如下:
1攻击者通过某种方法将正常的比特币节点的输出连接连接到攻击者控制的节点,并使用攻击节点连接器输入节点(全部连满)。而我们知道,在比特币节点的本地中存有新、旧两张节点地址的表格。而区块链节点每次建立的输出连接均是在这两张表中寻找到最新的一个节点。而攻击者要做的就是不断的与该节点进行连接,以便不断刷新表格以达到控制的目的。
2攻击者使用DDos使目标节点宕机并重启,这样就控制了该节点的输入输出,也就变相的控制了该节点。
我们知道,比特币设计之初只能生产8个输出的TCP连接,而以太坊需要13个。同时以太坊使用了点对点加密的安全通道,比特币却没有。所以我们一定会下意识的以为以太坊在日蚀攻击中肯定是比比特币平台安全的。但是事实不是这样。
对于比特币来说,攻击者可以控制足够数量的 IP 地址来垄断所有受害节点之间的有效连接。然后攻击者可以征用受害者的挖掘能力,并用它来攻击区块链的一致性算法或用于 “重复支付和私自挖矿”。
对于以太坊,攻击者可以垄断受害节点所有的输入和输出连接,从而将受害节点与网络中其他正常节点隔离开来。 然后攻击者日食攻击可以诱骗受害者查看不正确的以太网交易细节,诱骗卖家在交易其实还没有完成的情况下将物品交给给攻击者。
然而以太坊的点对点网络中的节点由其公钥所标识。也就是说,以太坊的版本允许用户在同一个IP下运行无限数量的节点,每个节点都有一个不同的公钥。而比特币相对于的一个IP地址只能对应一个节点。这也就是为何曾经国外实验室使用了两天PC机就可以模拟日蚀攻击的原因。
而这种攻击不同于上述的P2P网络类型的攻击。此类攻击运用了区块链中的“社会学”。简单来说,恶意节点没有必要在Pow机制下为了使自己能做恶而疯狂提升算力。反而,我可以在一个区块链协议之外的贿赂者通过一个条件来收购代币或者挖矿算力,从而达到攻击原有区块链的目的。
举例一个简单的投票机制。假设所有参与者均可以投票0或者1。而机制规定只有所有节点全部为0或者1才给与奖励。此时有下图:
即当自己投反对点1时,攻击方可以基于你贿赂,也就是给与你E价值的奖励。而原本全为0的时候,所有节点均处于纳什均衡(博弈论知识),而当其中1个人被贿赂的时候,这个均衡还不会被打破。倘若攻击者给所有的节点均进行了贿赂,则所有人均由原来的P到了现在的P+E。而吃亏的是区块链系统。
下面我们讲述一下利用区块链合约中的系统漏洞导致的攻击行为。
事件回顾:
4月25日早间,火币Pro公告,SMT项目方反馈今日凌晨发现其交易存在异常问题。该漏洞代理的直接经济损失高达上亿元人民币,间接产生的负面影响目前无法估量。
而这个漏洞取源于整数溢出问题。
我们来看一个例子:
pragma solidity ^0.4.10;
/**
这是一个测试整数类型上溢和下溢的例子
*/
contract Test{
// 整数上溢
//如果uint8 类型的变量达到了它的最大值(255),如果在加上一个大于0的值便会变成0
function test() returns(uint8){
uint8 a = 255;
uint8 b = 1;
return a+b;// return 0
}
//整数下溢
//如果uint8 类型的变量达到了它的最小值(0),如果在减去一个小于0的值便会变成255
function test() returns(uint8){
uint8 a = 0;
uint8 b = 1;
return a-b;// return 255
}
}
而根据上面的内容,我们知道这中漏洞就是由于检测不严格而导致的,这也会在现实的协议中产生致命的危害。
而在曾经出现过严重问题的SMT合约中,整数溢出问题就给了其一个大的教训。
比如在源码transferProxy()代码中:
function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt,
uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){
if(balances[_from] < _feeSmt + _value) revert();
uint256 nonce = nonces[_from];
bytes32 h = keccak256(_from,_to,_value,_feeSmt,nonce);
if(_from != ecrecover(h,_v,_r,_s)) revert();
if(balances[_to] + _value < balances[_to]
|| balances[msg.sender] + _feeSmt < balances[msg.sender]) revert();
balances[_to] += _value;
Transfer(_from, _to, _value);
balances[msg.sender] += _feeSmt;
Transfer(_from, msg.sender, _feeSmt);
balances[_from] -= _value + _feeSmt;
nonces[_from] = nonce + 1;
return true;
}
我们知道_feeSmt
与_value
均是由用户传入的变量参数。而我们看第一行代码,if(balances[_from] < _feeSmt + _value) revert();
当账户余额小于_feeSmt + _value
时会跳出if语句。也就是无法进行下一步的转账操作。然而我们根据上面的内容知道,加入_feeSmt = 9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
并且value = 6000000000000000000000000000000000000000000000000000000000000001
此时两者相加使数字溢出为0。
简单的说由于溢出而使if语句没有执行,直接跳过了检查而使下面的语句执行成功。
详细内容可以参考整数溢出安全漏洞
这也就与传统的web漏洞非常相似,例如PHP中的弱类型等等均可以看做类似的利用模式。
本稿为原创稿件,转载请标明出处。谢谢。