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 不是比特币协议的一部分。