rlp原理及用法

说明

RLP (递归长度前缀)提供了一种适用于任意二进制数据数组的编码,RLP已经成为以太坊中对对象进行序列化的主要编码方式。

实现原理

RLP 编码会对字符串和列表进行序列化操作,具体的编码流程如下图:

当然,目前在Web3中的python库和Javascript库中已经集成了RLP的编码方法。

交易数据编码

根据how-to-build-a-simple-transaction的说明,以太坊中的交易数据的逻辑结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-----------------------------
| Nonce | Up to 32 bytes |
-----------------------------
| GasPrice | Up to 32 bytes |
-----------------------------
| GasLimit | Up to 32 bytes |
-----------------------------
| To | 20 bytes addr |
-----------------------------
| Value | Up to 32 bytes |
-----------------------------
| Data | 0 - unlimited |
-----------------------------
| V | 1 (usually) |
-----------------------------
| R | 32 bytes |
-----------------------------
| S | 32 bytes |
-----------------------------

需要注意的是:这仅仅只是逻辑结构,实际上的交易数据都是经过RLP编码之后得到的数据,所以实际的数据会比这些逻辑数据更长(具体原因参考RLP的原理)

以一个简单的转账交易为例来做说明:0x14a298c1eea89f42285948b7d51eeac2876ca7406c9784b9b90dd3591d156d64

通过getRawTx,我们可以看到实际的交易数据是:

1
0xf86b80850ba43b7400825208947917bc33eea648809c285607579c9919fb864f8f8703baf82d03a0008025a0067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46a069b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69

通过ethereumdecoder, 解析得到对应的交易数据是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"nonce": 0,
"gasPrice": {
"_hex": "0x0ba43b7400"
},
"gasLimit": {
"_hex": "0x5208"
},
"to": "0x7917bc33eea648809c285607579c9919fb864f8f",
"value": {
"_hex": "0x03baf82d03a000"
},
"data": "0x",
"v": 37,
"r": "0x067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46",
"s": "0x69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69"
}

对比在区块链浏览器中的数据:

两者是完全一致的。

得到这些原始的交易数据,我们尝试通过RLP还原为原来原始的交易数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import rlp
tx_message = list()

# tx_len - f8
tx_nonce = ''
tx_gasPrice = 0x0ba43b7400
tx_gasLimit = 0x5208
tx_to = 0xcce5fd90eabab3d5d35119eed7f2ac5796e3d06c
tx_value = 0x03baf82d03a000
tx_data = 0x00
tx_w = 0x25
tx_r = 0x067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46
tx_s = 0x69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69

tx_message.extend(
(
rlp.encode(tx_nonce),
rlp.encode(tx_gasPrice),
rlp.encode(tx_gasLimit),
rlp.encode(tx_to),
rlp.encode(tx_value),
rlp.encode(tx_data),
rlp.encode(tx_w),
rlp.encode(tx_r),
rlp.encode(tx_s),
)
)

result_b = b''.join(tx_message)
result = result_b.hex()

最终输出的结果是:

1
80850ba43b740082520894cce5fd90eabab3d5d35119eed7f2ac5796e3d06c8703baf82d03a0008025a0067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46a069b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69

和预期的一直,说明我们通过python编码最终和实际的交易数据一致,也说明我们的逻辑是正确的。

编解码交易数据

发送交易有两个步骤:

  1. 对交易元数据使用RLP编码
  2. 用私钥对编码后的交易数据进行签名,签名之后数据的数据结构就会多出rsv
  3. 综合所有的交易元数据和r,s,v经过rlp编码就可以获得最终的raw_transaction

有关步骤1中的交易数据和步骤2中签名之后的交易数据,可以参考etherjs文档transactions

既然交易数据是通过RLP编码和解码的,那么我们就可以手动将交易数据编码或者是对已经编码后的交易数据进行解码。

编码交易数据

1
2
3
4
5
6
7
8
9
10
11
12
13
let transaction = {
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
value: ethers.utils.parseEther('1'),
data: '0xE0A293E08F72454CEd99E1769c3ebd21fD2C20a1',
gasLimit: '22000',
maxFeePerGas: ethers.utils.parseUnits('20', 'gwei'),
maxPriorityFeePerGas: ethers.utils.parseUnits('5', 'gwei'),
nonce: 1,
type: 2,
chainId: 31337, // 31337
}

let serializedTransaction = await ethers.utils.serializeTransaction(transaction)

得到编码后的交易数据是:

1
0x02f847827a690185012a05f2008504a817c8008255f09470997970c51812dc3a010c7d01b50e0d17dc79c8881bc16d674ec8000094e0a293e08f72454ced99e1769c3ebd21fd2c20a1c0

然后,我们使用私钥对这个交易数据进行签名和RLP编码:

1
2
let wallet = new ethers.Wallet(privateKey)
let signedTransaction = await wallet.signTransaction(transaction)

其中的signTransaction对编码后的交易数据(transaction)同时进行了签名和RLP编码,最终得到了签名之后的raw_transaction是:

1
0x02f88a827a690185012a05f2008504a817c8008255f09470997970c51812dc3a010c7d01b50e0d17dc79c8881bc16d674ec8000094e0a293e08f72454ced99e1769c3ebd21fd2c20a1c001a00d254c2662f6b1db272520fdb6b64d2402849058e89deab6c64d3b6cf2ebea4ca073460b644f90d5ccc59b3737f6e514b4ff91053756846bfbff16fa38d017a1b5

我们还可以严格按照先签名,后编码的方式获得raw_transaction:

参考

https://paper.seebug.org/656/

https://mirror.xyz/rbtree.eth/y2oMRSSKy3kI-fYL9P2nAmJUXhxV1P2x4vAy_7D9-MM

https://blog.csdn.net/yanerkouxin/article/details/113717774

文章作者: Oooverflow
文章链接: https://www.oooverflow.com/2022/10/10/rlp/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Oooverflow