【比特币】 BIP-0037 详细说明

标签: 比特币BIPBIP37
1030人阅读 评论(0) 收藏 举报
分类:

BIP-0037 详细说明

  BIP: 37
  Layer: Peer Services
  Title: 连接布隆过滤器
  Author: Mike Hearn <hearn@google.com>
          Matt Corallo <bip37@bluematt.me>
  Comments-Summary: No comments yet.
  Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0037
  Status: Final
  Type: Standards Track
  Created: 2012-10-24
  License: PD

概述

这个BIP为P2P协议增加了新的支持,允许同行减少他们发送的交易数据量。 在版本握手完成之后,节点可以选择在每个连接上设置过滤器。过滤器被定义为从交易派生的数据的布隆过滤器。 布隆过滤器是一个概率数据结构,允许测试集成员 - 他们可以有误报,但不是假的否定。

本文不会详细介绍布隆过滤器如何工作,读者可以参考维基百科进行介绍。

动机

随着比特币使用量的增长,下载数据块和交易广播所需的带宽量也随之增加。 实施简化支付验证的客户不会试图完全验证区块链,而只是检查区块标题连接在一起是正确的,并且相信高难度链中的交易实际上是有效的。 有关此模式的更多详细信息,请参阅比特币纸张。

今天,SPV客户端必须下载块和全部广播交易的全部内容,才能抛弃绝大多数与钱包无关的交易。 这减慢了他们的同步过程,浪费了用户带宽(在手机上经常被测量)并且增加了内存使用。 所有这三个问题都触发了真正的用户投诉Android“比特币钱包”应用程序,实施SPV模式。 为了使链同步更快,更便宜,并且能够在内存有限的旧电话上运行,我们希望远程对等方在通过网络发送不相关的事务之前丢弃它们。

设计原理

实现既定目标的最明显的方法是让客户端将密钥列表上传到远程节点。 出于以下原因,我们采取更复杂的方法:

  • 隐私:由于Bloom过滤器是概率性的,客户端选择误报率,所以节点可以在精度和带宽使用之间进行权衡。 一个能够获得大量带宽的节点可能会选择高FP率,这意味着远程节点无法准确地知道哪些事务属于客户端,哪些不属于客户端。 一个带宽非常小的节点可能会选择使用非常准确的过滤器,这意味着它们只能获得与其钱包实际相关的已发送事务,而远程对等可能能够将事务与IP地址(以及相互之间)相关联。
  • 布隆过滤器是紧凑的,测试它们的成员资格是快速的。 这样就可以以最小的风险开启DoS攻击的潜力,从而达到令人满意的性能特点。

规格

新消息

我们开始在协议中添加三条新消息:

  • filterload 其中设置当前布隆过滤器在连接上
  • filteradd 它将给定的数据元素添加到连接当前过滤器,而不需要设置全新的数据元素
  • filterclear 这会删除当前的过滤器并返回到BIP37的常规使用。

请注意,不存在filterremove命令,因为从本质上来说,Bloom过滤器是仅附加数据结构。 一旦添加了一个元素,就不能从头开始重建整个结构。

filterload命令定义如下:

字段大小 描述 数据类型 注释
? filter uint8_t[] 过滤器本身只是一个任意字节对齐大小的位字段。 最大大小是36,000字节。
4 nHashFuncs uint32_t 在此过滤器中使用的散列函数的数量。 该字段中允许的最大值是50。
4 nTweak uint32_t 将随机值添加到布隆过滤器所使用的哈希函数中的种子值。
1 nFlags uint8_t 一组控制如何将匹配项添加到过滤器的标志。

请参阅下面有关Bloom过滤器算法的说明,以及如何选择nHashFuncs和过滤器大小以获得所需的误报率。

在接收到一个filterload命令后,远程节点将立即将其宣告的广播事务(在inv包中)限制为匹配过滤器的事务,其中匹配算法在下面指定。 这些标志控制匹配算法的更新行为。

filteradd命令定义如下:

字段大小 描述 数据类型 注释
? data uint8_t[] 要添加到当前过滤器的数据元素。

数据字段的大小必须小于或等于520字节(任何可能匹配的对象的最大大小)。

给定的数据元素将被添加到布隆过滤器。 必须先使用filterload提供过滤器。 这个命令是有用的,如果一个新的密钥或脚本被添加到一个客户端钱包,同时它的网络连接打开,它避免了重新计算和发送一个全新的过滤器到每个节点的需要(虽然这样做通常建议 保持匿名)。

filterclear命令完全没有参数。

在设置过滤器后,节点不仅停止通告不匹配的交易,还可以为过滤的块提供服务。 一个过滤的块由merkleblock消息定义,并且定义如下:

