第一章  概述

比特币(Bitcoin)的概念最初由中本聪在2009年提出,是一种P2P形式的数字货币,可以用来兑换成大多数国家的货币。

区块链诞生自中本聪的比特币,自2009年以来,出现了各种各样的类比特币的数字货币,都基于公有区块链。

由于以比特币为代表的数字货币具备兑现的能力,与数字货币相关的恶意事件(如勒索病毒、恶意浏览器脚本挖矿以及IoT僵尸网络挖矿等)逐渐增多。因此,对数字货币的节点进行识别,有助于提供威胁情报,减缓和预防相关恶意事件的影响。

本文将以比特币为例,就节点识别做相关的介绍。第一章为概述,介绍比特币的概念以及节点识别的思路。第二章以新节点加入比特币网络为例,介绍比特币网络的通信过程。第三章介绍比特币的通信协议。第四章介绍了本文所述方法的节点识别实现过程。第五章对节点识别的结果做了分析。第六章为小结。

1 什么是比特币?

比特币是一系列概念和技术,构成了数字货币生态系统的基础。它是一个分布式的点对点系统。因此,没有“中央”服务器或控制点。比特币是通过称为“挖矿”的过程创建的,这涉及到在处理比特币交易时竞争找到数学问题的解决方案。比特币网络中的任何参与者(即使用设备的任何人)都可以运行完整的比特币协议栈,利用其计算机的处理能力来验证和记录交易,这样的节点称之为“矿工”,而验证和记录交易的过程,称之为“挖矿”。平均每10分钟,比特币矿工能够验证过去10分钟的交易,作为奖励,将获得全新的比特币。从本质上讲,比特币采矿分散了中央银行的货币发行和结算功能,取代了对任何中央银行的需求。

2 比特币节点识别的方式

对于特有协议的识别,一般的思路为全网扫描协议相关端口,模拟协议的通信方式,如果目的节点响应的内容符合协议的格式,则认为识别出开放该协议的节点。在比特币协议中,比特币常用的端口为8333,因此,可以全网扫描8333端口,从而确定大部分的比特币节点。但这种方式存在一些问题:1)端口扫描比较耗费时间。2)存在一部分比特币节点,没有使用8333端口,端口扫描难以对这部分节点进行识别。

更进一步,比特币网络为P2P网络,比特币节点均具备发现其他节点的能力(比特币协议中的“getaddr”消息,具有返回该节点周围活跃节点的功能),因此,从一个已知的比特币节点出发,通过模拟比特币协议的节点发现过程,即可获取其相邻节点的地址,之后,对得到的节点,依次通过模拟节点发现过程递归遍历,最终即可遍历整个比特币网络。该方法的优势在于,仅对比特币网络中的节点进行遍历,扫描速度快。因此,本文将对这种方式进行介绍。

第二章  比特币网络的通信过程

比特币网络基于TCP连接,默认端口8333,在套接字之上直接将数据打包成二进制流进行传输。

若一个新的比特币节点,希望加入到比特币网络,总体上流程分为两步,1)获取到当前比特币网络中网络状况正常的节点列表(获取种子节点列表)。2)运行比特币的协议栈,与比特币网络中的其他节点通信。

本章将以获取一个节点周围的缓存节点为例,对比特币节点之间的通信过程做相应的说明。

获取种子节点列表

如图2.1所示,比特币的Wiki[4]上提供了8种发现种子节点IP列表的方式,本文采用了第三种方式(比特币网络中存在一部分节点使用IPv6的地址,因此,通过该方式获取种子节点列表时,会获取到一部分使用IPv6地址的节点,本文仅对使用IPv4地址的节点做相关说明),即通过DNS请求获取IP地址。

图2.1 获取种子节点IP列表的方式

截至2017年12月,比特币源码的chainparams.cpp[5]文件中,一共包含了6个DNS域名(见表2.1),可以用于新节点加入比特币网络时获取种子节点列表。

