You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/src/trezor/crypto/rlp.py

104 lines
3.0 KiB

from micropython import const
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from trezor.utils import Writer
# The intention below is basically:
# RLPItem = int | bytes | list[RLPItem]
# That will not typecheck though. Type `list` is invariant in its parameter, meaning
# that we cannot pass list[bytes] into a list[RLPItem] parameter (what if the
# function wanted to append an int?). We do want to enforce that it's a `list`, not
# a generic `Sequence` (because we do isinstance checks for a list). We are however
# only reading from the list and passing into things that consume a RLPItem. Hence
# we have to enumerate single-type lists as well as the universal list[RLPItem].
RLPList = list[int] | list[bytes] | list["RLPItem"]
RLPItem = RLPList | bytes | int
STRING_HEADER_BYTE = const(0x80)
LIST_HEADER_BYTE = const(0xC0)
def _byte_size(x: int) -> int:
if x < 0:
raise ValueError # only unsigned ints are supported
for exp in range(64):
if x < 0x100**exp:
return exp
raise ValueError # int is too large
def int_to_bytes(x: int) -> bytes:
return x.to_bytes(_byte_size(x), "big")
def write_header(
w: Writer,
length: int,
header_byte: int,
data_start: bytes | None = None,
) -> None:
if length == 1 and data_start is not None and data_start[0] <= 0x7F:
# no header when encoding one byte below 0x80
pass
elif length <= 55:
w.append(header_byte + length)
else:
encoded_length = int_to_bytes(length)
w.append(header_byte + 55 + len(encoded_length))
w.extend(encoded_length)
def header_length(length: int, data_start: bytes | None = None) -> int:
if length == 1 and data_start is not None and data_start[0] <= 0x7F:
# no header when encoding one byte below 0x80
return 0
if length <= 55:
return 1
return 1 + _byte_size(length)
def length(item: RLPItem) -> int:
data: bytes | None = None
if isinstance(item, int):
data = int_to_bytes(item)
item_length = len(data)
elif isinstance(item, (bytes, bytearray)):
data = item
item_length = len(item)
elif isinstance(item, list):
item_length = sum(length(i) for i in item)
else:
raise TypeError
return header_length(item_length, data) + item_length
def write_string(w: Writer, string: bytes) -> None:
write_header(w, len(string), STRING_HEADER_BYTE, string)
w.extend(string)
def write_list(w: Writer, lst: RLPList) -> None:
payload_length = sum(length(item) for item in lst)
write_header(w, payload_length, LIST_HEADER_BYTE)
for item in lst:
write(w, item)
def write(w: Writer, item: RLPItem) -> None:
if isinstance(item, int):
write_string(w, int_to_bytes(item))
elif isinstance(item, (bytes, bytearray)):
write_string(w, item)
elif isinstance(item, list):
write_list(w, item)
else:
raise TypeError