字段大小 描述 数据类型 注释
4 version uint32_t 块的版本信息,根据软件版本创建此块
32 prev_block char[32] 先前块的特定块的引用的哈希值
32 merkle_root char[32] 对Merkle树集合的引用,它是与此块相关的所有事务的散列
4 timestamp uint32_t 创建此块时的时间戳记录(限于2106!)
4 bits uint32_t 计算的难度目标被用于这个块
4 nonce uint32_t 用于生成此块…随机数,以允许头的变化,并计算不同的散列
4 total_transactions uint32_t 块中的交易数量(包括不匹配的)
hashes uint256[] 散列深度优先(包括标准的varint大小前缀)
flags byte[] 标志位,每8个字节打包,最低有效位先(包括标准的varint大小前缀)

请参阅下面的部分merkle树散列和标志的格式。

因此,merkleblock消息是一个块头,加上一个merkle树的一部分,可以用来提取匹配过滤器的事务的标识信息,并证明匹配的事务数据确实出现在解决的块中。 客户可以使用这些数据来确保远程节点没有给他们提供从未出现在实际块中的假交易,虽然说谎通过遗漏仍然是可能的。

扩展到现有消息

版本命令扩展了一个新的字段:

字段大小 描述 数据类型 注释
1 byte fRelay bool 如果为false,则在收到过滤器{load,add,clear}命令之前,不会公布广播事务。 如果丢失或者为真,协议行为不会发生变化。

希望使用Bloom过滤的SPV客户端通常会在版本消息中将fRelay设置为false,然后根据自己的钱包(或其子集,如果它们重叠不同的对等方)设置过滤器。 在设置过滤器之前,能够选择退出inv消息可以防止客户端在完成版本握手和设置过滤器之间的短暂时间窗口中充斥流量。

getdata命令被扩展为允许inv子消息中的新类型。类型字段现在可以是MSG_FILTERED_BLOCK(== 3)而不是MSG_BLOCK。如果连接上没有设置过滤器,则忽略过滤块的请求。 如果已设置过滤器,则会为请求的块散列返回一个merkleblock消息。此外,由于merkleblock消息仅包含交易散列列表,因此匹配过滤器的交易也应在发送merkleblock后的单独tx消息中发送。 这避免了本来需要的慢速往返(接收哈希,没有看到一些这些事务,请求)。 请注意,由于目前没有办法请求节点中已经在块中的事务(除了请求完整块)外,请求节点还没有用inv发送或通告的一组匹配事务必须是 发送并且任何与该过滤器相匹配的附加交易也可以被发送。 这允许客户端(例如参考客户端)限制其必须记住给定节点的inv数量,同时仍然向节点提供所需的所有事务。

过滤器匹配算法

筛选器可以针对任意数据进行测试,以查看客户端是否插入了该数据。 因此,出现了什么样的数据应该被插入/测试的问题。

要确定事务是否与过滤器匹配,使用以下算法。 一旦找到匹配,算法就会中止。

  1. 测试交易本身的散列。
  2. 对于每个输出,测试输出脚本的每个数据元素。 这意味着输出脚本中的每个散列和键都是独立测试的。 重要提示:如果在测试交易时输出匹配,则节点可能需要通过插入序列化的COutPoint结构来更新过滤器。 请参阅下面的更多细节。
  3. 对于每个输入,测试序列化的COutPoint结构。
  4. 对于每个输入,测试输入脚本的每个数据元素(注意:输入脚本只包含数据元素)。
  5. 否则没有匹配。

通过这种方式,地址,键和脚本散列(对于P2SH输出)都可以被添加到过滤器中。 您还可以匹配在输入或输出中标记有众所周知的数据元素的事务类,例如,实现各种形式的智能属性。

为了确保你可以在你的钱包中找到交易消费的输出,即使你对他们的形式一无所知。 正如你所看到的,一旦设置了连接,过滤器就不是静态的,并且可以在整个连接生命周期中改变。 这样做是为了避免以下竞争条件:

  1. 客户端设置一个与钱包中的密钥匹配的过滤器。 然后他们开始下载块链。 使用getblocks来请求客户端丢失的链的部分。
  2. 服务节点从磁盘读取第一个块。 它包含发送钱给客户端密钥的TX 1。 它匹配过滤器,因此被发送到客户端。
  3. 服务对端从磁盘读取第二个数据块。 它包含发送TX 1的TX 2,但TX 2不包含任何客户密钥,因此不发送。 客户不知道他们收到的钱已经用完了。

通过在步骤2中利用所发现的端点原子地更新布隆过滤器,在步骤3中过滤器将与TX 2匹配,并且尽管在处理第一和第二块的节点之间没有暂停,客户端将会了解所有相关交易。

过滤器的nFlags字段控制节点的精确更新行为,并且是一个位字段。

  • BLOOM_UPDATE_NONE(0)表示找到匹配项时不调整过滤器。
  • BLOOM_UPDATE_ALL(1)表示如果过滤器匹配scriptPubKey中的任何数据元素,则将outpoint序列化并插入到过滤器中。
  • BLOOM_UPDATE_P2PUBKEY_ONLY(1)表示只有匹配scriptPubKey中的数据元素,并且该脚本具有标准的“pay to pubkey”或“pay to multisig”形式,才会将outpoint插入到过滤器中。

