Перейти до основного контенту
Change page

Серіалізація за допомогою рекурсивного префікса довжини (RLP)

Останні оновлення сторінки: 23 лютого 2026 р.

Серіалізація з префіксом рекурсивної довжини (RLP) широко використовується у виконавчих клієнтах Ethereum. RLP стандартизує передачу даних між вузлами у форматі, що заощаджує простір. Мета RLP — кодувати довільно вкладені масиви двійкових даних, і RLP є основним методом кодування, що використовується для серіалізації об'єктів у виконавчому рівні Ethereum. Основна мета RLP — кодувати структуру; за винятком додатних цілих чисел, RLP делегує кодування конкретних типів даних (наприклад, рядків, чисел з плаваючою комою) протоколам вищого порядку. Додатні цілі числа мають бути представлені у двійковій формі з порядком байтів від старшого до молодшого (big-endian) без початкових нулів (таким чином, ціле значення нуль еквівалентне порожньому масиву байтів). Десеріалізовані додатні цілі числа з початковими нулями повинні розглядатися як недійсні будь-яким протоколом вищого порядку, що використовує RLP.

Більше інформації в жовтій книзі Ethereum (додаток B) (opens in a new tab).

Для кодування словника за допомогою RLP пропонуються дві канонічні форми:

  • використання [[k1,v1],[k2,v2]...] з ключами в лексикографічному порядку
  • використання кодування вищого рівня Patricia Tree, як це робить Ethereum

Визначення

Функція кодування RLP приймає елемент. Елемент визначається наступним чином:

  • рядок (тобто масив байтів) — це елемент
  • список елементів — це елемент
  • додатне ціле число — це елемент

Наприклад, все перераховане нижче є елементами:

  • порожній рядок;
  • рядок, що містить слово «cat»;
  • список, що містить будь-яку кількість рядків;
  • і більш складні структури даних, як-от ["cat", ["puppy", "cow"], "horse", [[]], "pig", [""], "sheep"].
  • число 100

Зауважте, що в контексті решти цієї сторінки «рядок» означає «певна кількість байтів двійкових даних»; спеціальні кодування не використовуються, і не мається на увазі жодних знань про вміст рядків (за винятком випадків, коли це вимагається правилом проти невикористання мінімальних додатних цілих чисел).

Кодування RLP визначається наступним чином:

  • Для додатного цілого числа воно перетворюється на найкоротший масив байтів, інтерпретація якого з порядком байтів від старшого до молодшого (big-endian) є цим цілим числом, а потім кодується як рядок відповідно до правил, наведених нижче.
  • Для одного байта, значення якого знаходиться в діапазоні [0x00, 0x7f] (десяткове [0, 127]), цей байт є власним кодуванням RLP.
  • В іншому випадку, якщо рядок має довжину 0–55 байтів, кодування RLP складається з одного байта зі значенням 0x80 (128 у десятковій системі) плюс довжина рядка, за яким слідує сам рядок. Таким чином, діапазон першого байта становить [0x80, 0xb7] (десятковий [128, 183]).
  • Якщо рядок довший за 55 байтів, кодування RLP складається з одного байта зі значенням 0xb7 (183 у десятковій системі) плюс довжина в байтах довжини рядка у двійковій формі, за якою слідує довжина рядка, за якою слідує сам рядок. Наприклад, рядок довжиною 1024 байти буде закодовано як \xb9\x04\x00 (десятковий 185, 4, 0), за яким слідує рядок. Тут 0xb9 (183 + 2 = 185) є першим байтом, за яким слідують 2 байти 0x0400 (1024 у десятковій системі), які позначають довжину фактичного рядка. Таким чином, діапазон першого байта становить [0xb8, 0xbf] (десятковий [184, 191]).
  • Якщо рядок має довжину 2^64 байтів або більше, він не може бути закодований.
  • Якщо загальний обсяг корисного навантаження списку (тобто сукупна довжина всіх його елементів, що кодуються за допомогою RLP) становить 0–55 байтів, кодування RLP складається з одного байта зі значенням 0xc0 плюс довжина корисного навантаження, за яким слідує об’єднання RLP-кодувань елементів. Таким чином, діапазон першого байта становить [0xc0, 0xf7] (десятковий [192, 247]).
  • Якщо загальний обсяг корисного навантаження списку довший за 55 байтів, кодування RLP складається з одного байта зі значенням 0xf7 плюс довжина в байтах довжини корисного навантаження у двійковій формі, за якою слідує довжина корисного навантаження, за якою слідує об’єднання RLP-кодувань елементів. Таким чином, діапазон першого байта становить [0xf8, 0xff] (десятковий [248, 255]).

У коді це виглядає так:

1def rlp_encode(input):
2 if isinstance(input,str):
3 if len(input) == 1 and ord(input) < 0x80:
4 return input
5 return encode_length(len(input), 0x80) + input
6 elif isinstance(input, list):
7 output = ''
8 for item in input:
9 output += rlp_encode(item)
10 return encode_length(len(output), 0xc0) + output
11
12def encode_length(L, offset):
13 if L < 56:
14 return chr(L + offset)
15 elif L < 256**8:
16 BL = to_binary(L)
17 return chr(len(BL) + offset + 55) + BL
18 raise Exception("input too long")
19
20def to_binary(x):
21 if x == 0:
22 return ''
23 return to_binary(int(x / 256)) + chr(x % 256)
Показати все

