1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-07 15:18:08 +00:00

feat(core): use protobuf messages for network and token definitions; style and typing fixes

This commit is contained in:
Martin Novak 2022-09-16 13:56:03 +02:00
parent 62324c2de9
commit c1f27e0a11
20 changed files with 409 additions and 286 deletions

View File

@ -20,7 +20,6 @@ from urllib3.util.retry import Retry
from coin_info import ( from coin_info import (
Coin, Coin,
CoinBuckets,
Coins, Coins,
_load_builtin_erc20_tokens, _load_builtin_erc20_tokens,
_load_builtin_ethereum_networks, _load_builtin_ethereum_networks,
@ -389,7 +388,8 @@ def print_definitions_collision(
Returns a tuple composed from the prompt result if prompted otherwise None and the default value.""" Returns a tuple composed from the prompt result if prompted otherwise None and the default value."""
if old_definitions: if old_definitions:
old_defs_hash_no_metadata = [ old_defs_hash_no_metadata = [
hash_dict_on_keys(d, exclude_keys=["metadata"]) for d in old_definitions hash_dict_on_keys(d, exclude_keys=["metadata", "coingecko_id"])
for d in old_definitions
] ]
default_index = None default_index = None
@ -399,7 +399,7 @@ def print_definitions_collision(
found = "" found = ""
if ( if (
old_definitions old_definitions
and hash_dict_on_keys(definition, exclude_keys=["metadata"]) and hash_dict_on_keys(definition, exclude_keys=["metadata", "coingecko_id"])
in old_defs_hash_no_metadata in old_defs_hash_no_metadata
): ):
found = " (found in old definitions)" found = " (found in old definitions)"
@ -566,6 +566,8 @@ def check_definitions_list(
modified_definitions.remove((orig_def, new_def)) modified_definitions.remove((orig_def, new_def))
def any_in_top_100(*definitions) -> bool: def any_in_top_100(*definitions) -> bool:
if top100_coingecko_ids is None:
return True
if definitions is not None: if definitions is not None:
for d in definitions: for d in definitions:
if d is not None and d.get("coingecko_id") in top100_coingecko_ids: if d is not None and d.get("coingecko_id") in top100_coingecko_ids:
@ -625,10 +627,7 @@ def check_definitions_list(
accept_change = True accept_change = True
print_change = any_in_top_100(old_def, new_def) print_change = any_in_top_100(old_def, new_def)
# if the change contains symbol change "--force" parameter must be used to be able to accept this change # if the change contains symbol change "--force" parameter must be used to be able to accept this change
if ( if old_def.get("shortcut") != new_def.get("shortcut") and not force:
old_def.get("shortcut") != new_def.get("shortcut")
and not force
):
print( print(
"\nERROR: Symbol change in this definition! To be able to approve this change re-run with `--force` argument." "\nERROR: Symbol change in this definition! To be able to approve this change re-run with `--force` argument."
) )
@ -677,16 +676,22 @@ def check_definitions_list(
_set_definition_metadata(definition) _set_definition_metadata(definition)
def _load_prepared_definitions(definitions_file: pathlib.Path) -> tuple[list[dict], list[dict]]: def _load_prepared_definitions(
definitions_file: pathlib.Path,
) -> tuple[list[dict], list[dict]]:
if not definitions_file.is_file(): if not definitions_file.is_file():
click.ClickException(f"File {definitions_file} with prepared definitions does not exists or is not a file.") click.ClickException(
f"File {definitions_file} with prepared definitions does not exists or is not a file."
)
prepared_definitions_data = load_json(definitions_file) prepared_definitions_data = load_json(definitions_file)
try: try:
networks_data = prepared_definitions_data["networks"] networks_data = prepared_definitions_data["networks"]
tokens_data = prepared_definitions_data["tokens"] tokens_data = prepared_definitions_data["tokens"]
except KeyError: except KeyError:
click.ClickException(f"File with prepared definitions is not complete. Whole \"networks\" and/or \"tokens\" section are missing.") click.ClickException(
'File with prepared definitions is not complete. Whole "networks" and/or "tokens" section are missing.'
)
networks: Coins = [] networks: Coins = []
for network_data in networks_data: for network_data in networks_data:
@ -872,6 +877,15 @@ def prepare_definitions(
cg_tokens = _load_erc20_tokens_from_coingecko(downloader, networks) cg_tokens = _load_erc20_tokens_from_coingecko(downloader, networks)
repo_tokens = _load_erc20_tokens_from_repo(tokens_dir, networks) repo_tokens = _load_erc20_tokens_from_repo(tokens_dir, networks)
# get data used in further processing now to be able to save cache before we do any
# token collision process and others
# get CoinGecko coin list
cg_coin_list = downloader.get_coingecko_coins_list()
# get top 100 coins
cg_top100 = downloader.get_coingecko_top100()
# save cache
downloader.save_cache()
# merge tokens # merge tokens
tokens: List[Dict] = [] tokens: List[Dict] = []
cg_tokens_chain_id_and_address = [] cg_tokens_chain_id_and_address = []
@ -901,7 +915,6 @@ def prepare_definitions(
# map coingecko ids to tokens # map coingecko ids to tokens
tokens_by_chain_id_and_address = {(t["chain_id"], t["address"]): t for t in tokens} tokens_by_chain_id_and_address = {(t["chain_id"], t["address"]): t for t in tokens}
cg_coin_list = downloader.get_coingecko_coins_list()
for coin in cg_coin_list: for coin in cg_coin_list:
for platform_name, address in coin.get("platforms", dict()).items(): for platform_name, address in coin.get("platforms", dict()).items():
key = (coingecko_id_to_chain_id.get(platform_name), address) key = (coingecko_id_to_chain_id.get(platform_name), address)
@ -909,10 +922,7 @@ def prepare_definitions(
tokens_by_chain_id_and_address[key]["coingecko_id"] = coin["id"] tokens_by_chain_id_and_address[key]["coingecko_id"] = coin["id"]
# load top 100 (by market cap) definitions from CoinGecko # load top 100 (by market cap) definitions from CoinGecko
cg_top100_ids = [d["id"] for d in downloader.get_coingecko_top100()] cg_top100_ids = [d["id"] for d in cg_top100]
# save cache
downloader.save_cache()
# check changes in definitions # check changes in definitions
if old_defs is not None: if old_defs is not None:
@ -970,9 +980,11 @@ def prepare_definitions(
"--deffile", "--deffile",
type=click.Path(resolve_path=True, dir_okay=False, path_type=pathlib.Path), type=click.Path(resolve_path=True, dir_okay=False, path_type=pathlib.Path),
default="./definitions-latest.json", default="./definitions-latest.json",
help="File where the prepared definitions are saved in json format." help="File where the prepared definitions are saved in json format.",
) )
def sign_definitions(outdir: pathlib.Path, privatekey: TextIO, deffile: pathlib.Path) -> None: def sign_definitions(
outdir: pathlib.Path, privatekey: TextIO, deffile: pathlib.Path
) -> None:
"""Generate signed Ethereum definitions for python-trezor and others.""" """Generate signed Ethereum definitions for python-trezor and others."""
hex_key = None hex_key = None
if privatekey is None: if privatekey is None:
@ -992,7 +1004,7 @@ def sign_definitions(outdir: pathlib.Path, privatekey: TextIO, deffile: pathlib.
if complete_file_path.exists(): if complete_file_path.exists():
raise click.ClickException( raise click.ClickException(
f"Definition \"{complete_file_path}\" already generated - attempt to generate another definition." f'Definition "{complete_file_path}" already generated - attempt to generate another definition.'
) )
directory.mkdir(parents=True, exist_ok=True) directory.mkdir(parents=True, exist_ok=True)
@ -1039,7 +1051,8 @@ def sign_definitions(outdir: pathlib.Path, privatekey: TextIO, deffile: pathlib.
definitions_by_serialization: dict[bytes, dict] = dict() definitions_by_serialization: dict[bytes, dict] = dict()
for network in networks: for network in networks:
ser = serialize_eth_info( ser = serialize_eth_info(
eth_info_from_dict(network, EthereumNetworkInfo), EthereumDefinitionType.NETWORK eth_info_from_dict(network, EthereumNetworkInfo),
EthereumDefinitionType.NETWORK,
) )
network["serialized"] = ser network["serialized"] = ser
definitions_by_serialization[ser] = network definitions_by_serialization[ser] = network
@ -1052,8 +1065,8 @@ def sign_definitions(outdir: pathlib.Path, privatekey: TextIO, deffile: pathlib.
# build Merkle tree # build Merkle tree
mt = MerkleTree( mt = MerkleTree(
[network["serialized"] for network in networks] + [network["serialized"] for network in networks]
[token["serialized"] for token in tokens] + [token["serialized"] for token in tokens]
) )
# sign tree root hash # sign tree root hash

View File

@ -6,12 +6,15 @@ except ImportError:
from hashlib import sha256 from hashlib import sha256
class Node(): class Node:
""" """
Single node of Merkle tree. Single node of Merkle tree.
""" """
def __init__(self: "Node", left: Union[bytes, "Node"], right: Optional["Node"] = None) -> None:
self.is_leaf = (left is None) != (right is None) # XOR def __init__(
self: "Node", left: Union[bytes, "Node"], right: Optional["Node"] = None
) -> None:
self.is_leaf = (left is None) != (right is None) # XOR
if self.is_leaf: if self.is_leaf:
self.raw_value = left self.raw_value = left
self.hash = None self.hash = None
@ -43,11 +46,12 @@ class Node():
self.right_child.add_to_proof(proof) self.right_child.add_to_proof(proof)
class MerkleTree(): class MerkleTree:
""" """
Simple Merkle tree that implements the building of Merkle tree itself and generate proofs Simple Merkle tree that implements the building of Merkle tree itself and generate proofs
for leaf nodes. for leaf nodes.
""" """
def __init__(self, values: list[bytes]) -> None: def __init__(self, values: list[bytes]) -> None:
self.leaves = [Node(v) for v in values] self.leaves = [Node(v) for v in values]

View File

@ -1,28 +1,23 @@
from typing import Any
from ubinascii import unhexlify from ubinascii import unhexlify
from typing import TYPE_CHECKING
from apps.ethereum import tokens
from trezor import protobuf, wire from trezor import protobuf, wire
from trezor.crypto.curve import ed25519 from trezor.crypto.curve import ed25519
from trezor.enums import EthereumDefinitionType from trezor.enums import EthereumDefinitionType
from trezor.messages import EthereumNetworkInfo, EthereumTokenInfo from trezor.messages import EthereumNetworkInfo, EthereumTokenInfo
from apps.ethereum import tokens
from . import helpers, networks from . import helpers, networks
if TYPE_CHECKING:
from trezor.protobuf import MessageType
from .networks import NetworkInfo
from .tokens import TokenInfo
DEFINITIONS_PUBLIC_KEY = b"" DEFINITIONS_PUBLIC_KEY = b""
MIN_DATA_VERSION = 1 MIN_DATA_VERSION = 1
FORMAT_VERSION = "trzd1" FORMAT_VERSION = "trzd1"
if __debug__: if __debug__:
DEFINITIONS_DEV_PUBLIC_KEY = unhexlify("db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d") DEFINITIONS_DEV_PUBLIC_KEY = unhexlify(
"db995fe25169d141cab9bbba92baa01f9f2e1ece7df4cb2ac05190f37fcc1f9d"
)
class EthereumDefinitionParser: class EthereumDefinitionParser:
@ -31,15 +26,21 @@ class EthereumDefinitionParser:
try: try:
# prefix # prefix
self.format_version = definition_bytes[:8].rstrip(b'\0').decode("utf-8") self.format_version = definition_bytes[:8].rstrip(b"\0").decode("utf-8")
self.definition_type: int = definition_bytes[8] self.definition_type: int = definition_bytes[8]
self.data_version = int.from_bytes(definition_bytes[9:13], 'big') self.data_version = int.from_bytes(definition_bytes[9:13], "big")
self.payload_length_in_bytes = int.from_bytes(definition_bytes[13:15], 'big') self.payload_length_in_bytes = int.from_bytes(
definition_bytes[13:15], "big"
)
actual_position += 8 + 1 + 4 + 2 actual_position += 8 + 1 + 4 + 2
# payload # payload
self.payload = definition_bytes[actual_position:(actual_position + self.payload_length_in_bytes)] self.payload = definition_bytes[
self.payload_with_prefix = definition_bytes[:(actual_position + self.payload_length_in_bytes)] actual_position : (actual_position + self.payload_length_in_bytes)
]
self.payload_with_prefix = definition_bytes[
: (actual_position + self.payload_length_in_bytes)
]
actual_position += self.payload_length_in_bytes actual_position += self.payload_length_in_bytes
# suffix - Merkle tree proof and signed root hash # suffix - Merkle tree proof and signed root hash
@ -47,16 +48,20 @@ class EthereumDefinitionParser:
actual_position += 1 actual_position += 1
self.proof: list[bytes] = [] self.proof: list[bytes] = []
for _ in range(self.proof_length): for _ in range(self.proof_length):
self.proof.append(definition_bytes[actual_position:(actual_position + 32)]) self.proof.append(
definition_bytes[actual_position : (actual_position + 32)]
)
actual_position += 32 actual_position += 32
self.signed_tree_root = definition_bytes[actual_position:(actual_position + 64)] self.signed_tree_root = definition_bytes[
actual_position : (actual_position + 64)
]
except IndexError: except IndexError:
raise wire.DataError("Invalid Ethereum definition.") raise wire.DataError("Invalid Ethereum definition.")
def decode_definition( def decode_definition(
definition: bytes, expected_type: EthereumDefinitionType definition: bytes, expected_type: EthereumDefinitionType
) -> NetworkInfo | TokenInfo: ) -> EthereumNetworkInfo | EthereumTokenInfo:
# check network definition # check network definition
parsed_definition = EthereumDefinitionParser(definition) parsed_definition = EthereumDefinitionParser(definition)
@ -75,6 +80,7 @@ def decode_definition(
# at the end verify the signature - compute Merkle tree root hash using provided leaf data and proof # at the end verify the signature - compute Merkle tree root hash using provided leaf data and proof
def compute_mt_root_hash(data: bytes, proof: list[bytes]) -> bytes: def compute_mt_root_hash(data: bytes, proof: list[bytes]) -> bytes:
from trezor.crypto.hashlib import sha256 from trezor.crypto.hashlib import sha256
hash = sha256(b"\x00" + data).digest() hash = sha256(b"\x00" + data).digest()
for p in proof: for p in proof:
hash_a = min(hash, p) hash_a = min(hash, p)
@ -84,13 +90,21 @@ def decode_definition(
return hash return hash
# verify Merkle proof # verify Merkle proof
root_hash = compute_mt_root_hash(parsed_definition.payload_with_prefix, parsed_definition.proof) root_hash = compute_mt_root_hash(
parsed_definition.payload_with_prefix, parsed_definition.proof
)
if not ed25519.verify(DEFINITIONS_PUBLIC_KEY, parsed_definition.signed_tree_root, root_hash): if not ed25519.verify(
DEFINITIONS_PUBLIC_KEY, parsed_definition.signed_tree_root, root_hash
):
error_msg = wire.DataError("Ethereum definition signature is invalid.") error_msg = wire.DataError("Ethereum definition signature is invalid.")
if __debug__: if __debug__:
# check against dev key # check against dev key
if not ed25519.verify(DEFINITIONS_DEV_PUBLIC_KEY, parsed_definition.signed_tree_root, root_hash): if not ed25519.verify(
DEFINITIONS_DEV_PUBLIC_KEY,
parsed_definition.signed_tree_root,
root_hash,
):
raise error_msg raise error_msg
else: else:
raise error_msg raise error_msg
@ -98,35 +112,15 @@ def decode_definition(
# decode it if it's OK # decode it if it's OK
if expected_type == EthereumDefinitionType.NETWORK: if expected_type == EthereumDefinitionType.NETWORK:
info = protobuf.decode(parsed_definition.payload, EthereumNetworkInfo, True) info = protobuf.decode(parsed_definition.payload, EthereumNetworkInfo, True)
# TODO: temporarily convert to internal class
if info is not None:
from .networks import NetworkInfo
info = NetworkInfo(
chain_id=info.chain_id,
slip44=info.slip44,
shortcut=info.shortcut,
name=info.name,
rskip60=info.rskip60
)
else: else:
info = protobuf.decode(parsed_definition.payload, EthereumTokenInfo, True) info = protobuf.decode(parsed_definition.payload, EthereumTokenInfo, True)
# TODO: temporarily convert to internal class
if info is not None:
from .tokens import TokenInfo
info = TokenInfo(
symbol=info.symbol,
decimals=info.decimals,
address=info.address,
chain_id=info.chain_id,
name=info.name,
)
return info return info
def _get_network_definiton(encoded_network_definition: bytes | None, ref_chain_id: int | None = None) -> NetworkInfo | None: def _get_network_definiton(
encoded_network_definition: bytes | None, ref_chain_id: int | None = None
) -> EthereumNetworkInfo | None:
if encoded_network_definition is None and ref_chain_id is None: if encoded_network_definition is None and ref_chain_id is None:
return None return None
@ -134,23 +128,31 @@ def _get_network_definiton(encoded_network_definition: bytes | None, ref_chain_i
# if we have a built-in definition, use it # if we have a built-in definition, use it
network = networks.by_chain_id(ref_chain_id) network = networks.by_chain_id(ref_chain_id)
if network is not None: if network is not None:
return network return network # type: EthereumNetworkInfo
if encoded_network_definition is not None: if encoded_network_definition is not None:
# get definition if it was send # get definition if it was send
network = decode_definition(encoded_network_definition, EthereumDefinitionType.NETWORK) network = decode_definition(
encoded_network_definition, EthereumDefinitionType.NETWORK
)
# check referential chain_id with encoded chain_id # check referential chain_id with encoded chain_id
if ref_chain_id is not None and network.chain_id != ref_chain_id: if ref_chain_id is not None and network.chain_id != ref_chain_id:
raise wire.DataError("Invalid network definition - chain IDs not equal.") raise wire.DataError("Invalid network definition - chain IDs not equal.")
return network return network # type: ignore [Expression of type "EthereumNetworkInfo | EthereumTokenInfo" cannot be assigned to return type "EthereumNetworkInfo | None"]
return None return None
def _get_token_definiton(encoded_token_definition: bytes | None, ref_chain_id: int | None = None, ref_address: bytes | None = None) -> TokenInfo: def _get_token_definiton(
if encoded_token_definition is None and (ref_chain_id is None or ref_address is None): encoded_token_definition: bytes | None,
ref_chain_id: int | None = None,
ref_address: bytes | None = None,
) -> EthereumTokenInfo:
if encoded_token_definition is None and (
ref_chain_id is None or ref_address is None
):
return tokens.UNKNOWN_TOKEN return tokens.UNKNOWN_TOKEN
# if we have a built-in definition, use it # if we have a built-in definition, use it
@ -161,12 +163,13 @@ def _get_token_definiton(encoded_token_definition: bytes | None, ref_chain_id: i
if encoded_token_definition is not None: if encoded_token_definition is not None:
# get definition if it was send # get definition if it was send
token = decode_definition(encoded_token_definition, EthereumDefinitionType.TOKEN) token: EthereumTokenInfo = decode_definition( # type: ignore [Expression of type "EthereumNetworkInfo | EthereumTokenInfo" cannot be assigned to declared type "EthereumTokenInfo"]
encoded_token_definition, EthereumDefinitionType.TOKEN
)
# check token against ref_chain_id and ref_address # check token against ref_chain_id and ref_address
if ( if (ref_chain_id is None or token.chain_id == ref_chain_id) and (
(ref_chain_id is None or token.chain_id == ref_chain_id) ref_address is None or token.address == ref_address
and (ref_address is None or token.address == ref_address)
): ):
return token return token
@ -175,6 +178,7 @@ def _get_token_definiton(encoded_token_definition: bytes | None, ref_chain_id: i
class EthereumDefinitions: class EthereumDefinitions:
"""Class that holds Ethereum definitions - network and tokens. Prefers built-in definitions over encoded ones.""" """Class that holds Ethereum definitions - network and tokens. Prefers built-in definitions over encoded ones."""
def __init__( def __init__(
self, self,
encoded_network_definition: bytes | None = None, encoded_network_definition: bytes | None = None,
@ -183,20 +187,22 @@ class EthereumDefinitions:
ref_token_address: bytes | None = None, ref_token_address: bytes | None = None,
) -> None: ) -> None:
self.network = _get_network_definiton(encoded_network_definition, ref_chain_id) self.network = _get_network_definiton(encoded_network_definition, ref_chain_id)
self.token_dict: dict[bytes, TokenInfo] = dict() self.token_dict: dict[bytes, EthereumTokenInfo] = dict()
# if we have some network, we can try to get token # if we have some network, we can try to get token
if self.network is not None: if self.network is not None:
token = _get_token_definiton(encoded_token_definition, self.network.chain_id, ref_token_address) token = _get_token_definiton(
encoded_token_definition, self.network.chain_id, ref_token_address
)
if token is not tokens.UNKNOWN_TOKEN: if token is not tokens.UNKNOWN_TOKEN:
self.token_dict[token.address] = token self.token_dict[token.address] = token
def get_definitions_from_msg(msg: MessageType) -> EthereumDefinitions: def get_definitions_from_msg(msg: Any) -> EthereumDefinitions:
encoded_network_definition: bytes | None = None encoded_network_definition: bytes | None = None
encoded_token_definition: bytes | None = None encoded_token_definition: bytes | None = None
chain_id: int | None = None chain_id: int | None = None
token_address: str | None = None token_address: bytes | None = None
# first try to get both definitions # first try to get both definitions
try: try:
@ -225,4 +231,6 @@ def get_definitions_from_msg(msg: MessageType) -> EthereumDefinitions:
except AttributeError: except AttributeError:
pass pass
return EthereumDefinitions(encoded_network_definition, encoded_token_definition, chain_id, token_address) return EthereumDefinitions(
encoded_network_definition, encoded_token_definition, chain_id, token_address
)

View File

@ -20,7 +20,10 @@ if TYPE_CHECKING:
@with_keychain_from_path_and_defs(*PATTERNS_ADDRESS) @with_keychain_from_path_and_defs(*PATTERNS_ADDRESS)
async def get_address( async def get_address(
ctx: Context, msg: EthereumGetAddress, keychain: Keychain, defs: definitions.EthereumDefinitions ctx: Context,
msg: EthereumGetAddress,
keychain: Keychain,
defs: definitions.EthereumDefinitions,
) -> EthereumAddress: ) -> EthereumAddress:
from trezor.messages import EthereumAddress from trezor.messages import EthereumAddress
from trezor.ui.layouts import show_address from trezor.ui.layouts import show_address
@ -36,7 +39,7 @@ async def get_address(
if len(msg.address_n) > 1: # path has slip44 network identifier if len(msg.address_n) > 1: # path has slip44 network identifier
slip44 = msg.address_n[1] & 0x7FFF_FFFF slip44 = msg.address_n[1] & 0x7FFF_FFFF
if slip44 == defs.network.slip44: if defs.network is not None and slip44 == defs.network.slip44:
network = defs.network network = defs.network
else: else:
network = networks.by_slip44(slip44) network = networks.by_slip44(slip44)

View File

@ -5,10 +5,12 @@ from .networks import by_chain_id
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import EthereumFieldType from trezor.messages import EthereumFieldType
from .networks import NetworkInfo from .networks import EthereumNetworkInfo
def address_from_bytes(address_bytes: bytes, network: NetworkInfo = by_chain_id(1)) -> str: def address_from_bytes(
address_bytes: bytes, network: EthereumNetworkInfo | None = by_chain_id(1)
) -> str:
""" """
Converts address in bytes to a checksummed string as defined Converts address in bytes to a checksummed string as defined
in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md

View File

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from apps.common import paths from apps.common import paths
from apps.common.keychain import get_keychain from apps.common.keychain import get_keychain
from . import CURVE, networks, definitions from . import CURVE, definitions, networks
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Awaitable, Callable, Iterable, TypeVar from typing import Awaitable, Callable, Iterable, TypeVar
@ -15,20 +15,31 @@ if TYPE_CHECKING:
from trezor.messages import ( from trezor.messages import (
EthereumGetAddress, EthereumGetAddress,
EthereumGetPublicKey, EthereumGetPublicKey,
EthereumNetworkInfo,
EthereumSignMessage, EthereumSignMessage,
EthereumSignTx, EthereumSignTx,
EthereumSignTxEIP1559, EthereumSignTxEIP1559,
EthereumSignTypedData, EthereumSignTypedData,
) )
from apps.common.keychain import MsgIn as MsgInGeneric, MsgOut, Handler, HandlerWithKeychain from apps.common.keychain import (
MsgIn as MsgInGeneric,
MsgOut,
Handler,
HandlerWithKeychain,
)
# messages for "with_keychain_from_path" decorator # messages for "with_keychain_from_path" decorator
MsgInKeychainPath = TypeVar("MsgInKeychainPath", bound=EthereumGetPublicKey) MsgInKeychainPath = TypeVar("MsgInKeychainPath", bound=EthereumGetPublicKey)
# messages for "with_keychain_from_path_and_defs" decorator # messages for "with_keychain_from_path_and_defs" decorator
MsgInKeychainPathDefs = TypeVar("MsgInKeychainPathDefs", bound=EthereumGetAddress | EthereumSignMessage | EthereumSignTypedData) MsgInKeychainPathDefs = TypeVar(
"MsgInKeychainPathDefs",
bound=EthereumGetAddress | EthereumSignMessage | EthereumSignTypedData,
)
# messages for "with_keychain_from_chain_id_and_defs" decorator # messages for "with_keychain_from_chain_id_and_defs" decorator
MsgInKeychainChainIdDefs = TypeVar("MsgInKeychainChainIdDefs", bound=EthereumSignTx | EthereumSignTxEIP1559) MsgInKeychainChainIdDefs = TypeVar(
"MsgInKeychainChainIdDefs", bound=EthereumSignTx | EthereumSignTxEIP1559
)
# TODO: check the types of messages # TODO: check the types of messages
HandlerWithKeychainAndDefinitions = Callable[[Context, MsgInGeneric, Keychain, definitions.EthereumDefinitions], Awaitable[MsgOut]] HandlerWithKeychainAndDefinitions = Callable[[Context, MsgInGeneric, Keychain, definitions.EthereumDefinitions], Awaitable[MsgOut]]
@ -48,7 +59,9 @@ PATTERNS_ADDRESS = (
def _schemas_from_address_n( def _schemas_from_address_n(
patterns: Iterable[str], address_n: paths.Bip32Path, network_info: networks.NetworkInfo | None patterns: Iterable[str],
address_n: paths.Bip32Path,
network_info: EthereumNetworkInfo | None,
) -> Iterable[paths.PathSchema]: ) -> Iterable[paths.PathSchema]:
if len(address_n) < 2: if len(address_n) < 2:
return () return ()
@ -104,7 +117,9 @@ def with_keychain_from_path_and_defs(
return decorator return decorator
def _schemas_from_chain_id(network_info: networks.NetworkInfo | None) -> Iterable[paths.PathSchema]: def _schemas_from_chain_id(
network_info: EthereumNetworkInfo | None,
) -> Iterable[paths.PathSchema]:
slip44_id: tuple[int, ...] slip44_id: tuple[int, ...]
if network_info is None: if network_info is None:
# allow Ethereum or testnet paths for unknown networks # allow Ethereum or testnet paths for unknown networks

View File

@ -21,15 +21,15 @@ if TYPE_CHECKING:
from trezor.wire import Context from trezor.wire import Context
from . import tokens from . import tokens
from . import tokens from trezor.messages import EthereumNetworkInfo, EthereumTokenInfo
def require_confirm_tx( def require_confirm_tx(
ctx: Context, ctx: Context,
to_bytes: bytes, to_bytes: bytes,
value: int, value: int,
network: networks.NetworkInfo, network: EthereumNetworkInfo | None,
token: tokens.TokenInfo, token: EthereumTokenInfo | None,
) -> Awaitable[None]: ) -> Awaitable[None]:
from .helpers import address_from_bytes from .helpers import address_from_bytes
from trezor.ui.layouts import confirm_output from trezor.ui.layouts import confirm_output
@ -53,8 +53,8 @@ async def require_confirm_fee(
spending: int, spending: int,
gas_price: int, gas_price: int,
gas_limit: int, gas_limit: int,
network: networks.NetworkInfo, network: EthereumNetworkInfo | None,
token: tokens.TokenInfo, token: EthereumTokenInfo | None,
) -> None: ) -> None:
await confirm_amount( await confirm_amount(
ctx, ctx,
@ -77,8 +77,8 @@ async def require_confirm_eip1559_fee(
max_priority_fee: int, max_priority_fee: int,
max_gas_fee: int, max_gas_fee: int,
gas_limit: int, gas_limit: int,
network: networks.NetworkInfo, network: EthereumNetworkInfo | None,
token: tokens.TokenInfo, token: EthereumTokenInfo | None,
) -> None: ) -> None:
await confirm_amount( await confirm_amount(
ctx, ctx,
@ -253,7 +253,9 @@ async def confirm_typed_value(
def format_ethereum_amount( def format_ethereum_amount(
value: int, token: tokens.TokenInfo | None, network_info: networks.NetworkInfo | None value: int,
token: EthereumTokenInfo | None,
network_info: EthereumNetworkInfo | None,
) -> str: ) -> str:
from trezor.strings import format_amount from trezor.strings import format_amount

View File

@ -8,6 +8,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.common.paths import HARDENED from apps.common.paths import HARDENED
from trezor.messages import EthereumNetworkInfo
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Iterator from typing import Iterator
@ -25,11 +26,11 @@ if TYPE_CHECKING:
UNKNOWN_NETWORK_SHORTCUT = "UNKN" UNKNOWN_NETWORK_SHORTCUT = "UNKN"
def by_chain_id(chain_id: int) -> "NetworkInfo" | None: def by_chain_id(chain_id: int) -> EthereumNetworkInfo | None:
for n in _networks_iterator(): for n in _networks_iterator():
n_chain_id = n[0] n_chain_id = n[0]
if n_chain_id == chain_id: if n_chain_id == chain_id:
return NetworkInfo( return EthereumNetworkInfo(
chain_id=n[0], chain_id=n[0],
slip44=n[1], slip44=n[1],
shortcut=n[2], shortcut=n[2],
@ -39,11 +40,11 @@ def by_chain_id(chain_id: int) -> "NetworkInfo" | None:
return None return None
def by_slip44(slip44: int) -> "NetworkInfo" | None: def by_slip44(slip44: int) -> EthereumNetworkInfo | None:
for n in _networks_iterator(): for n in _networks_iterator():
n_slip44 = n[1] n_slip44 = n[1]
if n_slip44 == slip44: if n_slip44 == slip44:
return NetworkInfo( return EthereumNetworkInfo(
chain_id=n[0], chain_id=n[0],
slip44=n[1], slip44=n[1],
shortcut=n[2], shortcut=n[2],
@ -59,17 +60,6 @@ def all_slip44_ids_hardened() -> Iterator[int]:
yield n[1] | HARDENED yield n[1] | HARDENED
class NetworkInfo:
def __init__(
self, chain_id: int, slip44: int, shortcut: str, name: str, rskip60: bool
) -> None:
self.chain_id = chain_id
self.slip44 = slip44
self.shortcut = shortcut
self.name = name
self.rskip60 = rskip60
# fmt: off # fmt: off
def _networks_iterator() -> Iterator[NetworkInfoTuple]: def _networks_iterator() -> Iterator[NetworkInfoTuple]:
yield ( yield (

View File

@ -8,6 +8,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from apps.common.paths import HARDENED from apps.common.paths import HARDENED
from trezor.messages import EthereumNetworkInfo
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Iterator from typing import Iterator
@ -25,11 +26,11 @@ if TYPE_CHECKING:
UNKNOWN_NETWORK_SHORTCUT = "UNKN" UNKNOWN_NETWORK_SHORTCUT = "UNKN"
def by_chain_id(chain_id: int) -> "NetworkInfo" | None: def by_chain_id(chain_id: int) -> EthereumNetworkInfo | None:
for n in _networks_iterator(): for n in _networks_iterator():
n_chain_id = n[0] n_chain_id = n[0]
if n_chain_id == chain_id: if n_chain_id == chain_id:
return NetworkInfo( return EthereumNetworkInfo(
chain_id=n[0], chain_id=n[0],
slip44=n[1], slip44=n[1],
shortcut=n[2], shortcut=n[2],
@ -39,11 +40,11 @@ def by_chain_id(chain_id: int) -> "NetworkInfo" | None:
return None return None
def by_slip44(slip44: int) -> "NetworkInfo" | None: def by_slip44(slip44: int) -> EthereumNetworkInfo | None:
for n in _networks_iterator(): for n in _networks_iterator():
n_slip44 = n[1] n_slip44 = n[1]
if n_slip44 == slip44: if n_slip44 == slip44:
return NetworkInfo( return EthereumNetworkInfo(
chain_id=n[0], chain_id=n[0],
slip44=n[1], slip44=n[1],
shortcut=n[2], shortcut=n[2],
@ -59,17 +60,6 @@ def all_slip44_ids_hardened() -> Iterator[int]:
yield n[1] | HARDENED yield n[1] | HARDENED
class NetworkInfo:
def __init__(
self, chain_id: int, slip44: int, shortcut: str, name: str, rskip60: bool
) -> None:
self.chain_id = chain_id
self.slip44 = slip44
self.shortcut = shortcut
self.name = name
self.rskip60 = rskip60
# fmt: off # fmt: off
def _networks_iterator() -> Iterator[NetworkInfoTuple]: def _networks_iterator() -> Iterator[NetworkInfoTuple]:
% for n in supported_on("trezor2", eth): % for n in supported_on("trezor2", eth):

View File

@ -25,7 +25,10 @@ def message_digest(message: bytes) -> bytes:
@with_keychain_from_path_and_defs(*PATTERNS_ADDRESS) @with_keychain_from_path_and_defs(*PATTERNS_ADDRESS)
async def sign_message( async def sign_message(
ctx: Context, msg: EthereumSignMessage, keychain: Keychain, defs: definitions.EthereumDefinitions ctx: Context,
msg: EthereumSignMessage,
keychain: Keychain,
defs: definitions.EthereumDefinitions,
) -> EthereumMessageSignature: ) -> EthereumMessageSignature:
from trezor.crypto.curve import secp256k1 from trezor.crypto.curve import secp256k1
from trezor.messages import EthereumMessageSignature from trezor.messages import EthereumMessageSignature

View File

@ -9,7 +9,7 @@ from .keychain import with_keychain_from_chain_id_and_defs
if TYPE_CHECKING: if TYPE_CHECKING:
from apps.common.keychain import Keychain from apps.common.keychain import Keychain
from trezor.messages import EthereumSignTx, EthereumTxAck from trezor.messages import EthereumSignTx, EthereumTxAck, EthereumTokenInfo
from trezor.wire import Context from trezor.wire import Context
from .keychain import MsgInKeychainChainIdDefs from .keychain import MsgInKeychainChainIdDefs
@ -45,7 +45,9 @@ async def sign_tx(
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
# Handle ERC20s # Handle ERC20s
token, address_bytes, recipient, value = await handle_erc20(ctx, msg, defs.token_dict) token, address_bytes, recipient, value = await handle_erc20(
ctx, msg, defs.token_dict
)
data_total = msg.data_length data_total = msg.data_length
@ -100,13 +102,14 @@ async def sign_tx(
async def handle_erc20( async def handle_erc20(
ctx: Context, msg: MsgInKeychainChainIdDefs, token_dict: dict[bytes, tokens.TokenInfo] ctx: Context,
) -> tuple[tokens.TokenInfo | None, bytes, bytes, int]: msg: MsgInKeychainChainIdDefs, # type: ignore [TypeVar "MsgInKeychainChainIdDefs" appears only once in generic function signature]
token_dict: dict[bytes, EthereumTokenInfo],
) -> tuple[EthereumTokenInfo | None, bytes, bytes, int]:
from .layout import require_confirm_unknown_token from .layout import require_confirm_unknown_token
from . import tokens from . import tokens
data_initial_chunk = msg.data_initial_chunk # local_cache_attribute data_initial_chunk = msg.data_initial_chunk # local_cache_attribute
token = None token = None
address_bytes = recipient = bytes_from_address(msg.to) address_bytes = recipient = bytes_from_address(msg.to)
value = int.from_bytes(msg.value, "big") value = int.from_bytes(msg.value, "big")
@ -185,7 +188,7 @@ def _sign_digest(
return req return req
def check_common_fields(msg: MsgInKeychainChainIdDefs) -> None: def check_common_fields(msg: MsgInKeychainChainIdDefs) -> None: # type: ignore [TypeVar "MsgInKeychainChainIdDefs" appears only once in generic function signature]
data_length = msg.data_length # local_cache_attribute data_length = msg.data_length # local_cache_attribute
if data_length > 0: if data_length > 0:

View File

@ -57,7 +57,9 @@ async def sign_tx_eip1559(
await paths.validate_path(ctx, keychain, msg.address_n) await paths.validate_path(ctx, keychain, msg.address_n)
# Handle ERC20s # Handle ERC20s
token, address_bytes, recipient, value = await handle_erc20(ctx, msg, defs.token_dict) token, address_bytes, recipient, value = await handle_erc20(
ctx, msg, defs.token_dict
)
data_total = msg.data_length data_total = msg.data_length

View File

@ -29,7 +29,10 @@ _MAX_VALUE_BYTE_SIZE = const(1024)
@with_keychain_from_path_and_defs(*PATTERNS_ADDRESS) @with_keychain_from_path_and_defs(*PATTERNS_ADDRESS)
async def sign_typed_data( async def sign_typed_data(
ctx: Context, msg: EthereumSignTypedData, keychain: Keychain, defs: definitions.EthereumDefinitions ctx: Context,
msg: EthereumSignTypedData,
keychain: Keychain,
defs: definitions.EthereumDefinitions,
) -> EthereumTypedDataSignature: ) -> EthereumTypedDataSignature:
from trezor.crypto.curve import secp256k1 from trezor.crypto.curve import secp256k1
from apps.common import paths from apps.common import paths

View File

@ -35,6 +35,7 @@ class TokenInfo:
UNKNOWN_TOKEN = TokenInfo("Wei UNKN", 0, b"", 0) UNKNOWN_TOKEN = TokenInfo("Wei UNKN", 0, b"", 0)
# TODO: delete completely
def token_by_chain_address(chain_id: int, address: bytes) -> TokenInfo: def token_by_chain_address(chain_id: int, address: bytes) -> TokenInfo:
for addr, symbol, decimal in _token_iterator(chain_id): for addr, symbol, decimal in _token_iterator(chain_id):
if address == addr: if address == addr:

View File

@ -14,6 +14,7 @@
# of it has enough collision-resistance.) # of it has enough collision-resistance.)
# (In the if-tree approach the address length did not have any effect whatsoever.) # (In the if-tree approach the address length did not have any effect whatsoever.)
from trezor.messages import EthereumTokenInfo
from typing import Iterator from typing import Iterator
<% <%
@ -26,39 +27,38 @@ def group_tokens(tokens):
return r return r
%>\ %>\
class TokenInfo: UNKNOWN_TOKEN = EthereumTokenInfo(
def __init__( symbol="Wei UNKN",
self, decimals=0,
symbol: str, address=b"",
decimals: int, chain_id=0,
address: bytes, name="Unknown token",
chain_id: int, )
name: str = None,
) -> None:
self.symbol = symbol
self.decimals = decimals
self.address = address
self.chain_id = chain_id
self.name = name
UNKNOWN_TOKEN = TokenInfo("Wei UNKN", 0, b"", 0)
def token_by_chain_address(chain_id: int, address: bytes) -> TokenInfo: def token_by_chain_address(chain_id: int, address: bytes) -> EthereumTokenInfo:
for addr, symbol, decimal in _token_iterator(chain_id): for addr, symbol, decimal, name in _token_iterator(chain_id):
if address == addr: if address == addr:
return TokenInfo(symbol, decimal) return EthereumTokenInfo(
symbol=symbol,
decimals=decimal,
address=address,
chain_id=chain_id,
name=name,
)
return UNKNOWN_TOKEN return UNKNOWN_TOKEN
def _token_iterator(chain_id: int) -> Iterator[tuple[bytes, str, int]]: def _token_iterator(chain_id: int) -> Iterator[tuple[bytes, str, int]]:
% for token_chain_id, tokens in group_tokens(supported_on("trezor2", erc20)).items(): % for token_chain_id, tokens in group_tokens(supported_on("trezor2", erc20)).items():
if chain_id == ${token_chain_id}: if chain_id == ${token_chain_id}: # ${tokens[0].chain}
% for t in tokens: % for t in tokens:
yield ( # address, symbol, decimals yield ( # address, symbol, decimals, name
${black_repr(t.address_bytes)}, ${black_repr(t.address_bytes)},
${black_repr(t.symbol)}, ${black_repr(t.symbol)},
${t.decimals}, ${t.decimals},
${black_repr(t.name.strip())},
) )
% endfor % endfor
% endfor % endfor

View File

@ -3,14 +3,14 @@ from trezor.utils import ensure
from ubinascii import hexlify, unhexlify # noqa: F401 from ubinascii import hexlify, unhexlify # noqa: F401
from trezor import messages from trezor import messages
from apps.ethereum import networks, tokens from apps.ethereum import tokens
EXPECTED_FORMAT_VERSION = 1 EXPECTED_FORMAT_VERSION = 1
EXPECTED_DATA_VERSION = 1663054984 # unix epoch time EXPECTED_DATA_VERSION = 1663054984 # unix epoch time
class InfoWithDefinition(): class InfoWithDefinition():
def __init__(self, definition, info): def __init__(self, definition: bytes | None, info: messages.EthereumNetworkInfo | messages.EthereumTokenInfo):
self.definition = definition self.definition = definition
self.info = info self.info = info
@ -21,7 +21,7 @@ NETWORKS = {
# Ethereum # Ethereum
1: InfoWithDefinition( 1: InfoWithDefinition(
definition=None, # built-in definitions are not encoded definition=None, # built-in definitions are not encoded
info=networks.NetworkInfo( info=messages.EthereumNetworkInfo(
chain_id=1, chain_id=1,
slip44=60, slip44=60,
shortcut="ETH", shortcut="ETH",
@ -32,7 +32,7 @@ NETWORKS = {
# Rinkeby # Rinkeby
4: InfoWithDefinition( 4: InfoWithDefinition(
definition=unhexlify("74727a643100000000632034880015080410011a047452494e220752696e6b65627928000e8cc47ed4e657d9a9b98e1dd02164320c54a9724e17f91d1d79f6760169582c98ec70ca6f4e94d27e574175c59d2ae04e0cd30b65fb19acd8d2c5fb90bcb7db96f6102e4182c0cef5f412ac3c5fa94f9505b4df2633a0f7bdffa309588d722415624adeb8f329b1572ff9dfc81fbc86e61f1fcb2369f51ba85ea765c908ac254ba996f842a6277583f8d02f149c78bc0eeb8f3d41240403f85785dc3a3925ea768d76aae12342c8a24de223c1ea75e5f07f6b94b8f22189413631eed3c9a362b4501f68b645aa487b9d159a8161404a218507641453ebf045cec56710bb7d873e102777695b56903766e1af16f95576ec4f41874bdaf80cec02ee067d30e721515564d4f30fa74a6c61eb784ea65cc881ead7af2ffac02d5bf1fe1a756918fe37b74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"), definition=unhexlify("74727a643100000000632034880015080410011a047452494e220752696e6b65627928000e8cc47ed4e657d9a9b98e1dd02164320c54a9724e17f91d1d79f6760169582c98ec70ca6f4e94d27e574175c59d2ae04e0cd30b65fb19acd8d2c5fb90bcb7db96f6102e4182c0cef5f412ac3c5fa94f9505b4df2633a0f7bdffa309588d722415624adeb8f329b1572ff9dfc81fbc86e61f1fcb2369f51ba85ea765c908ac254ba996f842a6277583f8d02f149c78bc0eeb8f3d41240403f85785dc3a3925ea768d76aae12342c8a24de223c1ea75e5f07f6b94b8f22189413631eed3c9a362b4501f68b645aa487b9d159a8161404a218507641453ebf045cec56710bb7d873e102777695b56903766e1af16f95576ec4f41874bdaf80cec02ee067d30e721515564d4f30fa74a6c61eb784ea65cc881ead7af2ffac02d5bf1fe1a756918fe37b74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"),
info=networks.NetworkInfo( info=messages.EthereumNetworkInfo(
chain_id=4, chain_id=4,
slip44=1, slip44=1,
shortcut="tRIN", shortcut="tRIN",
@ -43,7 +43,7 @@ NETWORKS = {
# Ubiq # Ubiq
8: InfoWithDefinition( 8: InfoWithDefinition(
definition=unhexlify("74727a6431000000006320348800110808106c1a0355425122045562697128000e5641d82e3622b4e6addd4354efd933cf15947d1d608a60d324d1156b5a4999f70c41beb85bd866aa3059123447dfeef2e1b6c009b66ac8d04ebbca854ad30049edbbb2fbfda3bfedc6fdb4a76f1db8a4f210bd89d3c3ec1761157b0ec2b13e2f624adeb8f329b1572ff9dfc81fbc86e61f1fcb2369f51ba85ea765c908ac254ba996f842a6277583f8d02f149c78bc0eeb8f3d41240403f85785dc3a3925ea768d76aae12342c8a24de223c1ea75e5f07f6b94b8f22189413631eed3c9a362b4501f68b645aa487b9d159a8161404a218507641453ebf045cec56710bb7d873e102777695b56903766e1af16f95576ec4f41874bdaf80cec02ee067d30e721515564d4f30fa74a6c61eb784ea65cc881ead7af2ffac02d5bf1fe1a756918fe37b74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"), definition=unhexlify("74727a6431000000006320348800110808106c1a0355425122045562697128000e5641d82e3622b4e6addd4354efd933cf15947d1d608a60d324d1156b5a4999f70c41beb85bd866aa3059123447dfeef2e1b6c009b66ac8d04ebbca854ad30049edbbb2fbfda3bfedc6fdb4a76f1db8a4f210bd89d3c3ec1761157b0ec2b13e2f624adeb8f329b1572ff9dfc81fbc86e61f1fcb2369f51ba85ea765c908ac254ba996f842a6277583f8d02f149c78bc0eeb8f3d41240403f85785dc3a3925ea768d76aae12342c8a24de223c1ea75e5f07f6b94b8f22189413631eed3c9a362b4501f68b645aa487b9d159a8161404a218507641453ebf045cec56710bb7d873e102777695b56903766e1af16f95576ec4f41874bdaf80cec02ee067d30e721515564d4f30fa74a6c61eb784ea65cc881ead7af2ffac02d5bf1fe1a756918fe37b74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"),
info=networks.NetworkInfo( info=messages.EthereumNetworkInfo(
chain_id=8, chain_id=8,
slip44=108, slip44=108,
shortcut="UBQ", shortcut="UBQ",
@ -54,7 +54,7 @@ NETWORKS = {
# Ethereum Classic # Ethereum Classic
61: InfoWithDefinition( 61: InfoWithDefinition(
definition=unhexlify("74727a64310000000063203488001d083d103d1a034554432210457468657265756d20436c617373696328000e6b891a57fe4c38c54b475f22f0d9242dd8ddab0b4f360bd86e37e2e8b79de5ef29237436351f7bc924cd110716b5adde7c28c03d76ac83b091dbce1b5d7d0edbddb221bd894806f7ea1b195443176e06830a83c0204e33f19c51d2fccc3a9f80ac2cca38822db998ddf76778dada240d39b3c6193c6335d7c693dea90d19a41f86855375c2f48c18cdc012ccac771aa316d776c8721c2b1f6d5980808337dfdae13b5be07e3cbc3526119b88c5eb44be0b1dab1094a5ec5215b47daf91736d16501f68b645aa487b9d159a8161404a218507641453ebf045cec56710bb7d873e102777695b56903766e1af16f95576ec4f41874bdaf80cec02ee067d30e721515564d4f30fa74a6c61eb784ea65cc881ead7af2ffac02d5bf1fe1a756918fe37b74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"), definition=unhexlify("74727a64310000000063203488001d083d103d1a034554432210457468657265756d20436c617373696328000e6b891a57fe4c38c54b475f22f0d9242dd8ddab0b4f360bd86e37e2e8b79de5ef29237436351f7bc924cd110716b5adde7c28c03d76ac83b091dbce1b5d7d0edbddb221bd894806f7ea1b195443176e06830a83c0204e33f19c51d2fccc3a9f80ac2cca38822db998ddf76778dada240d39b3c6193c6335d7c693dea90d19a41f86855375c2f48c18cdc012ccac771aa316d776c8721c2b1f6d5980808337dfdae13b5be07e3cbc3526119b88c5eb44be0b1dab1094a5ec5215b47daf91736d16501f68b645aa487b9d159a8161404a218507641453ebf045cec56710bb7d873e102777695b56903766e1af16f95576ec4f41874bdaf80cec02ee067d30e721515564d4f30fa74a6c61eb784ea65cc881ead7af2ffac02d5bf1fe1a756918fe37b74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"),
info=networks.NetworkInfo( info=messages.EthereumNetworkInfo(
chain_id=61, chain_id=61,
slip44=61, slip44=61,
shortcut="ETC", shortcut="ETC",
@ -80,21 +80,23 @@ TOKENS = {
# AAVE # AAVE
"7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": InfoWithDefinition( "7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": InfoWithDefinition(
definition=None, # built-in definitions are not encoded definition=None, # built-in definitions are not encoded
info=tokens.TokenInfo( info=messages.EthereumTokenInfo(
symbol="AAVE", symbol="AAVE",
decimals=18, decimals=18,
address=unhexlify("7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9"), address=unhexlify("7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9"),
chain_id=1, chain_id=1,
name="Aave",
), ),
), ),
# TrueAUD # TrueAUD
"00006100f7090010005f1bd7ae6122c3c2cf0090": InfoWithDefinition( "00006100f7090010005f1bd7ae6122c3c2cf0090": InfoWithDefinition(
definition=unhexlify("74727a6431000000016320348800290a045441554410121a1400006100f7090010005f1bd7ae6122c3c2cf009020012a07547275654155440e310dad13f7d3012903a9a457134c9f38c62c04370cb92c7a528838e30a032dffbceeaa2aa849e590c4e6dbc69b0ea5359f3527b95b56ab59a33dc584105b35ea7c06afc296cc1c1e58cc3d6b461631c4c770b9409837ab3d29bc1b666fb9cf5245c4c218b0e9521c185d102f596905ba860e6f56a0a8b394f943855c74eea6fcac87210a9988ac02803f4cc61cf78e7e2409175a75f4f3a82eb84b1f2d1ea8177d5dccd62949d80d7942105e22a452be01859fe816736e803b120fb9bcc0c1117180dbda19e1ad1aafb9b9f1555c75275820bf7c1e568bcb265bdc4dfdae0511782026e11a151f6894d11128327c8c42958c9ae900af970fec13a11ffdeba6ac10733ca55a906142e0b9130312e8e85606108612581aca9087c452f38f14185db74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"), definition=unhexlify("74727a6431000000016320348800290a045441554410121a1400006100f7090010005f1bd7ae6122c3c2cf009020012a07547275654155440e310dad13f7d3012903a9a457134c9f38c62c04370cb92c7a528838e30a032dffbceeaa2aa849e590c4e6dbc69b0ea5359f3527b95b56ab59a33dc584105b35ea7c06afc296cc1c1e58cc3d6b461631c4c770b9409837ab3d29bc1b666fb9cf5245c4c218b0e9521c185d102f596905ba860e6f56a0a8b394f943855c74eea6fcac87210a9988ac02803f4cc61cf78e7e2409175a75f4f3a82eb84b1f2d1ea8177d5dccd62949d80d7942105e22a452be01859fe816736e803b120fb9bcc0c1117180dbda19e1ad1aafb9b9f1555c75275820bf7c1e568bcb265bdc4dfdae0511782026e11a151f6894d11128327c8c42958c9ae900af970fec13a11ffdeba6ac10733ca55a906142e0b9130312e8e85606108612581aca9087c452f38f14185db74828a24b640025cd79443ada60063e3034444fc49ed6055dbba6a09fa4484c42cb85abb49103dc8c781c8f190c4632e2dec30081770448021313955dbb49e8a02fd49b34d030280452fe0a5c3bcba4958bc287c67e12519be4f4aec7ab0c8e574e53a663f635f75508f23d92c77b2147f29feb79c38d0f793fba295aae605c7e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"),
info=tokens.TokenInfo( info=messages.EthereumTokenInfo(
symbol="TAUD", symbol="TAUD",
decimals=18, decimals=18,
address=unhexlify("00006100f7090010005f1bd7ae6122c3c2cf0090"), address=unhexlify("00006100f7090010005f1bd7ae6122c3c2cf0090"),
chain_id=1, chain_id=1,
name="TrueAUD",
), ),
), ),
}, },
@ -102,44 +104,20 @@ TOKENS = {
# Karma Token # Karma Token
"275a5b346599b56917e7b1c9de019dcf9ead861a": InfoWithDefinition( "275a5b346599b56917e7b1c9de019dcf9ead861a": InfoWithDefinition(
definition=unhexlify("74727a64310000000163203488002b0a024b4310121a14275a5b346599b56917e7b1c9de019dcf9ead861a20042a0b4b61726d6120546f6b656e0e2b3cb176ff5a2cf431620c1a7eee9aa297f5de36d29ae6d423166cf7391e41c5826c57f30b11421a4bf10f336f12050f6d959e02bfb17a8ce7ae15087d4f083124c0cebed2ce45b15b2608b1a8f0ee443e8c4f33111d880a6a3c09a77c627f82d68b62a1bd39975b2a2c86f196b9a3dcb62bdc3554fbf85b75331bc0d39f23a46f5ed91f208757d1136bb20b3618294fbfb0a826e9c09e392fe8109181bc6c28cad78db1987947f461bfc1042b88a91d6d61297d0cf194dfeea981b4515c2ed09dc2966671f5c715c64ceb25e53e1df3c7234e3e0ddf0dcd54d40fde0c51903685f9dc7fa69c71184f17af852e74490ea7286e89a0aa4770629664f7dd8eab8c4e009ff4c24682f85f7e01d4e10ae5c06212d5a4f43bac2b4f0e79383666ef12054ddbf757809aa6b446d65f7fd1bdd76fb1d7770398bd17af50635027e680801d244bd7b4f14c57edc3cd961722315e076120bf1d35db8520edb812bfbb5bab8ff57cc2dc1b3d1f9d95b33dba5d759aef1123f2ef346b6328973fba204fd745e644c8e492f9a76c0019b2cf21715fba682b46b9c58013e0b0927e5272c808a67e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"), definition=unhexlify("74727a64310000000163203488002b0a024b4310121a14275a5b346599b56917e7b1c9de019dcf9ead861a20042a0b4b61726d6120546f6b656e0e2b3cb176ff5a2cf431620c1a7eee9aa297f5de36d29ae6d423166cf7391e41c5826c57f30b11421a4bf10f336f12050f6d959e02bfb17a8ce7ae15087d4f083124c0cebed2ce45b15b2608b1a8f0ee443e8c4f33111d880a6a3c09a77c627f82d68b62a1bd39975b2a2c86f196b9a3dcb62bdc3554fbf85b75331bc0d39f23a46f5ed91f208757d1136bb20b3618294fbfb0a826e9c09e392fe8109181bc6c28cad78db1987947f461bfc1042b88a91d6d61297d0cf194dfeea981b4515c2ed09dc2966671f5c715c64ceb25e53e1df3c7234e3e0ddf0dcd54d40fde0c51903685f9dc7fa69c71184f17af852e74490ea7286e89a0aa4770629664f7dd8eab8c4e009ff4c24682f85f7e01d4e10ae5c06212d5a4f43bac2b4f0e79383666ef12054ddbf757809aa6b446d65f7fd1bdd76fb1d7770398bd17af50635027e680801d244bd7b4f14c57edc3cd961722315e076120bf1d35db8520edb812bfbb5bab8ff57cc2dc1b3d1f9d95b33dba5d759aef1123f2ef346b6328973fba204fd745e644c8e492f9a76c0019b2cf21715fba682b46b9c58013e0b0927e5272c808a67e8226523edefc6ad1eefe088e5b8376028bf90116ece4fb876510b4ae1c89686dbcaacbbac8225baba429ca376fafac50f4bd1ff4ce1c61dd53318d0718bf513ea6f770cce81e07a653622e4dbd03bdaa570bfe43219eb0d4fab725c9a8da04"),
info=tokens.TokenInfo( info=messages.EthereumTokenInfo(
symbol="KC", symbol="KC",
decimals=18, decimals=18,
address=unhexlify("275a5b346599b56917e7b1c9de019dcf9ead861a"), address=unhexlify("275a5b346599b56917e7b1c9de019dcf9ead861a"),
chain_id=4, chain_id=4,
name="Karma Token",
), ),
), ),
}, },
} }
def equalNetworkInfo(n1: networks.NetworkInfo, n2: networks.NetworkInfo, msg: str = '') -> bool: def construct_network_info(chain_id: int = 0, slip44: int = 0, shortcut: str = "", name: str = "", rskip60: bool = False) -> messages.EthereumNetworkInfo:
ensure( return messages.EthereumNetworkInfo(
cond=(
n1.chain_id == n2.chain_id
and n1.slip44 == n2.slip44
and n1.shortcut == n2.shortcut
and n1.name == n2.name
and n1.rskip60 == n2.rskip60
),
msg=msg,
)
def equalTokenInfo(t1: tokens.TokenInfo, t2: tokens.TokenInfo, msg: str = '') -> bool:
ensure(
cond=(
t1.symbol == t2.symbol
and t1.decimals == t2.decimals
and t1.address == t2.address
and t1.chain_id == t2.chain_id
),
msg=msg,
)
def construct_network_info(chain_id: int = 0, slip44: int = 0, shortcut: str = "", name: str = "", rskip60: bool = False) -> networks.NetworkInfo:
return networks.NetworkInfo(
chain_id=chain_id, chain_id=chain_id,
slip44=slip44, slip44=slip44,
shortcut=shortcut, shortcut=shortcut,
@ -154,8 +132,8 @@ def construct_token_info(
address: bytes = b'', address: bytes = b'',
chain_id: int = 0, chain_id: int = 0,
name: str = "", name: str = "",
) -> tokens.TokenInfo: ) -> messages.EthereumTokenInfo:
return tokens.TokenInfo( return messages.EthereumTokenInfo(
symbol=symbol, symbol=symbol,
decimals=decimals, decimals=decimals,
address=address, address=address,
@ -202,14 +180,14 @@ def get_ethereum_encoded_definition(chain_id: int | None = None, slip44: int | N
) )
def builtin_networks_iterator() -> Iterator[networks.NetworkInfo]: def builtin_networks_iterator() -> Iterator[messages.EthereumNetworkInfo]:
"""Mockup function replaces original function from core/src/apps/ethereum/networks.py used to get built-in network definitions.""" """Mockup function replaces original function from core/src/apps/ethereum/networks.py used to get built-in network definitions."""
for _, network in NETWORKS.items(): for _, network in NETWORKS.items():
if network.definition is None: if network.definition is None:
yield network.info yield network.info
def builtin_token_by_chain_address(chain_id: int, address: bytes) -> tokens.TokenInfo: def builtin_token_by_chain_address(chain_id: int, address: bytes) -> messages.EthereumTokenInfo:
"""Mockup function replaces original function from core/src/apps/ethereum/tokens.py used to get built-in token definitions.""" """Mockup function replaces original function from core/src/apps/ethereum/tokens.py used to get built-in token definitions."""
address_str = hexlify(address).decode('hex') address_str = hexlify(address).decode('hex')
try: try:

View File

@ -5,11 +5,14 @@ from ubinascii import hexlify # noqa: F401
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
import apps.ethereum.definitions as dfs import apps.ethereum.definitions as dfs
from apps.ethereum import networks
from ethereum_common import * from ethereum_common import *
from trezor import protobuf from trezor import protobuf
from trezor.enums import EthereumDefinitionType from trezor.enums import EthereumDefinitionType
from trezor.messages import ( from trezor.messages import (
EthereumEncodedDefinitions, EthereumEncodedDefinitions,
EthereumNetworkInfo,
EthereumTokenInfo,
EthereumGetAddress, EthereumGetAddress,
EthereumGetPublicKey, EthereumGetPublicKey,
EthereumSignMessage, EthereumSignMessage,
@ -59,10 +62,6 @@ class TestEthereumDefinitionParser(unittest.TestCase):
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestDecodeDefinition(unittest.TestCase): class TestDecodeDefinition(unittest.TestCase):
def setUp(self):
self.addTypeEqualityFunc(networks.NetworkInfo, equalNetworkInfo)
self.addTypeEqualityFunc(tokens.TokenInfo, equalTokenInfo)
# successful decode network # successful decode network
def test_network_definition(self): def test_network_definition(self):
rinkeby_network = get_ethereum_network_info_with_definition(chain_id=4) rinkeby_network = get_ethereum_network_info_with_definition(chain_id=4)
@ -112,7 +111,6 @@ class TestDecodeDefinition(unittest.TestCase):
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestGetNetworkDefiniton(unittest.TestCase): class TestGetNetworkDefiniton(unittest.TestCase):
def setUp(self): def setUp(self):
self.addTypeEqualityFunc(networks.NetworkInfo, equalNetworkInfo)
# use mockup function for built-in networks # use mockup function for built-in networks
networks._networks_iterator = builtin_networks_iterator networks._networks_iterator = builtin_networks_iterator
@ -153,7 +151,6 @@ class TestGetNetworkDefiniton(unittest.TestCase):
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestGetTokenDefiniton(unittest.TestCase): class TestGetTokenDefiniton(unittest.TestCase):
def setUp(self): def setUp(self):
self.addTypeEqualityFunc(tokens.TokenInfo, equalTokenInfo)
# use mockup function for built-in tokens # use mockup function for built-in tokens
tokens.token_by_chain_address = builtin_token_by_chain_address tokens.token_by_chain_address = builtin_token_by_chain_address
@ -200,8 +197,6 @@ class TestGetTokenDefiniton(unittest.TestCase):
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestEthereumDefinitions(unittest.TestCase): class TestEthereumDefinitions(unittest.TestCase):
def setUp(self): def setUp(self):
self.addTypeEqualityFunc(networks.NetworkInfo, equalNetworkInfo)
self.addTypeEqualityFunc(tokens.TokenInfo, equalTokenInfo)
# use mockup functions for built-in definitions # use mockup functions for built-in definitions
networks._networks_iterator = builtin_networks_iterator networks._networks_iterator = builtin_networks_iterator
tokens.token_by_chain_address = builtin_token_by_chain_address tokens.token_by_chain_address = builtin_token_by_chain_address
@ -212,8 +207,8 @@ class TestEthereumDefinitions(unittest.TestCase):
token_definition: bytes | None, token_definition: bytes | None,
ref_chain_id: int | None, ref_chain_id: int | None,
ref_token_address: bytes | None, ref_token_address: bytes | None,
network_info: networks.NetworkInfo | None, network_info: EthereumNetworkInfo | None,
token_info: tokens.TokenInfo | None, token_info: EthereumTokenInfo | None,
): ):
# get # get
definitions = dfs.EthereumDefinitions(network_definition, token_definition, ref_chain_id, ref_token_address) definitions = dfs.EthereumDefinitions(network_definition, token_definition, ref_chain_id, ref_token_address)
@ -267,8 +262,6 @@ class TestEthereumDefinitions(unittest.TestCase):
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestGetDefinitonsFromMsg(unittest.TestCase): class TestGetDefinitonsFromMsg(unittest.TestCase):
def setUp(self): def setUp(self):
self.addTypeEqualityFunc(networks.NetworkInfo, equalNetworkInfo)
self.addTypeEqualityFunc(tokens.TokenInfo, equalTokenInfo)
# use mockup functions for built-in definitions # use mockup functions for built-in definitions
networks._networks_iterator = builtin_networks_iterator networks._networks_iterator = builtin_networks_iterator
tokens.token_by_chain_address = builtin_token_by_chain_address tokens.token_by_chain_address = builtin_token_by_chain_address
@ -276,8 +269,8 @@ class TestGetDefinitonsFromMsg(unittest.TestCase):
def get_and_compare_ethereum_definitions( def get_and_compare_ethereum_definitions(
self, self,
msg: protobuf.MessageType, msg: protobuf.MessageType,
network_info: networks.NetworkInfo | None, network_info: EthereumNetworkInfo | None,
token_info: tokens.TokenInfo | None, token_info: EthereumTokenInfo | None,
): ):
# get # get
definitions = dfs.get_definitions_from_msg(msg) definitions = dfs.get_definitions_from_msg(msg)

View File

@ -3,7 +3,7 @@ from apps.common.paths import HARDENED
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
from apps.ethereum.helpers import address_from_bytes from apps.ethereum.helpers import address_from_bytes
from apps.ethereum.networks import NetworkInfo from trezor.messages import EthereumNetworkInfo
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
@ -40,7 +40,7 @@ class TestEthereumGetAddress(unittest.TestCase):
'0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB', '0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB',
'0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB' '0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB'
] ]
n = NetworkInfo(chain_id=30, slip44=1, shortcut='T', name='T', rskip60=True) n = EthereumNetworkInfo(chain_id=30, slip44=1, shortcut='T', name='T', rskip60=True)
for s in rskip60_chain_30: for s in rskip60_chain_30:
b = unhexlify(s[2:]) b = unhexlify(s[2:])
h = address_from_bytes(b, n) h = address_from_bytes(b, n)

View File

@ -19,14 +19,15 @@ import pathlib
import re import re
import sys import sys
import tarfile import tarfile
from io import BytesIO
from decimal import Decimal from decimal import Decimal
from io import BytesIO
from typing import ( from typing import (
NoReturn,
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
BinaryIO,
Dict, Dict,
List, List,
NoReturn,
Optional, Optional,
Sequence, Sequence,
TextIO, TextIO,
@ -168,30 +169,37 @@ def _format_access_list(
def _get_ethereum_definitions( def _get_ethereum_definitions(
definitions_dir: pathlib.Path = None, definitions_dir: pathlib.Path = None,
network_def_file: TextIO = None, network_def_file: BinaryIO = None,
token_def_file: TextIO = None, token_def_file: BinaryIO = None,
download_definitions: bool = False, download_definitions: bool = False,
chain_id: Optional[int] = None, chain_id: Optional[int] = None,
slip44_hardened: Optional[int] = None, slip44_hardened: Optional[int] = None,
token_address: Optional[str] = None, token_address: Optional[str] = None,
) -> ethereum.messages.EthereumEncodedDefinitions: ) -> ethereum.messages.EthereumEncodedDefinitions:
count_of_options_used = sum( count_of_options_used = sum(
bool(o) for o in ( bool(o)
for o in (
definitions_dir, definitions_dir,
(network_def_file or token_def_file), (network_def_file or token_def_file),
download_definitions download_definitions,
) )
) )
if count_of_options_used > 1: if count_of_options_used > 1:
raise click.ClickException("More than one mutually exclusive option for definitions was used. See --help for more info.") raise click.ClickException(
"More than one mutually exclusive option for definitions was used. See --help for more info."
)
defs = ethereum.messages.EthereumEncodedDefinitions() defs = ethereum.messages.EthereumEncodedDefinitions()
if definitions_dir is not None: if definitions_dir is not None:
if chain_id is not None or slip44_hardened is not None: if chain_id is not None or slip44_hardened is not None:
defs.encoded_network = ethereum.network_definition_from_dir(definitions_dir, chain_id, slip44_hardened) defs.encoded_network = ethereum.network_definition_from_dir(
definitions_dir, chain_id, slip44_hardened
)
if chain_id is not None and token_address is not None: if chain_id is not None and token_address is not None:
defs.encoded_token = ethereum.token_definition_from_dir(definitions_dir, chain_id, token_address) defs.encoded_token = ethereum.token_definition_from_dir(
definitions_dir, chain_id, token_address
)
elif network_def_file is not None or token_def_file is not None: elif network_def_file is not None or token_def_file is not None:
if network_def_file is not None: if network_def_file is not None:
with network_def_file: with network_def_file:
@ -201,9 +209,13 @@ def _get_ethereum_definitions(
defs.encoded_token = token_def_file.read() defs.encoded_token = token_def_file.read()
elif download_definitions: elif download_definitions:
if chain_id is not None or slip44_hardened is not None: if chain_id is not None or slip44_hardened is not None:
defs.encoded_network = ethereum.download_network_definition(chain_id, slip44_hardened) defs.encoded_network = ethereum.download_network_definition(
chain_id, slip44_hardened
)
if chain_id is not None and token_address is not None: if chain_id is not None and token_address is not None:
defs.encoded_token = ethereum.download_token_definition(chain_id, token_address) defs.encoded_token = ethereum.download_token_definition(
chain_id, token_address
)
return defs return defs
@ -215,26 +227,28 @@ def _get_ethereum_definitions(
definitions_dir_option = click.option( definitions_dir_option = click.option(
"--definitions-dir", "--definitions-dir",
type=click.Path(exists=True, file_okay=False, resolve_path=True, path_type=pathlib.Path), type=click.Path(
help="Directory with stored definitions. Directory structure should be the same as it is in downloaded archive from " \ exists=True, file_okay=False, resolve_path=True, path_type=pathlib.Path
"`https:\\data.trezor.io\definitions\???`. Mutually exclusive with `--network-def`, `--token-def` and " \ ),
"`--download-definitions`.", # TODO: add link?, replace this ur with function used to download defs help="Directory with stored definitions. Directory structure should be the same as it is in downloaded archive from "
r"`https:\\data.trezor.io\definitions\???`. Mutually exclusive with `--network-def`, `--token-def` and "
"`--download-definitions`.", # TODO: add link?, replace this ur with function used to download defs
) )
network_def_option = click.option( network_def_option = click.option(
"--network-def", "--network-def",
type=click.File(mode="rb"), type=click.File(mode="rb"),
help="Binary file with network definition. Mutually exclusive with `--definitions-dir` and `--download-definitions`." help="Binary file with network definition. Mutually exclusive with `--definitions-dir` and `--download-definitions`.",
) )
token_def_options = click.option( token_def_options = click.option(
"--token-def", "--token-def",
type=click.File(mode="rb"), type=click.File(mode="rb"),
help="Binary file with token definition. Mutually exclusive with `--definitions-dir` and `--download-definitions`." help="Binary file with token definition. Mutually exclusive with `--definitions-dir` and `--download-definitions`.",
) )
download_definitions_option = click.option( download_definitions_option = click.option(
"--download-definitions", "--download-definitions",
is_flag=True, is_flag=True,
help="Automatically download required definitions from `data.trezor.io\definitions` and use them. " \ help=r"Automatically download required definitions from `data.trezor.io\definitions` and use them. "
"Mutually exclusive with `--definitions-dir`, `--network-def` and `--token-def`." "Mutually exclusive with `--definitions-dir`, `--network-def` and `--token-def`.",
) )
@ -244,14 +258,21 @@ def cli() -> None:
@cli.command() @cli.command()
@click.option("-o", "--outdir", type=click.Path(resolve_path=True, file_okay=False, path_type=pathlib.Path), default="./definitions-latest") @click.option(
"-o",
"--outdir",
type=click.Path(resolve_path=True, file_okay=False, path_type=pathlib.Path),
default="./definitions-latest",
)
@click.option("-u", "--unpack", is_flag=True) @click.option("-u", "--unpack", is_flag=True)
def download_definitions(outdir: pathlib.Path, unpack: bool) -> str: def download_definitions(outdir: pathlib.Path, unpack: bool) -> None:
"""Download all Ethereum network and token definitions and save them.""" """Download all Ethereum network and token definitions and save them."""
archive_filename = "definitions.tar.gz" archive_filename = "definitions.tar.gz"
# TODO: change once we know the urls # TODO: change once we know the urls
archived_definitions = ethereum.download_from_url("https://data.trezor.io/eth_definitions/" + archive_filename) archived_definitions = ethereum.download_from_url(
"https://data.trezor.io/eth_definitions/" + archive_filename
)
# unpack and/or save # unpack and/or save
if unpack: if unpack:
@ -271,7 +292,14 @@ def download_definitions(outdir: pathlib.Path, unpack: bool) -> str:
@network_def_option @network_def_option
@download_definitions_option @download_definitions_option
@with_client @with_client
def get_address(client: "TrezorClient", address: str, show_display: bool, definitions_dir: pathlib.Path, network_def: TextIO, download_definitions: bool) -> str: def get_address(
client: "TrezorClient",
address: str,
show_display: bool,
definitions_dir: pathlib.Path,
network_def: BinaryIO,
download_definitions: bool,
) -> str:
"""Get Ethereum address in hex encoding.""" """Get Ethereum address in hex encoding."""
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
defs = _get_ethereum_definitions( defs = _get_ethereum_definitions(
@ -290,7 +318,14 @@ def get_address(client: "TrezorClient", address: str, show_display: bool, defini
@network_def_option @network_def_option
@download_definitions_option @download_definitions_option
@with_client @with_client
def get_public_node(client: "TrezorClient", address: str, show_display: bool, definitions_dir: pathlib.Path, network_def: TextIO, download_definitions: bool) -> dict: def get_public_node(
client: "TrezorClient",
address: str,
show_display: bool,
definitions_dir: pathlib.Path,
network_def: BinaryIO,
download_definitions: bool,
) -> dict:
"""Get Ethereum public node of given path.""" """Get Ethereum public node of given path."""
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
defs = _get_ethereum_definitions( defs = _get_ethereum_definitions(
@ -299,7 +334,12 @@ def get_public_node(client: "TrezorClient", address: str, show_display: bool, de
download_definitions=download_definitions, download_definitions=download_definitions,
slip44_hardened=address_n[1], slip44_hardened=address_n[1],
) )
result = ethereum.get_public_node(client, address_n, show_display=show_display, encoded_network=defs.encoded_network) result = ethereum.get_public_node(
client,
address_n,
show_display=show_display,
encoded_network=defs.encoded_network,
)
return { return {
"node": { "node": {
"depth": result.node.depth, "depth": result.node.depth,
@ -380,8 +420,8 @@ def sign_tx(
access_list: List[ethereum.messages.EthereumAccessList], access_list: List[ethereum.messages.EthereumAccessList],
eip2718_type: Optional[int], eip2718_type: Optional[int],
definitions_dir: pathlib.Path, definitions_dir: pathlib.Path,
network_def: TextIO, network_def: BinaryIO,
token_def: TextIO, token_def: BinaryIO,
download_definitions: bool, download_definitions: bool,
) -> str: ) -> str:
"""Sign (and optionally publish) Ethereum transaction. """Sign (and optionally publish) Ethereum transaction.
@ -457,7 +497,7 @@ def sign_tx(
token_def_file=token_def, token_def_file=token_def,
download_definitions=download_definitions, download_definitions=download_definitions,
chain_id=chain_id, chain_id=chain_id,
token_address=to_address token_address=to_address,
) )
if is_eip1559: if is_eip1559:
@ -547,7 +587,14 @@ def sign_tx(
@network_def_option @network_def_option
@download_definitions_option @download_definitions_option
@with_client @with_client
def sign_message(client: "TrezorClient", address: str, message: str, definitions_dir: pathlib.Path, network_def: TextIO, download_definitions: bool) -> Dict[str, str]: def sign_message(
client: "TrezorClient",
address: str,
message: str,
definitions_dir: pathlib.Path,
network_def: BinaryIO,
download_definitions: bool,
) -> Dict[str, str]:
"""Sign message with Ethereum address.""" """Sign message with Ethereum address."""
address_n = tools.parse_path(address) address_n = tools.parse_path(address)
defs = _get_ethereum_definitions( defs = _get_ethereum_definitions(
@ -578,7 +625,13 @@ def sign_message(client: "TrezorClient", address: str, message: str, definitions
@download_definitions_option @download_definitions_option
@with_client @with_client
def sign_typed_data( def sign_typed_data(
client: "TrezorClient", address: str, metamask_v4_compat: bool, file: TextIO, definitions_dir: pathlib.Path, network_def: TextIO, download_definitions: bool client: "TrezorClient",
address: str,
metamask_v4_compat: bool,
file: TextIO,
definitions_dir: pathlib.Path,
network_def: BinaryIO,
download_definitions: bool,
) -> Dict[str, str]: ) -> Dict[str, str]:
"""Sign typed data (EIP-712) with Ethereum address. """Sign typed data (EIP-712) with Ethereum address.
@ -595,7 +648,11 @@ def sign_typed_data(
slip44_hardened=address_n[1], slip44_hardened=address_n[1],
) )
ret = ethereum.sign_typed_data( ret = ethereum.sign_typed_data(
client, address_n, data, metamask_v4_compat=metamask_v4_compat, encoded_network=defs.encoded_network client,
address_n,
data,
metamask_v4_compat=metamask_v4_compat,
encoded_network=defs.encoded_network,
) )
output = { output = {
"address": ret.address, "address": ret.address,
@ -613,7 +670,13 @@ def sign_typed_data(
@download_definitions_option @download_definitions_option
@with_client @with_client
def verify_message( def verify_message(
client: "TrezorClient", address: str, signature: str, message: str, definitions_dir: pathlib.Path, network_def: TextIO, download_definitions: bool client: "TrezorClient",
address: str,
signature: str,
message: str,
definitions_dir: pathlib.Path,
network_def: BinaryIO,
download_definitions: bool,
) -> bool: ) -> bool:
"""Verify message signed with Ethereum address.""" """Verify message signed with Ethereum address."""
chain_id = 1 chain_id = 1
@ -624,7 +687,9 @@ def verify_message(
download_definitions=download_definitions, download_definitions=download_definitions,
chain_id=chain_id, chain_id=chain_id,
) )
return ethereum.verify_message(client, address, signature_bytes, message, chain_id, defs.encoded_network) return ethereum.verify_message(
client, address, signature_bytes, message, chain_id, defs.encoded_network
)
@cli.command() @cli.command()

View File

@ -14,12 +14,14 @@
# You should have received a copy of the License along with this library. # You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>. # If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from itertools import chain import pathlib
import pathlib, re, requests import re
from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, TextIO, Tuple from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, Tuple
import requests
from . import exceptions, messages from . import exceptions, messages
from .tools import expect, UH_, prepare_message_bytes, session from .tools import UH_, expect, prepare_message_bytes, session
if TYPE_CHECKING: if TYPE_CHECKING:
from .client import TrezorClient from .client import TrezorClient
@ -28,11 +30,11 @@ if TYPE_CHECKING:
# TODO: change once we know the urls # TODO: change once we know the urls
DEFS_BASE_URL="https://data.trezor.io/eth_definitions/{lookup_type}/{id}/{name}.dat" DEFS_BASE_URL = "https://data.trezor.io/eth_definitions/{lookup_type}/{id}/{name}.dat"
DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE="by_chain_id" DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE = "by_chain_id"
DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE="by_slip44" DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE = "by_slip44"
DEFS_NETWORK_URI_NAME="network" DEFS_NETWORK_URI_NAME = "network"
DEFS_TOKEN_URI_NAME="token_{hex_address}" DEFS_TOKEN_URI_NAME = "token_{hex_address}"
def int_to_big_endian(value: int) -> bytes: def int_to_big_endian(value: int) -> bytes:
@ -159,9 +161,13 @@ def download_from_url(url: str, error_msg: str = "") -> bytes:
raise RuntimeError(f"{error_msg}{err}") raise RuntimeError(f"{error_msg}{err}")
def download_network_definition(chain_id: Optional[int] = None, slip44_hardened: Optional[int] = None) -> Optional[bytes]: def download_network_definition(
if not ((chain_id is None) != (slip44_hardened is None)): # not XOR chain_id: Optional[int] = None, slip44_hardened: Optional[int] = None
raise RuntimeError(f"Exactly one of chain_id or slip44_hardened parameters are needed to load network definition from directory.") ) -> Optional[bytes]:
if not ((chain_id is None) != (slip44_hardened is None)): # not XOR
raise RuntimeError(
"Exactly one of chain_id or slip44_hardened parameters are needed to load network definition from directory."
)
if chain_id is not None: if chain_id is not None:
url = DEFS_BASE_URL.format( url = DEFS_BASE_URL.format(
@ -172,17 +178,21 @@ def download_network_definition(chain_id: Optional[int] = None, slip44_hardened:
else: else:
url = DEFS_BASE_URL.format( url = DEFS_BASE_URL.format(
lookup_type=DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE, lookup_type=DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE,
id=UH_(slip44_hardened), id=UH_(slip44_hardened), # type: ignore [Argument of type "int | None" cannot be assigned to parameter "x" of type "int" in function "UH_"]
name=DEFS_NETWORK_URI_NAME, name=DEFS_NETWORK_URI_NAME,
) )
error_msg = f"While downloading network definition from \"{url}\" following HTTP error occured: " error_msg = f'While downloading network definition from "{url}" following HTTP error occured: '
return download_from_url(url, error_msg) return download_from_url(url, error_msg)
def download_token_definition(chain_id: Optional[int] = None, token_address: Optional[str] = None) -> Optional[bytes]: def download_token_definition(
chain_id: Optional[int] = None, token_address: Optional[str] = None
) -> Optional[bytes]:
if chain_id is None or token_address is None: if chain_id is None or token_address is None:
raise RuntimeError(f"Both chain_id and token_address parameters are needed to download token definition.") raise RuntimeError(
"Both chain_id and token_address parameters are needed to download token definition."
)
url = DEFS_BASE_URL.format( url = DEFS_BASE_URL.format(
lookup_type=DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE, lookup_type=DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE,
@ -190,37 +200,64 @@ def download_token_definition(chain_id: Optional[int] = None, token_address: Opt
name=DEFS_TOKEN_URI_NAME.format(hex_address=token_address), name=DEFS_TOKEN_URI_NAME.format(hex_address=token_address),
) )
error_msg = f"While downloading token definition from \"{url}\" following HTTP error occured: " error_msg = f'While downloading token definition from "{url}" following HTTP error occured: '
return download_from_url(url, error_msg) return download_from_url(url, error_msg)
def network_definition_from_dir(path: pathlib.Path, chain_id: Optional[int] = None, slip44_hardened: Optional[int] = None) -> Optional[bytes]: def network_definition_from_dir(
if not ((chain_id is None) != (slip44_hardened is None)): # not XOR path: pathlib.Path,
raise RuntimeError(f"Exactly one of chain_id or slip44_hardened parameters are needed to load network definition from directory.") chain_id: Optional[int] = None,
slip44_hardened: Optional[int] = None,
) -> Optional[bytes]:
if not ((chain_id is None) != (slip44_hardened is None)): # not XOR
raise RuntimeError(
"Exactly one of chain_id or slip44_hardened parameters are needed to load network definition from directory."
)
def read_definition(path: pathlib.Path) -> Optional[bytes]: def read_definition(path: pathlib.Path) -> Optional[bytes]:
if not path.exists() or not path.is_file(): if not path.exists() or not path.is_file():
return None return None
with open(path, mode="rb") as f: with open(path, mode="rb") as f:
return f.read() return f.read()
if chain_id is not None: if chain_id is not None:
return read_definition(path / DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE / str(chain_id) / (DEFS_NETWORK_URI_NAME + ".dat")) return read_definition(
path
/ DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE
/ str(chain_id)
/ (DEFS_NETWORK_URI_NAME + ".dat")
)
else: else:
return read_definition(path / DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE / str(UH_(slip44_hardened)) / (DEFS_NETWORK_URI_NAME + ".dat")) return read_definition(
path
/ DEFS_NETWORK_BY_SLIP44_LOOKUP_TYPE
/ str(UH_(slip44_hardened)) # type: ignore [Argument of type "int | None" cannot be assigned to parameter "x" of type "int" in function "UH_"]
/ (DEFS_NETWORK_URI_NAME + ".dat")
)
def token_definition_from_dir(path: pathlib.Path, chain_id: Optional[int] = None, token_address: Optional[str] = None) -> Optional[bytes]: def token_definition_from_dir(
path: pathlib.Path,
chain_id: Optional[int] = None,
token_address: Optional[str] = None,
) -> Optional[bytes]:
if chain_id is None or token_address is None: if chain_id is None or token_address is None:
raise RuntimeError(f"Both chain_id and token_address parameters are needed to load token definition from directory.") raise RuntimeError(
"Both chain_id and token_address parameters are needed to load token definition from directory."
)
path = path / DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE / str(chain_id) / (DEFS_TOKEN_URI_NAME.format(hex_address=token_address) + ".dat") path = (
path
/ DEFS_NETWORK_BY_CHAINID_LOOKUP_TYPE
/ str(chain_id)
/ (DEFS_TOKEN_URI_NAME.format(hex_address=token_address) + ".dat")
)
if not path.exists() or not path.is_file(): if not path.exists() or not path.is_file():
return None return None
with open(path, mode="rb") as f: with open(path, mode="rb") as f:
return f.read() return f.read()
# ====== Client functions ====== # # ====== Client functions ====== #
@ -228,7 +265,10 @@ def token_definition_from_dir(path: pathlib.Path, chain_id: Optional[int] = None
@expect(messages.EthereumAddress, field="address", ret_type=str) @expect(messages.EthereumAddress, field="address", ret_type=str)
def get_address( def get_address(
client: "TrezorClient", n: "Address", show_display: bool = False, encoded_network: bytes = None client: "TrezorClient",
n: "Address",
show_display: bool = False,
encoded_network: bytes = None,
) -> "MessageType": ) -> "MessageType":
return client.call( return client.call(
messages.EthereumGetAddress( messages.EthereumGetAddress(
@ -241,7 +281,10 @@ def get_address(
@expect(messages.EthereumPublicKey) @expect(messages.EthereumPublicKey)
def get_public_node( def get_public_node(
client: "TrezorClient", n: "Address", show_display: bool = False, encoded_network: bytes = None client: "TrezorClient",
n: "Address",
show_display: bool = False,
encoded_network: bytes = None,
) -> "MessageType": ) -> "MessageType":
return client.call( return client.call(
messages.EthereumGetPublicKey( messages.EthereumGetPublicKey(
@ -446,7 +489,12 @@ def sign_typed_data(
def verify_message( def verify_message(
client: "TrezorClient", address: str, signature: bytes, message: AnyStr, chain_id: int = 1, encoded_network: bytes = None client: "TrezorClient",
address: str,
signature: bytes,
message: AnyStr,
chain_id: int = 1,
encoded_network: bytes = None,
) -> bool: ) -> bool:
try: try:
resp = client.call( resp = client.call(