表2.1 比特币获取种子节点IP的DNS域名

seed.bitcoin.sipa.be

dnsseed.bluematt.me

dnsseed.bitcoin.dashjr.org

seed.bitcoinstats.com

seed.bitcoin.jonasschnelli.ch

seed.btc.petertodd.org

比特币节点的通信流程

在完成2.2节的步骤后,正常情况下,可以获取到140个左右比特币节点的地址(IPv4),之后即可通过比特币协议进行通信,比特币节点的通信流程如图2.2所示。

图2.2 比特币节点的通信过程

节点A做为通信的发起者,首先与节点B完成握手的过程。需要着重说明的是,节点之间进行握手时,若一方发送的握手包有问题,则另一方节点不做任何回应。握手无误后,节点B将向节点A发送大量的数据包,如获取节点A的头、区块信息等数据包。

若节点A希望获取到节点B的信息(以获取节点B周围的缓存节点为例),则发送相应的消息即可(图2.2中,节点A发送了一个getaddr消息以获取节点B周围的缓存节点),节点B收到后,将继续向节点A发送消息,以获取自己希望获取到的信息,一段时候后,回应节点A发送消息的结果(图2.2中即为最后的addr消息)。

 2.2.1 握手过程

比特币节点之间的握手过程,建立在已经完成TCP连接的基础上。握手的发起者会向目标节点发起TCP连接,默认为8333端口(部分节点不使用8333端口),连接建立成功后,双方需要根据比特币协议的规定,完成握手的过程,主要是确认双方版本、IP地址、端口等信息。

如图2.3所示,节点A为握手的发起者,希望与节点B进行握手,首先向B节点发送一个version消息(含有自己的协议版本等),节点B收到节点A的version消息后,会对数据包进行验证,若验证无误,则会向A回应自己的version消息,否则,节点B将忽略掉节点A的消息。节点B在回应了自己的version消息之后,会再回应一个version ack的消息。节点A在收到B节点的version ack消息后,向节点B回应一个version ack,握手过程即正常结束。

图2.3比特币节点握手过程

握手期间,version消息包含的内容如图2.4所示。有协议的版本号、提供的服务、时间戳、双方的IP地址、客户端描述以及自身当前区块的高度。

图2.4 握手包含有的数据(部分)

2.2.2 获取节点周围缓存节点

完成正常的握手后,节点识别的关键,即为获取对方节点周围活跃的节点地址。比特币协议中规定了用于获取一个节点周围活跃的节点getaddr消息,在完成握手后,通过向对方节点发送getaddr消息,即可获取其周围活跃节点,通信过程如图2.5所示。

图2.5getaddr过程时序

第三章  通信协议

比特币的协议是在TCP层之上,直接封装了自己的协议,因此,通信时,直接通过套接字,将协议转为二进制数据流进行传输。比特币网络中,连接均使用TCP的方式。本章将对比特币的通信协议做简单的描述,第四章识别的实现,是在本章的基础上完成的,对比特币节点的识别,需要实际上是对通信数据包的过滤和解析,第四章的实现中,对数据包进行处理,实际上是根据协议中规定的数据包的长度以及特定的字段,进行过滤和打包。

数据包结构

比特币协议中,数据包的结构如表3.1所示(payload长度可变,用“?”表示),由magic、command、length、checksum以及payload组成。

表3.1 比特币节点数据包格式

Field  Size

Description

Data  type

Comments

4

magic

uint32_t

Magic value indicating  message origin network, and used to seek to next message when stream state is  unknown

12

command

char[12]

ASCII string identifying  the packet content, NULL padded (non-NULL padding results in packet rejected)

4

length

uint32_t

Length of  payload in number of bytes

4

checksum

uint32_t

First 4  bytes of sha256(sha256(payload))

?(variable)

payload

uchar[]

The actual  data

比特币的网络有主网络,也有用于测试的网络,通过magic即可区分各个网络,如表3.2所示