这些区别有助于避免由于错误的阳性率增加而使滤波器过快劣化。 我们可以观察到,一个预计只接收标准付款地址表单的钱包的钱包不需要自动过滤器更新,因为任何花费其自己的输出的交易在输入中具有可预测的数据元素(公钥 哈希到地址)。如果钱包可能收到付费地址输出以及付费到付费或付费多输出,那么BLOOM_UPDATE_P2PUBKEY_ONLY是合适的,因为它避免了最常见输出类型的过滤器的不必要的扩展,但仍能确保正确的行为 付款明确指定密钥。

显然,nFlags == 1或nFlags == 2意味着随着更多的链扫描,过滤器会变得更脏。 客户应该监测观察到的假阳性率,并定期用干净的刷新过滤器。

部分Merkle分支格式

Merkle树是一种将一组项目排列为树的叶节点的方式,其中内部节点是其子哈希连接的哈希(hash)。 根节点被称为Merkle根。每个比特币块都包含从块交易形成的树的Merkle根。 通过提供树内部节点的一些元素(称为Merkle分支),形成了一个证明,即在开采时给定的事务确实在块中,但是证明的大小远小于原始块的大小。

构建一个部分Merkle树对象

  • 遍历从根向下的merkle树,并为每个遇到的节点:
    • 检查此节点是否对应于要包含的叶子节点(事务)或其父节点:
    • 如果是这样,附加一个“1”比特的标志位
    • 否则,追加一个“0”比特位
    • 检查此节点是否为内部节点(非叶节点)并且 是包含的叶节点的父节点:
    • 如果是这样的话:
    • 下降到其左侧子节点,并完全处理其下的子树(深度优先)。
    • 如果这个节点也有一个正确的子节点,那么也进入它。
    • 否则:将此节点的哈希附加到哈希列表。

解析部分Merkle树对象

由于部分块消息包含整个块中的事务数量,所以先前已知该Merkle树的形状。 再次,遍历这棵树,计算遍历节点的散列:

  • 从标志位列表中读一个比特位
    • 如果是’0’
    • 从哈希列表中读取哈希值,并将其作为该节点的哈希值返回。
    • 如果它是“1”并且这是叶节点:
    • 下降到其左侧的子树,并将其计算的散列存储为L.
    • 如果这个节点也有一个正确的子节点:
    • 下降到其正确的子节点,并将其计算的散列存储为R.
    • 如果L == R,则部分Merkle树对象无效。
    • 返回 Hash(L || R).
    • 如果此节点没有正确的子节点,则返回Hash(L || L)。

部分merkle树对象仅在以下情况下有效:

  • 哈希列表中的所有哈希都被消耗掉了。
  • 在标志位列表中的所有比特位被消耗(除了填充,使之成为一个完整的字节),并没有更多的。
  • 为根节点计算的散列匹配区块头部的merkle根。
  • 区块头部是有效的,其声称工作量证明相匹配。
  • 在两个孩子的节点中,左右分支的哈希值永远不相等。

布隆过滤器格式

布隆过滤器是一个位域,其中根据将数据元素馈送到一组不同的散列函数来设置位。 所使用的散列函数的数量是过滤器的参数。 在比特币中,我们使用32位Murmur哈希函数的版本3。 为了得到N个“不同的”散列函数,我们只需用下面的公式初始化Murmur算法:

nHashNum * 0xFBA4C795 + nTweak

即如果过滤器用4个散列函数初始化并且调整为0x00000005,则当需要第二函数(索引1)时,h1将等于4221880218。

使用 filterload命令加载过滤器时,可以选择两个参数。 一个是以字节为单位的过滤器的大小。 另一个是要使用的散列函数的数量。 要选择参数,您可以使用以下公式:

假设N是你想插入到集合中的元素的数量,P是误报的概率,其中1.0是“匹配所有”,零是无法实现的。

以字节为单位的过滤器的大小S由(-1 / pow(log(2), 2) * N * log(P)) / 8给出。当然,您必须确保它不超过最大大小(36,000 :因为它代表20,000个项目的过滤器,误报率<0.1%或10,000个项目,误报率<0.0001%)。

所需散列函数的数量由S * 8 / N * log(2)给出。

版权

该文件被放置在公共领域。

参考资料

https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki

https://zh.wikipedia.org/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8

https://en.wikipedia.org/wiki/Bloom_filter

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

https://github.com/cpselvis/zhihu-crawler/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%AE%9E%E7%8E%B0

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:543238次
    • 积分:8078
    • 等级:
    • 排名:第2874名
    • 原创:287篇
    • 转载:13篇
    • 译文:4篇
    • 评论:70条
    博客专栏
    微信订阅号
    欢迎加入QQ群
    玩家老黄历(微信小程序)
    谢谢支持~
    最新评论