RLP 编码:以太坊执行层为什么只编码结构不编码类型 图 1
RLP 编码:以太坊执行层为什么只编码结构不编码类型 · 图 1

以太坊的交易哈希为什么不可能藏下多余数据?签名验证为什么能精确重建当初签名的字节?答案藏在一种刻意简陋的编码里。RLP 全称 Recursive Length Prefix,递归长度前缀,是执行层客户端之间传输数据的标准序列化格式。官方文档对它的定位说得很克制:RLP 的目的在于编码结构,除了正整数,它把具体数据类型怎么编码交给上层协议决定。这句话是理解整个设计的钥匙。

四种前缀,一句话讲完

RLP 世界里只有两种东西:字节串和列表。规则从第一个字节就能读出全部结构。数据本身小于 0x7f 时,它就是自己,单字节零开销;字节串长度在 0 到 55 之间,加一个 0x80 到 0xb7 的前缀,前缀减去 0x80 就是长度;更长的字符串用 0xb8 到 0xbf 前缀,多出来的几个字节写长度本身。列表同理,只是前缀区间换成 0xc0 到 0xf7 与 0xf8 到 0xff。嵌套结构递归展开,列表里每个元素重新走一遍这四条规则。一个经典例子:空字符串编码为 0x80,空列表编码为 0xc0——两者只差一个比特位,却区分了没有内容和没有成员两件事。

正整数:唯一被写死的类型

RLP 唯一一条硬性的类型规定是正整数必须用大端二进制表示且不带前导零,因此整数零干脆就编成空字节数组。带前导零的整数编码必须被上层协议判为无效。为什么要这么较真?因为 RLP 要保证规范性:同一份逻辑数据只允许存在一种合法字节串。假如 0x05 和 0x0005 都合法,同一条交易就有了两种字节形态,哈希可以各算一个,签名字节可以在不改内容的前提下被改写——这是交易变异类攻击的经典入口。把编码逼成单射,哈希与签名才有一言堂。

为什么不做类型系统

对比 protobuf 或 SSZ 这类自带类型与模式文件的方案,RLP 像一个只有括号和长度记号的极简语法。这不是偷懒:执行层的共识数据(交易、区块头、状态 trie 节点)只需要结构一致,类型语义本来就在协议消息定义里各写一份。省掉模式协商,客户端实现门槛更低,跨语言实现更难跑偏;反面的代价是编码本身不可自省,拿到一坨 RLP 不知道字段含义,必须对照协议文档逐位解码。以太坊后来在共识层引入 SSZ 与默克尔化需求时,执行层依旧保留 RLP——两套序列化并存的格局是历史选择,不是谁替代谁。

一条算术直觉

算一笔账:一条典型旧式交易九个字段,RLP 头部大多只花一个前缀字节,字段名一个字节都不占——因为每个字段的位置已经由协议约定。把字段名写进编码当然更易读,但每笔交易多发几十字节、全链永久存储,这个溢价乘以几十亿笔交易,就是只编码结构的理由。可读性从来不是共识数据的刚需,确定性才是。

调试时的三个观察点

真去解码一段交易字节时,有三个高频观察点值得背下来:开头字节落在 0xc0 到 0xf7 区间说明最外层是列表,交易正是八字段列表;第一个元素解出小整数是版本号;紧跟着的长度前缀异常大的字段多半是签名字节串。把这几条对齐,就能在浏览器与调试器不一致时自己核对谁在说谎——大部分解析工具的怪结果,最后都能归到对前导零和长度的宽松处理上。

快速问答

问:RLP 编码能解出数字和地址吗?答:能解出字节串和边界;字节串是不是地址、多大数值,由调用它的那层协议说了算。

问:交易里能塞垃圾字节让哈希变长吗?答:不合规的填充会让解码严格校验失败,规范性是硬约束而非风格偏好。

问:谁在用 RLP?答:以太坊执行层客户端之间,以及旧式与带类型交易的序列化;签名前做哈希的那段字节正是 RLP 输出。

风险提示:本文是协议编码科普,具体编码细节以当期客户端实现与黄色说明书附录 B 为准;解析来路不明的编码数据时,请以严格模式校验并避免直接执行。