表3.2 比特币主网络与测试网络的magic

Network

Magic  value    

Sent  over wire as

main

0xD9B4BEF9

F9  BE B4 D9

testnet

0xDAB5BFFA

FA  BF B5 DA

testnet3

0x0709110B

0B  11 09 07

namecoin

0xFEB4BEF9

F9  BE B4 FE

如表3.3所示(payload值可变,用“?”表示),为一个比特币的主网络中的数据包结构。

表3.3 比特币主网络中数据包的示例

f9beb4d9

-magic  

76657273696f6e0000000000

-command  “version”

55000000

--  lenth. Payload lenth is 85

0d0fe5c6

--  checksum

?(variable)

Payload

version包

如图3.1所示,为比特币节点在通信的握手过互发的一个version消息数据包。      

图3.1 version包

version数据包由数据包的头和payload两部分组成。数据包的头包含magic、command、payloadlength和checksum,如表3.4所示。

表3.4 version包的头

F9 BE B4 D9                                                                    

Main  network magic bytes

76 65 72 73 69 6F 6E 00 00 00 00 00

"version" command

64 00 00 00                                                                    

Payload is  100 bytes long

3B 64 8D 5A                                                                    

payload  checksum

version数据包的payload如表3.5所示,主要包含了协议的版本号、节点提供的服务、时间戳,接收者的地址、发送者的地址、随机生成的id(用于区分不同的连接)、客户端软件的描述以及该节点当前最新的块。

表3.5 version包的payload

62 EA 00 00

- 60002 (protocol  version 60002)

01 00 00 00 00 00 00 00

- 1 (NODE_NETWORK  services)

01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00

- Tue Dec 18 10:12:33  PST 2012

01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00

- Recipient address  info - see Network Address

01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00

- Sender address info -  see Network Address

3B 2E B3 5D 8C E6 17 65

- Node ID

0F 2F 53 61 74 6F 73 68  69 3A 30 2E 37 2E 32 2F

-  "/Satoshi:0.7.2/" sub-version string (string is 15 bytes long)

C0 3E 03 00

- Last block sending  node has is block #212672

version包payload中不同字段的类型和长度非常重要,对于比特币节点的识别,均是根据数据包字段的类型和长度进行过滤,类型和长度如表3.6所示(user_agent为客户端的描述,长度可变,用“?”表示)。

表3.6 version包不同字段的类型和长度

Field  Size

Description

Data  type

Comments

4

version

int32_t

Identifies protocol  version being used by the node

8

services

uint64_t

bitfield of features  to be enabled for this connection

8

timestamp

int64_t

standard UNIX  timestamp in seconds

26

addr_recv

net_addr

The network address of  the node receiving this message

Fields  below require version ≥ 106

26

addr_from

net_addr

The network  address of the node emitting this message

8

nonce

uint64_t

Node random nonce,  randomly generated every time a version packet is sent. This nonce is used to  detect connections to self.

user_agent

var_str

User Agent  (0x00 if string is 0 bytes long)

4

start_height

int32_t

The last  block received by the emitting node

Fields  below require version ≥ 70001

1

relay

bool

Whether the  remote peer should announce relayed transactions or not

verison包中包含的节点提供的服务,如表3.7所示。

表3.7 节点提供的服务

Value

Name

Description

1

NODE_NETWORK

This node can be asked  for full blocks instead of just headers.

2

NODE_GETUTXO

See BIP 0064[6]

4

NODE_BLOOM

See BIP 0111[7]

8

NODE_WITNESS

See BIP 0144[8]

1024

NODE_NETWORK_LIMITED

See BIP 0159[9]

version ack包

version ack的包相对简单,仅有数据包的头,没有payload,如图3.3所示。

图3.3 version ack包

getaddr包

getaddr消息向节点发送请求,询问一个节点周围缓存的活跃节点情况,以帮助查找网络中的潜在节点。getaddr消息的响应是响应节点发送一个或多个addr消息(上限为1000)。典型的假设是,如果节点在过去三小时内发送消息,则该节点可能处于活动状态。此消息与version ack包一样,没有payload。

