以太坊RLP编码规则

前言

以太坊又在重复造轮子了,其实他完全可以使用thrift。

RLP目前只有以太坊在用,是以太坊自创的编码规则。存储空间比json少很多,但是可阅读性不如json。另外市面上还有thrift, google Protocol Buffer(PB)协议都是广泛应用的性能很好的结构化数据存储格式。

所以如果不是研究以太坊,RLP可以不学,学那些应用面更广法的就行了。

那么学习RLP也有一个好处,就是可以了解结构化数据存储格式。不管是google的PB协议还是json,都是将结构化数据进行序列化进行存储或者网络传输。精通一种即可快速掌握其他几种。在实际工作中也会拓宽你的知识面,思考问题的方式,融会贯通,可以应用到具体的工作中(如一些安全性特别高的接口,我们可以自定义序列化结构)。

序列化如何节省空间

假设java中有一个类Stu:

public class Stu{
    String name;
    String sex;
}

我们初始化这个对象:Stu stu = new Stu(“zhangsan”, “male”)。json序列化后的字符串是:"{“name”:“zhangsan”,“sex”:“male”}" 总长度为35。 而实际有效数据只有zhangsan和male共12。很明显,浪费了2倍的存储空间,不管是存储在DB还是磁盘还是网络传输,都大大增加了额外的损耗。

thrift是通过客户端可服务器端固定子段顺序,然后按序进行序列化,只序列化有效信息,而没有字段信息。大概可以节省一半的空间。

那么我们一步一步看RLP 是如何做的。

RLP编码定义

类型 首字节范围 首字节(转为10进制)范围 编码内容
[0x00, 0x7f]的单个字节 [0x00, 0x7f] [0-127] 字节内容本身
0-55 字节长的字符串 [0x80, 0xb7] [128-183] 0x80加上字符串长度,后跟字符串二进制内容
超过55个字节的字符串 [0xb8, 0xbf] [184-191] 0xb7加上字符串长度的长度,后跟字符串二进制内容. RLP可以支持的单个最大字符串长度为2的64次方
0-55个字节长的列表(所有项的组合长度) [0xc0, 0xf7] [192-247] 0xc0加上所有的项的RLP编码串联起来的长度得到的单个字节,后跟所有的项的RLP编码的串联组成。
列表的内容超过55字节 [0xf8, 0xff] [248-255] 0xf7加上所有的项的RLP编码串联起来的长度的长度得到的单个字节,后跟列表元素总长度(每项实际长度之和+每个子字符串本身长度的长度),再后跟所有的项的RLP编码的串联组成。

RLP实际只给以下两种类型数据编码:

  • byte数组
  • byte数组的数组,称之为列表
    规则1:对于值在[0, 127]之间的单个字节,其编码是其本身。
    例1:a的编码是97` 。

规则2:如果byte数组长度 l <= 55,编码的结果是数组本身,再加上128+l作为前缀。
例2:空字符串编码是128,即128 = 128 + 0
例3:abc编码结果是131 97 98 99,其中 131=128+len("abc")97 98 99依次是 a b c
  
规则3:如果字符串长度大于55, 编码结果第一个是183加字符串长度的编码(指二进制编码)的长度,然后是字符串长度的本身的编码,最后是字符串的编码。
请把上面的规则多读几篇,特别是字符串长度的编码的长度。
例4:编码下面这段字符串:
The length of this sentence is more than 55 bytes, I know it because I pre-designed it
这段字符串共86个字节,而86的编码只需要一个字节,那就是它自己,因此,编码的结果如下:

184 86 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 
105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 73 32 107 110 111 119 32 105 
116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116

其中前三个字节的计算方式如下:

  • 184 = 183 + 1,因为字符串长度86编码后仅占用一个字节。
  • 86即字符串长度86(86二进制是1010110,长度为1字节)
  • 84是T的编码
    例5:编码一个重复1024次"a"的字符串,其结果为:185 4 0 97 97 97 97 97 97 …。
    1024按 二进制编码为10000000000,长度为2字节,因此185 = 183 + 2。

规则1~3定义了byte数组的编码方案,下面介绍列表的编码规则。在此之前,我们先定义列表长度是指子列表编码后的长度之和。

规则4:如果列表长度小于55,编码结果第一位是192加列表长度的编码的长度,然后依次连接各子列表的编码。

注意规则4本身是递归定义的。
例6:[“abc”, “def”]的编码结果是200 131 97 98 99 131 100 101 102。
其中abc的编码为131 97 98 99,def的编码为131 100 101 102。两个子字符串的编码后总长度是8,因此编码结果第一位计算得出:192 + 8 = 200。

规则5:如果列表长度超过55,编码结果第一位是247加列表长度的编码长度,然后是列表长度本身的编码,最后依次连接各子列表的编码。

规则5本身也是递归定义的,和规则3相似。
例7:
["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]
编码结果是:

248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 
32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 
105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116

其中前两个字节的计算方式如下:

  • 248 = 247 +1
  • 88 = 86 + 2,在规则3的示例中,长度为86,而在此例中,由于有两个子字符串,每个子字符串本身的长度的编码各占1字节,因此总共占2字节。
    第3个字节179依据规则2得出179 = 128 + 51
    第55个字节163同样依据规则2得出163 = 128 + 35

例8:最后我们再来看个稍复杂点的例子以加深理解递归长度前缀,
["abc",["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]]
编码结果是:

248 94 131 97 98 99 248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 
115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 
115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 
115 105 103 110 101 100 32 105 116

列表第一项字符串abc根据规则2,编码结果为131 97 98 99,长度为4。
列表第二项也是一个列表项:
["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]
根据规则5,结果为

48 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116
 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32
 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115
 105 103 110 101 100 32 105 116

长度为90,因此,整个列表的编码结果第二位是90 + 4 = 94, 占用1个字节,第一位247 + 1 = 248

以上5条就是RPL的全部编码规则。

RLP解码

解码时,首先根据编码结果第一个字节f的大小,执行以下的规则判断:

  1. 如果f∈ [0,128), 那么它是一个字节本身。
  2. 如果f∈[128,184),那么它是一个长度不超过55的byte数组,数组的长度为 l=f-128
  3. 如果f∈[184,192),那么它是一个长度超过55的数组,长度本身的编码长度ll=f-183,然后从第二个字节开始读取长度为ll的bytes,按照BigEndian编码成整数l,l即为数组的长度。
  4. 如果f∈(192,247],那么它是一个编码后总长度不超过55的列表,列表长度为l=f-192。递归使用规则1~4进行解码。
  5. 如果f∈(247,256],那么它是编码后长度大于55的列表,其长度本身的编码长度ll=f-247,然后从第二个字节读取长度为ll的bytes,按BigEndian编码成整数l,l即为子列表长度。然后递归根据解码规则进行解码。

以上解释了什么叫递归长度前缀编码,这个名字本身很好的解释了编码规则。

参考资料

https://yq.aliyun.com/articles/573191

https://segmentfault.com/a/1190000012393311

https://segmentfault.com/a/1190000011763339

https://www.jianshu.com/p/c13d9218ac55

你可能感兴趣的:(区块链)