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/apps/ethereum/helpers.py

127 lines
4.2 KiB

from typing import TYPE_CHECKING
from ubinascii import hexlify, unhexlify
from trezor import wire
from trezor.enums import EthereumDataType
from trezor.messages import EthereumFieldType
if TYPE_CHECKING:
from .networks import NetworkInfo
def address_from_bytes(address_bytes: bytes, network: NetworkInfo | None = None) -> str:
"""
Converts address in bytes to a checksummed string as defined
in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
"""
from trezor.crypto.hashlib import sha3_256
if network is not None and network.rskip60:
prefix = str(network.chain_id) + "0x"
else:
prefix = ""
address_hex = hexlify(address_bytes).decode()
digest = sha3_256((prefix + address_hex).encode(), keccak=True).digest()
def maybe_upper(i: int) -> str:
"""Uppercase i-th letter only if the corresponding nibble has high bit set."""
digest_byte = digest[i // 2]
hex_letter = address_hex[i]
if i % 2 == 0:
# even letter -> high nibble
bit = 0x80
else:
# odd letter -> low nibble
bit = 0x08
if digest_byte & bit:
return hex_letter.upper()
else:
return hex_letter
return "0x" + "".join(maybe_upper(i) for i in range(len(address_hex)))
def bytes_from_address(address: str) -> bytes:
if len(address) == 40:
return unhexlify(address)
elif len(address) == 42:
if address[0:2] not in ("0x", "0X"):
raise wire.ProcessError("Ethereum: invalid beginning of an address")
return unhexlify(address[2:])
elif len(address) == 0:
return bytes()
raise wire.ProcessError("Ethereum: Invalid address length")
def get_type_name(field: EthereumFieldType) -> str:
"""Create a string from type definition (like uint256 or bytes16)."""
data_type = field.data_type
size = field.size
TYPE_TRANSLATION_DICT = {
EthereumDataType.UINT: "uint",
EthereumDataType.INT: "int",
EthereumDataType.BYTES: "bytes",
EthereumDataType.STRING: "string",
EthereumDataType.BOOL: "bool",
EthereumDataType.ADDRESS: "address",
}
if data_type == EthereumDataType.STRUCT:
assert field.struct_name is not None # validate_field_type
return field.struct_name
elif data_type == EthereumDataType.ARRAY:
assert field.entry_type is not None # validate_field_type
type_name = get_type_name(field.entry_type)
if size is None:
return f"{type_name}[]"
else:
return f"{type_name}[{size}]"
elif data_type in (EthereumDataType.UINT, EthereumDataType.INT):
assert size is not None # validate_field_type
return TYPE_TRANSLATION_DICT[data_type] + str(size * 8)
elif data_type == EthereumDataType.BYTES:
if size:
return TYPE_TRANSLATION_DICT[data_type] + str(size)
else:
return TYPE_TRANSLATION_DICT[data_type]
else:
# all remaining types can use the name directly
# if the data_type is left out, this will raise KeyError
return TYPE_TRANSLATION_DICT[data_type]
def decode_typed_data(data: bytes, type_name: str) -> str:
"""Used by sign_typed_data module to show data to user."""
if type_name.startswith("bytes"):
return hexlify(data).decode()
elif type_name == "string":
return data.decode()
elif type_name == "address":
return address_from_bytes(data)
elif type_name == "bool":
return "true" if data == b"\x01" else "false"
elif type_name.startswith("uint"):
return str(int.from_bytes(data, "big"))
elif type_name.startswith("int"):
# Micropython does not implement "signed" arg in int.from_bytes()
return str(from_bytes_bigendian_signed(data))
raise ValueError # Unsupported data type for direct field decoding
def from_bytes_bigendian_signed(b: bytes) -> int:
negative = b[0] & 0x80
if negative:
neg_b = bytearray(b)
for i in range(len(neg_b)):
neg_b[i] = ~neg_b[i] & 0xFF
result = int.from_bytes(neg_b, "big")
return -result - 1
else:
return int.from_bytes(b, "big")