Compact Size、Compact 和 VarInt

介绍三种比特币网络和节点在序列化整数时用到的编码格式。

Compact Size

Compact Size 是最常用的格式,变长,用 1 ~ 9 字节来表示一个无符号整数。交易原始数据(raw transaction)和区块原始数据(raw block)中的很多字段,都使用这个格式编码,例如:

  • 交易输入、输出的个数
  • 解锁脚本的长度
  • 锁定脚本的长度
  • 区块中的交易个数

它的前导字节(leading byte)暗示了整个字段的长度,也暗示了整数编码存放的位置。

前导字节 字段长度 整数编码的位置 能表示的整数范围 例子
0x00 ~ 0xFC 1 当前字节 [0, 252] 0x64(100)
0xFD 3 接下来的 2 字节 [253, \(2^{16}-1\)] 0xFDE803(1,000)
0xFE 5 接下来的 4 字节 [\(2^{16}\), \(2^{32}-1\)] 0xFEA0860100(100,000)
0xFF 9 接下来的 8 字节 [\(2^{32}\), \(2^{64}-1\)] 0xFF00E40B5402000000(10,000,000,000)

整数本身使用小端序(little-endian)原码(true form)编码。0x64 的前导字节为 0x64,它小于 0xFC,所以这个整数是 0x64 = 100。0xFDE803 的前导字节为 0xFD,表示接下来的 2 字节是这个整数的小端原码编码,所以这个整数是 0x03E8 = 1000。

注意,有些文档会将 Compact Size 类型写成 VarInt,这是不准确的。

Compact

Compact 格式用 4 字节来表示一个整数 N,它很像浮点数格式。高 8 位是指数(exponent)的原码,低 23 位是尾数(mantissa)的原码,第 24 位(0x800000)是符号位(sign)。

\[ N = (-1)^{\text{sign}} * \text{mantissa} * 256^{(\text{exponent} - 3)} \]

在比特币系统中,只有区块头(block header)的 Bits(也叫 nBits)字段使用了这个格式来表示区块哈希的目标值,以暗示挖矿难度。区块 #856473区块头原始数据(raw block header)中 Bits 字段的字节序列是 0xbe1a0317,注意这是小端序,所以实际的值为 0x17031abe。

\[ N = \text{0x031abe} * 256^{\text{0x14}} = \text{0x031abe0000000000000000000000000000000000000000} \]

创世区块 Bits 字段的值 0x1d00ffff 是它的 90666502495565.78 倍,这个倍数就是该区块的挖矿难度(difficulty)。

0x1d00ffff --> 0xffff0000000000000000000000000000000000000000000000000000

Bits 不会出现编码负数的情况,计算时可以直接将尾数左移 \((\text{exponent}-3)\) 个字节来得到 N。减去的 3 正好是尾数的长度,所以指数的值实际上就是 N 的字节长度。

VarInt

VarInt 类型一般指的是 Protocol Buffers 里定义的格式。解析时持续读取字节流直到某个字节的最高有效位(MSB, the most significant bit)为 0 ,再对读到的字节做某些变换拼接后计算整数的值。

比特币协议本身没有用到 VarInt 格式,节点在读写本地 chainstate 数据库时用了它,所以 VarInt 不是比特币协议的一部分。

参考