这是以太坊源码研究的第一篇文章。基本上来说,我写什么内容,说明我正好在学习什么内容,并没有固定的顺序。之所以先写RLP编码,是因为在一开始研究以太坊交易结构的时候,就遇上RLP编码了,所谓拣上不如撞上,就从它开始吧。
RLP(Recursive Length Prefix)是以太坊中广泛运用的一种编码方法,用于序列化以太坊中各类对象。RLP可以把任意嵌套的二进制数据数组,都编码成为一个“扁平”的无嵌套的byte数组。
任意嵌套的二进制数据数组,可能是一个空字符串“”,可能是一个整数比如36,也可能是一个非常复杂的数据结构,比如["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]。对于这些千变万化的数据,RLP到底是怎么进行编码的呢?其主要规则如下:
如果是单个字节,并且值在[0x00, 0x7f]范围内,则RLP编码就是它自己。这个好理解,不用解释。
如果是长度不超过55的字符串,那么RLP编码的第一个字节用来指定字符串长度,其值为0x80加上字符串长度,之后再跟整个字符串。因此,第一个字节值范围从0x80到0xb7,空字符串时为0x80,长度55的字符串为0x80+0x37=0xb7。
例:“dog"编码后为4个字节:[0x83, 'd', 'o', 'g']如果是长度超过55的字符串,这时字符串的长度可能会很长,甚至超过一个字节。RLP规定这种情况下,从第二个字节开始存储字符串的长度,将长度所花的字节数再加上0xb7的和作为第一个字节,字符串长度之后再跟字符串的内容。
例:"Lorem ipsum dolor sit amet, consectetur adipisicing elit"字符串一共56个字节,编码结果为58字节:[ 0xb8, 0x38, 'L', 'o', 'r', 'e', 'm', ' ', ... , 'e', 'l', 'i', 't' ]。其中,0x38即56为字符串长度,0xb8为0xb7+1,1就是0x38这个长度本身的长度也就是1个字节。如果是长度不超过55的列表,那么RLP编码的第一个字节是0xc0加上列表长度,后跟列表内容。因此,第一个字节值范围从0xc0到0xf7。
例: [ "cat", "dog" ]列表,编码后为[ 0xc8, 0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g' ]。其中0xc8为0xc0+8,8就是两个字符串的总长度。如果是长度超过55的列表,那么RLP编码的第一个字节是0xf7加上列表长度的字节长度,后跟列表总长度,再跟列表项目内容的串联。因此,第一个字节的范围[0xf8, 0xff]。
再举个稍微复杂的例子:[ [ ], [ [ ] ], [ [ ], [ [ ] ] ] ] 的编码结果是 [ 0xc7, 0xc0, 0xc1, 0xc0, 0xc3, 0xc0, 0xc1, 0xc0 ]。
编码过程用python语言来描述是下面这样的:
def rlp_encode(input): if isinstance(input,str): if len(input) == 1 and ord(input) < 0x80: return input else: return encode_length(len(input), 0x80) + input elif isinstance(input,list): output = '' for item in input: output += rlp_encode(item) return encode_length(len(output), 0xc0) + outputdef encode_length(L,offset): if L < 56: return chr(L + offset) elif L < 256**8: BL = to_binary(L) return chr(len(BL) + offset + 55) + BL else: raise Exception("input too long")def to_binary(x): if x == 0: return '' else: return to_binary(int(x / 256)) + chr(x % 256)
下面再看解码。解码其实就是编码的逆过程,每次读入第一个字节时判断其类型,计算出长度和偏移量,再解构具体数字。其具体规则如下:
如果第一个字节的范围是[0x00,0x7f],则数据是一个字符串,且字符串本身就是第一个字节;
如果第一个字节的范围是[0x80,0xb7],则数据是一个字符串,其长度等于第一个字节减去0x80,字符串内容在第一个字节之后;
如果第一个字节的范围是[0xb8,0xbf],则数据是一个字符串,并且字节长度等于第一个字节减去0xb7,字符串长度在第一个字节之后,字符串跟随长度串;
如果第一个字节的范围是[0xc0,0xf7],则数据是一个列表,其总长度等于第一个字节减去0xc0,列表中各项目的RLP编码的串联在第一个字节之后;
如果第一个字节的范围是[0xf8,0xff],则数据是一个列表,字节长度等于第一个字节减去0xf7,列表总长度在第一个字节之后,再接下来是所有项目的RLP编码串联。
例子就不再举了。解码过程的python代码如下:
def rlp_decode(input): if len(input) == 0: return output = '' (offset, dataLen, type) = decode_length(input) if type is str: output = instantiate_str(substr(input, offset, dataLen)) elif type is list: output = instantiate_list(substr(input, offset, dataLen)) output + rlp_decode(substr(input, offset + dataLen)) return outputdef decode_length(input): length = len(input) if length == 0: raise Exception("input is null") prefix = ord(input[0]) if prefix <= 0x7f: return (0, 1, str) elif prefix <= 0xb7 and length > prefix - 0x80: strLen = prefix - 0x80 return (1, strLen, str) elif prefix <= 0xbf and length > prefix - 0xb7 and length > prefix - 0xb7 + to_integer(substr(input, 1, prefix - 0xb7)): lenOfStrLen = prefix - 0xb7 strLen = to_integer(substr(input, 1, lenOfStrLen)) return (1 + lenOfStrLen, strLen, str) elif prefix <= 0xf7 and length > prefix - 0xc0: listLen = prefix - 0xc0; return (1, listLen, list) elif prefix <= 0xff and length > prefix - 0xf7 and length > prefix - 0xf7 + to_integer(substr(input, 1, prefix - 0xf7)): lenOfListLen = prefix - 0xf7 listLen = to_integer(substr(input, 1, lenOfListLen)) return (1 + lenOfListLen, listLen, list) else: raise Exception("input don't conform RLP encoding form")def to_integer(b): length = len(b) if length == 0: raise Exception("input is null") elif length == 1: return ord(b[0]) else: return ord(substr(b, -1)) + to_integer(substr(b, 0, -1)) * 256
作者:魏兆华
链接:https://www.jianshu.com/p/f960553742ae
共同学习,写下你的评论
评论加载中...
作者其他优质文章