Приклади

  • рядок «dog» = [ 0x83, 'd', 'o', 'g' ]
  • список [ «cat», «dog» ] = [ 0xc8, 0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g' ]
  • порожній рядок («null») = [ 0x80 ]
  • порожній список = [ 0xc0 ]
  • ціле число 0 = [ 0x80 ]
  • байт '\x00' = [ 0x00 ]
  • байт '\x0f' = [ 0x0f ]
  • байти '\x04\x00' = [ 0x82, 0x04, 0x00 ]
  • теоретико-множинне представлення (opens in a new tab) числа три, [ [], [[]], [ [], [[]] ] ] = [ 0xc7, 0xc0, 0xc1, 0xc0, 0xc3, 0xc0, 0xc1, 0xc0 ]
  • рядок «Lorem ipsum dolor sit amet, consectetur adipisicing elit» = [ 0xb8, 0x38, 'L', 'o', 'r', 'e', 'm', ' ', ... , 'e', 'l', 'i', 't' ]`

Декодування RLP

Згідно з правилами та процесом кодування RLP, вхідні дані для декодування RLP розглядаються як масив двійкових даних. Процес декодування RLP виглядає наступним чином:

  1. відповідно до першого байта (тобто префікса) вхідних даних і декодування типу даних, довжини фактичних даних і зсуву;

  2. відповідно до типу та зсуву даних, декодувати дані відповідним чином, дотримуючись правила мінімального кодування для додатних цілих чисел;

  3. продовжити декодування решти вхідних даних;

Серед них правила декодування типів даних та зсуву є наступними:

  1. дані є рядком, якщо діапазон першого байта (тобто префікса) становить [0x00, 0x7f], і рядок — це точно сам перший байт;

  2. дані є рядком, якщо діапазон першого байта становить [0x80, 0xb7], і рядок, довжина якого дорівнює першому байту мінус 0x80, слідує за першим байтом;

  3. дані є рядком, якщо діапазон першого байта становить [0xb8, 0xbf], а довжина рядка, довжина якого в байтах дорівнює першому байту мінус 0xb7, слідує за першим байтом, а рядок слідує за довжиною рядка;

  4. дані є списком, якщо діапазон першого байта становить [0xc0, 0xf7], а об’єднання RLP-кодувань усіх елементів списку, загальний обсяг корисного навантаження якого дорівнює першому байту мінус 0xc0, слідує за першим байтом;

  5. дані є списком, якщо діапазон першого байта становить [0xf8, 0xff], а загальне корисне навантаження списку, довжина якого дорівнює першому байту мінус 0xf7, слідує за першим байтом, а об’єднання RLP-кодувань усіх елементів списку слідує за загальним корисним навантаженням списку;

У коді це виглядає так:

1def rlp_decode(input):
2 if len(input) == 0:
3 return
4 output = ''
5 (offset, dataLen, type) = decode_length(input)
6 if type is str:
7 output = instantiate_str(substr(input, offset, dataLen))
8 elif type is list:
9 output = instantiate_list(substr(input, offset, dataLen))
10 output += rlp_decode(substr(input, offset + dataLen))
11 return output
12
13def decode_length(input):
14 length = len(input)
15 if length == 0:
16 raise Exception("input is null")
17 prefix = ord(input[0])
18 if prefix <= 0x7f:
19 return (0, 1, str)
20 elif prefix <= 0xb7 and length > prefix - 0x80:
21 strLen = prefix - 0x80
22 return (1, strLen, str)
23 elif prefix <= 0xbf and length > prefix - 0xb7 and length > prefix - 0xb7 + to_integer(substr(input, 1, prefix - 0xb7)):
24 lenOfStrLen = prefix - 0xb7
25 strLen = to_integer(substr(input, 1, lenOfStrLen))
26 return (1 + lenOfStrLen, strLen, str)
27 elif prefix <= 0xf7 and length > prefix - 0xc0:
28 listLen = prefix - 0xc0;
29 return (1, listLen, list)
30 elif prefix <= 0xff and length > prefix - 0xf7 and length > prefix - 0xf7 + to_integer(substr(input, 1, prefix - 0xf7)):
31 lenOfListLen = prefix - 0xf7
32 listLen = to_integer(substr(input, 1, lenOfListLen))
33 return (1 + lenOfListLen, listLen, list)
34 raise Exception("input does not conform to RLP encoding form")
35
36def to_integer(b):
37 length = len(b)
38 if length == 0:
39 raise Exception("input is null")
40 elif length == 1:
41 return ord(b[0])
42 return ord(substr(b, -1)) + to_integer(substr(b, 0, -1)) * 256
Показати все

Для подальшого читання

Чи була ця стаття корисною?