addr包

addr包用于节点之间传输地址信息。非广播节点,通常在三小时后被丢弃。

payload结构如表3.8所示(addr_list长度可变,用“?”表示),count表示该数据包中含有多少条地址,数值在1-1000之间。addr_list为一个地址列表,单条地址的长度为30。

表3.8 addr包payload结构

Field Size

Description

Data type

Comments

1+

count

var_int

Number  of address entries (max: 1000)

30*?

addr_list

(uint32_t + net_addr)[]

Address  of other nodes on the network. version < 209 will only read the first one.  The uint32_t is a timestamp (see note below).

第四章  实现方式

总体思路

文本所描述的对比特币节点识别的实现,使用了python 3.6.6。整体思路如下:

1)从DNS种子获取IP种子列表。

2)与IP种子列表中的所有节点建立TCP连接,完成握手。

3)获取种子节点周围的缓存节点,存储到缓存节点列表中。

4)与缓存节点列表中的每一个节点通信,获得新的缓存节点,添加到当前缓存节点列表中并去重。

5)重复第四步,直到处理完所有的缓存节点列表。

脚本的总体流程

脚本的流程如图4.1所示,脚本启动后,首先通过DNS种子获取到种子IP的列表,之后,启动10个线程,通过getaddr扫描种子IP的列表,将扫描出的结果存入一个缓存字典中。

脚本启动的同时,另一个线程,每隔1s,对脚本当前的扫描结果进行判断,若新扫描出1000个地址,则开启40个线程,对这1000个地址进行二次的验证确认,验证成功,记录入输出的比特币节点字典中。开放100个线程,通过getaddr,扫描这1000个地址,以获取更多的地址。

图4.1 脚本的总体流程

getaddr扫描

getaddr用于获取一个节点周围活跃的节点,脚本处理getaddr的流程如下:

1) 完成握手。若握手出现超时或其他情况,则直接跳过这个节点。

2) 发送getaddr消息,等待0.2秒,再次发送getaddr消息,以防丢包。

3) 对收到的数据包进行初次的过滤,过滤出addr的数据包。若40个包依然过滤不出addr包,或10秒内解析不出addr包,则跳过这个节点。

4) 过滤出addr包后,对addr包进行解析,由于一个addr包中包含的地址数量不同,所以需要根据payload中含有的地址数量,动态解析数据包的内容。

二次确认

二次确认的作用,是对getaddr扫描出来的地址,做第二次确认。通常情况下,getaddr扫描出来的地址,大部分是超时或者连接拒绝的,比特币P2P网络的特点本身如此,网络不是十分稳定。二次确认的流程如下:

1) 建立TCP连接,若连接出现错误,则记录相应的原因。

2) TCP连接建立成功后,进行握手,若超过5秒,无法完成握手,则记录其为超时。

3) 握手成功后,初步过滤出version包。

4) 对version包进行解析。

其他(性能方面的改进)

脚本一共经历了四个大的版本:

第一版,使用一个线程进行,造成了资源的空闲,12小时可以扫描10万个节点,得到7000个左右正常的节点。

第二版,初次使用了多线程,6小时可以扫描出20万个未作二次确认的节点,但地址池中的数量已经有60万个,主要是有一部分重复的节点。发现扫描时CPU占用率极高。

第三版,优化脚本中的数据结构,通过如将列表替换为字典等方式减少时间复杂度。20分钟扫描出7000个正常节点。

第四版,使用了系统定时器管理getaddr模块和二次确认模块线程的开放情况。重构了日志输出的逻辑,10分钟扫描出7000个正常的节点,且CPU占用率较低。

如图4.2所示,红色线条为getaddr模块扫描的节点总数,黑色线条为二次确认模块扫描出的超时节点数量。图中,当一条曲线斜率大时,另一条曲线斜率区域稳定,从侧面说明了多线程的调度比较理想。

