科普 | 叔块验证与网络安全性

2024-02-09 14:20

本文主要是介绍科普 | 叔块验证与网络安全性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

来源 | 以太坊爱好者

责编 | 晋兆雨 

头图 | 付费下载于视觉中国 

在一个共识协议中,最简单的错误也会导致灾难。

我准备开一个系列,讲解我在 go-ethereum(Geth 客户端)(以太坊协议的正式 Go 语言实现)中发现的 Bug,本篇是第一篇。虽然阅读这系列文章不需要你对 Geth 有多深的理解,但懂得以太坊协议是怎么运行的,会很有帮助。

这篇文章讲的是 Geth 客户端叔块验证程序中的一个 bug,传入一个专门构造的叔块后,该程序的行动是错误的。如果该漏洞被利用,会导致 Geth 节点和 Parity 节点发生分叉。


区块与叔块

每一条区块链都会在运行中产生一条大家都认可的主链(canonical chain);而主链的辨识方法也是协议定义好的:最长的链,或者总工作量最大的链,等等。不过,网络的延迟意味着,可能会有两个区块在同一时间生成。那么,只有其中一个能最终成为主链的一部分,而另一个则必须被抛弃。

某些区块链协议,比如比特币,会完全无视掉这些被落败的区块,让它们成为主链的 “孤块(orphan)”。另一些区块链协议,比如以太坊,依然会奖励挖出这些落败区块并努力传播它的矿工。在以太坊的语境中,这些仍然成为了主链一部分的孤块叫做 “叔块(uncle block)”。

但一个块要成为一个有效的叔块,还需满足一些条件:(1)该区块本身的所有内容都必须是有效的(根据正常的共识规则);(2)区块与其意图标记的叔块,两者的块高度相差不超过 6(一个叔块挖出后,只有在未来的 6 个区块以内被标记为叔块,才是有效的)。但是,这里有个例外:虽然正常的区块的时间戳间隔不应超过 15 秒,但叔块则无此限制。


关于整数的一个小插曲

大部分的编程语言都有依赖于平台的整数(platform-dependent integer)和定长整数(fixed-width integer)两种概念。依赖于平台的整数可能是 32 位或者 64 位的(等等),取决于程序所在的编译平台。在 C/C++ 和 Go 语言中,你可能会使用 uint,而在 Rust 中,你会使用 usize

但是,有些时候程序员想要保证其程序变量可以存储 64 位的数据,即使程序运行在 32 位的平台上。这时候,程序员可以使用定长的整数类型。在 C/C++ 中就是 uint64_t,Go 语言是 uint64,而在 Rust 中是 u64

使用这些语言自带的整数型的好处是,它们都具备最高优先级(first-class citizen),使用起来都非常简单。来看看这个支持 64 位整数的 Collatz Conjecture 实现:

func collatz(n uint64) uint64 {    if n % 2 == 0 {      return n / 2    } else {      return 3 * n + 1    }}

但是,这个实现有个瑕疵:它不支持超过 64 位的整数。因此,我们需要 大整数(big integers)。大多数语言都支持大整数,要么是用自带的标准库(比如 Go 的 big.Int),要么是通过外部代码库(比如 C/C++ 和 Rust 都是如此)。

难搞的是,使用大整数有一个很大的缺点:很不灵活。我们用支持任意大整数类型的 Collatz Conjecture 把上面的程序再实现一遍:

var big0 = big.NewInt(0)var big1 = big.NewInt(1)var big2 = big.NewInt(2)var big3 = big.NewInt(3)
func collatzBig(n *big.Int) *big.Int {  if new(big.Int).Mod(n, big2).Cmp(big0) == 0 {    return new(big.Int).Div(n, big2)  } else {    v := new(big.Int).Mul(big3, n)    v.Add(v, big1)    return v  }}显然,64 位的版本既好写,又好读。所以,不意外的是,程序员都会尽可能使用简单的整数型。


例外 vs. 现实

在以太坊协议中,可以预期大部分数据都不会超过 256 位,虽然某些整数字段的长度是任意的,无法有任何预期。重点是,区块的时间戳 Hs 也定义为一个 256 位的整数。

- 以太坊黄皮书,P6 -

Geth 团队尝试通过验证叔块的时间戳是小于 2^256 - 1 的整数来满足这个定义。再次提醒,叔块的出块时间不受任何限制。

// Verify the header's timestampif uncle {    if header.Time.Cmp(math.MaxBig256) > 0 {        return errLargeBlockTime    }} else {    if header.Time.Cmp(big.NewInt(time.Now().Add(allowedFutureBlockTime).Unix())) > 0 {        return consensus.ErrFutureBlock    }}

- 来源 -

但是,接下来的代码却要将区块时间戳强制调整为一个 64 位的整数,以计算该区块的正确难度。

// Verify the block's difficulty based in it's timestamp and parent's difficultyexpected := ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)- 来源 -

如果 Parity 也是一样的做法,那也不会有什么大问题。但是,Parity 的时间戳在 2^64 - 1 就已到达上限,不会再溢出了。

let mut blockheader = Header {    parent_hash: r.val_at(0)?,    uncles_hash: r.val_at(1)?,    author: r.val_at(2)?,    state_root: r.val_at(3)?,    transactions_root: r.val_at(4)?,    receipts_root: r.val_at(5)?,    log_bloom: r.val_at(6)?,    difficulty: r.val_at(7)?,    number: r.val_at(8)?,    gas_limit: r.val_at(9)?,    gas_used: r.val_at(10)?,    timestamp: cmp::min(r.val_at::<U256>(11)?, u64::max_value().into()).as_u64(),    extra_data: r.val_at(12)?,    seal: vec![],    hash: keccak(r.as_raw()).into(),};- 来源 -

也就是说,如果有个恶意的矿工,在所出的区块里纳入了一个叔块,该叔块的时间戳是 584942419325-01-27 07:00:16 UTC,也就是 unix 时间 2^64,那么 Geth 会用 Unix 时间 0 来计算难度,而 Parity 会用 unix 时间 2^64 - 1 来计算难度。结果会不一样,所以其中一个客户端会在验证叔块后从主链分裂出去。

Geth 团队在 PR 19372 中修复了这个 Bug,切换到所有时间戳都使用 unit64 。


结论

每一种参与同一个共识协议的客户端,都必须有同样的行动,因此,一些看起来完全无害的操作可能正是导致一半网络相互隔离的罪魁祸首。

这同样也表明,要发现一个影响巨大的 bug,你并不需要很高的技术水平。如果你对这些东西感兴趣,最好的办法就是立即动手。

下一篇文章,我们会讨论 Geth 客户端如何存储构成以太坊的数据,以及一个手段高明的攻击者可以如何规划一个定时炸弹,在引爆时导致链硬分叉。

更多阅读推荐

  • C罗捧回欧洲杯首个区块链奖杯,但区块链+体育不止于此

  • 从历次升级看以太坊协议的演化

  • V神设计理念公布,什么是以太坊的初心?

  • 绝密邮件曝光!看乔布斯如何拯救濒危的苹果?

  • 超视频化到来,你能看到什么?

这篇关于科普 | 叔块验证与网络安全性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/694450

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)

《Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)》本文介绍了如何使用Python和Selenium结合ddddocr库实现图片验证码的识别和点击功能,感兴趣的朋友一起看... 目录1.获取图片2.目标识别3.背景坐标识别3.1 ddddocr3.2 打码平台4.坐标点击5.图

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如