From 9b78dd107bf7b58cf57bede897d441beff37b917 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Tue, 26 Sep 2023 23:02:09 +0200 Subject: [PATCH] fix(python): drop simple-rlp dependency and use internal copy --- poetry.lock | 11 ------ pyproject.toml | 1 - python/.changelog.d/3045.fixed | 1 + python/requirements.txt | 1 - python/setup.cfg | 2 +- python/src/trezorlib/_rlp.py | 55 ++++++++++++++++++++++++++++ python/src/trezorlib/cli/ethereum.py | 25 ++----------- python/tests/test_rlp.py | 33 +++++++++++++++++ 8 files changed, 94 insertions(+), 35 deletions(-) create mode 100644 python/.changelog.d/3045.fixed create mode 100644 python/src/trezorlib/_rlp.py create mode 100644 python/tests/test_rlp.py diff --git a/poetry.lock b/poetry.lock index 86a7d16df0..4ddabd8dbb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1429,16 +1429,6 @@ colorama = "*" dev = ["black", "flake8", "isort"] tests = ["pytest"] -[[package]] -name = "simple-rlp" -version = "0.1.2" -description = "RLP (Recursive Length Prefix) - Encode and decode data structures" -optional = false -python-versions = ">=3.6" -files = [ - {file = "simple-rlp-0.1.2.tar.gz", hash = "sha256:5c4a9c58f1b742f7fa8af0fe4ea6ff9fb02294ae041912f771570dfaf339d2b9"}, -] - [[package]] name = "six" version = "1.16.0" @@ -1580,7 +1570,6 @@ ecdsa = ">=0.9" libusb1 = ">=1.6.4" mnemonic = ">=0.20" requests = ">=2.4.0" -simple-rlp = {version = ">=0.1.2", markers = "python_version >= \"3.7\""} typing_extensions = ">=3.10" [package.extras] diff --git a/pyproject.toml b/pyproject.toml index 698bb75bcc..8113570fc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,6 @@ ed25519 = "^1.4" requests = "^2.31" termcolor = "*" Pillow = "^9" -simple-rlp = "^0.1.2" # crypto ecdsa = "^0.16" diff --git a/python/.changelog.d/3045.fixed b/python/.changelog.d/3045.fixed new file mode 100644 index 0000000000..8ab6edd9ab --- /dev/null +++ b/python/.changelog.d/3045.fixed @@ -0,0 +1 @@ +Drop simple-rlp dependency and use internal copy diff --git a/python/requirements.txt b/python/requirements.txt index 916fad94fc..b0712b3e2d 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -6,5 +6,4 @@ libusb1>=1.6.4 construct>=2.9,!=2.10.55 typing_extensions>=3.10 dataclasses ; python_version<'3.7' -simple-rlp>=0.1.2 ; python_version>='3.7' construct-classes>=0.1.2 diff --git a/python/setup.cfg b/python/setup.cfg index e801ad6669..aad62ab4d7 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -25,7 +25,7 @@ per-file-ignores = helper-scripts/*:I tools/*:I tests/*:I -known-modules = libusb1:[usb1],hidapi:[hid],PyQt5:[PyQt5.QtWidgets,PyQt5.QtGui,PyQt5.QtCore],simple-rlp:[rlp] +known-modules = libusb1:[usb1],hidapi:[hid],PyQt5:[PyQt5.QtWidgets,PyQt5.QtGui,PyQt5.QtCore] [isort] profile = black diff --git a/python/src/trezorlib/_rlp.py b/python/src/trezorlib/_rlp.py new file mode 100644 index 0000000000..4d7fa1bac3 --- /dev/null +++ b/python/src/trezorlib/_rlp.py @@ -0,0 +1,55 @@ +# inspired by core/src/trezor/crypto/rlp.py + +import typing as t +from collections.abc import Sequence + +if t.TYPE_CHECKING: + RLPItem = t.Union[t.Sequence["RLPItem"], bytes, int] + + +def _byte_size(x: int) -> int: + if x < 0: + raise ValueError("only unsigned ints are supported") + return (x.bit_length() + 7) // 8 + + +def _int_to_bytes(n: int) -> bytes: + """Convert to a correctly sized bytes object.""" + return n.to_bytes(_byte_size(n), "big") + + +def _encode_with_length(value: bytes, header_byte: int) -> bytes: + length = len(value) + if length == 1 and value[0] <= 0x7F: + return value + elif length <= 55: + return (header_byte + length).to_bytes(1, "big") + value + else: + encoded_length = _int_to_bytes(length) + return ( + (header_byte + 55 + len(encoded_length)).to_bytes(1, "big") + + encoded_length + + value + ) + + +def encode(value: "RLPItem") -> bytes: + """Encode lists or objects to bytes.""" + if isinstance(value, int): + # ints are stored as byte strings + value = _int_to_bytes(value) + + # sanity check: `str` is a Sequence so it would be incorrectly + # picked up by the Sequence branch below + assert not isinstance(value, str) + + # check for bytes type first, because bytes is a Sequence too + if isinstance(value, bytes): + header_byte = 0x80 + elif isinstance(value, Sequence): + header_byte = 0xC0 + value = b"".join(encode(item) for item in value) + else: + raise TypeError("Unsupported type") + + return _encode_with_length(value, header_byte) diff --git a/python/src/trezorlib/cli/ethereum.py b/python/src/trezorlib/cli/ethereum.py index 31b53e7c0c..3e3b89c17c 100644 --- a/python/src/trezorlib/cli/ethereum.py +++ b/python/src/trezorlib/cli/ethereum.py @@ -20,21 +20,11 @@ import sys import tarfile from decimal import Decimal from pathlib import Path -from typing import ( - TYPE_CHECKING, - Any, - Dict, - List, - NoReturn, - Optional, - Sequence, - TextIO, - Tuple, -) +from typing import TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, TextIO import click -from .. import definitions, ethereum, tools +from .. import _rlp, definitions, ethereum, tools from ..messages import EthereumDefinitions from . import with_client @@ -162,7 +152,7 @@ def _erc20_contract(token_address: str, to_address: str, amount: int) -> str: def _format_access_list( access_list: List[ethereum.messages.EthereumAccessList], -) -> List[Tuple[bytes, Sequence[bytes]]]: +) -> "_rlp.RLPItem": return [ (ethereum.decode_hex(item.address), item.storage_keys) for item in access_list ] @@ -386,11 +376,6 @@ def sign_tx( try to connect to an ethereum node and auto-fill these values. You can configure the connection with WEB3_PROVIDER_URI environment variable. """ - try: - import rlp - except ImportError: - _print_eth_dependencies_and_die() - is_eip1559 = eip2718_type == 2 if ( (not is_eip1559 and gas_price is None) @@ -490,8 +475,6 @@ def sign_tx( to = ethereum.decode_hex(to_address) - # NOTE: rlp.encode needs a list input to iterate through all its items, - # it does not work with a tuple if is_eip1559: transaction_items = [ chain_id, @@ -518,7 +501,7 @@ def sign_tx( data_bytes, *sig, ] - transaction = rlp.encode(transaction_items) + transaction = _rlp.encode(transaction_items) if eip2718_type is not None: eip2718_prefix = f"{eip2718_type:02x}" diff --git a/python/tests/test_rlp.py b/python/tests/test_rlp.py new file mode 100644 index 0000000000..358f793aac --- /dev/null +++ b/python/tests/test_rlp.py @@ -0,0 +1,33 @@ +import pytest + +from trezorlib import _rlp + +VECTORS = ( # data, expected + (b"\x10", b"\x10"), + (b"dog", b"\x83dog"), + (b"A" * 55, b"\xb7" + b"A" * 55), + (b"A" * 56, b"\xb8\x38" + b"A" * 56), + (b"A" * 1024, b"\xb9\x04\x00" + b"A" * 1024), + ([b"dog", b"cat", [b"spy"]], b"\xcd\x83dog\x83cat\xc4\x83spy"), + ([b"A" * 1024], b"\xf9\x04\x03\xb9\x04\x00" + b"A" * 1024), + ([], b"\xc0"), + ([b"A"] * 55, b"\xf7" + b"A" * 55), + ([b"A"] * 56, b"\xf8\x38" + b"A" * 56), + ([b"A"] * 1024, b"\xf9\x04\x00" + b"A" * 1024), + ([b"dog"] * 1024, b"\xf9\x10\x00" + b"\x83dog" * 1024), + (b"", b"\x80"), + (1, b"\x01"), + (0x7F, b"\x7f"), + (0x80, b"\x81\x80"), + (0x1_0000_0001, b"\x85\x01\x00\x00\x00\x01"), + (2 ** (54 * 8), b"\xb7\x01" + b"\x00" * 54), + (2 ** (55 * 8), b"\xb8\x38\x01" + b"\x00" * 55), + ([0x1234, 0x5678], b"\xc6\x82\x12\x34\x82\x56\x78"), +) + + +@pytest.mark.parametrize("data, expected", VECTORS) +def test_encode(data: "_rlp.RLPItem", expected: bytes): + actual = _rlp.encode(data) + assert len(actual) == len(expected) + assert actual == expected