图4.2 扫描节点总数与超时节点数量变化折线图

如图4.3所示,红色曲线为扫描出当前活跃的比特币节点数量。只需要10分钟左右,数量即趋于稳定,达到7500个左右。

图4.3 比特币节点数量扫描趋势折线图

第五章 扫描结果的分析

如图5.1所示,就扫描出节点的协议版本号做统计,可以发现,目前比特币网络中,大部分的节点运行70015版本的协议栈。

图 5.1 比特币节点版本号分布

如图5.2所示,对扫描出节点的客户端版本进行统计,可以发现,比特币节点的客户端软件,大部分使用了GitHub上官方的开源软件,Satoshi即为比特币的创始人中本聪,GitHub上,比特币客户端的版本命名,以它的名字加版本号组成。

图5.2 比特币节点User Agent分布

如图5.3所示,对扫描出节点的地区分布情况做统计(单次扫描的节点数量在7500-8000左右,此处对三次扫描结果做了合并),可以看出,美国和中国比特币节点数量非常多,欧洲因为国家比较多,所以分散到各个国家的数量相对较少,但欧洲比特币节点总量也很多,且比较密集。

图5.3 比特币节点地区分布情况

我们在进行比特币节点识别的过程中,发现Bitnodes网站也提供了对于比特币节点的识别。因此,我们爬取了Bitnodes网站的数据,对比两者的识别结果,从图5.4中可以看出,约有2/3的节点是重叠的,说明通过本文所述的识别方法扫描出的结果与Bitnodes总体相近,可信度较高。

图5.4 与Bitnodes网站数据的重叠度

在图5.5中,我们对本文所述方法扫描出的数据以及Bitnodes数据的国家分布进行了统计。从图中可以看出,我们所扫描到的中国的节点数量要明显高于Bitnodes的,而Bitnodes所识别到的中国外的大部分国家的节点数量要高于我们识别的数据。我们认为造成这一现象的原因为,我们的扫描VPS位于中国,而Bitnodes的位于国外。因此,我们有一个推测,在国内外同时部署VPS进行扫描,并将结果进行合并去重所得到的比特币节点的数据会更全。

图5.5 比特币节点的国家分布情况

第六章  小结

本文对比特币协议以及比特币节点识别方法进行了介绍,并对扫描到的结果进行了分析。我们发现,单次扫描(1小时左右),比特币网络中稳定提供服务的节点数量在7500到8500个(IPv4)之间,而三小时内(比特币协议规定,getaddr获取到的节点是每个节点缓存的三小时内活动过的节点)在比特币网络中活动过的节点数量在20万到30万(IPv4)之间,之所以出现差异,是因为比特币网络中除了存在负责打包区块、挖矿等行为,需要长时间运行协议栈的节点外,还存在大量轻量钱包节点,这些节点在短暂运行协议栈后就停止了协议栈。

同时,通过与Bitnodes网站的数据进行对比,我们发现双方数据的重叠度较高,说明双方的识别方式相近,数据可信度高。关于二者差异的部分,我们推测在国内外同时部署VPS进行扫描,并将结果进行合并去重所得到的比特币节点的数据会更加全面。

参考文献:

[1] Andreas M. Antonopoulos. MasteringBitcoin[J]. 2015.

[2] 比特币官网P2P网络部分介绍.

https://bitcoin.org/en/developer-guide#p2p-network

[3] 比特币协议.

https://en.bitcoin.it/wiki/Protocol_documentation

[4] 获取比特币种子节点.

https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery

[5] 比特币客户端源中的chainparams.cpp .

https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp#L128

[6]BIP 0064. https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki

[7]BIP 0111. https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki

[8]BIP 0144. https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki

[9]BIP 0159. https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki

内容编辑:物联网安全实验室 魏佩儒 张星  责任编辑:肖晴

声明:本文来自绿盟科技研究通讯,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。