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
feat(core/ethereum): EIP-712 Based on original contribution by Max Kupriianov <xlab@hey.com> Implemented EIP-712 typed data signatures in Ethereum app. Add eth_abi into pyproject deps device test for EIP 712 fixed hex decoding for address fixup! fixed hex decoding for address code quality, more pythonic code, removing unused imports running black and isort on changed files trezorctl file input for EIP 712 data signing fixup! code quality, more pythonic code, removing unused imports fixup! fixup! code quality, more pythonic code, removing unused imports necessary changes after rebase to master unit tests for sign_typed_data.py new protobuf messages, working for nonarray types simplified and verified solution for our simple data support for simple arrays, without their confirmation reverting protobuf value messages to bytes, appropriate changes showing arrays in Trezor, code quality improvements data validation on Trezor, minor improvements using custom types for storing type data instead of dicts, addressing feedback from review moving helper functions to its own file, tests for decode_data additional overall tests support for arrays of structs adding support for metamask_v4_compat variable using HashWriter object to collect the final hash continously minor improvements in code quality validate_field_type function streaming values from client without saving them, missing UI prototype of streamed UI using confirm_properties accounting for bytes in data, more data types in integration tests rebase on master, using f-strings minor fixes and improvements from code review StructHasher class for the whole hashing process mypy and style changes asking users whether to show structs and arrays protobuf descriptions to fix make defs_check unifying comments, mypy fix unit tests for StructHasher class UI fixtures, skipping device tests for T1 addressing majority of code review comments about code quality and structure changing file structure - layouts, helpers, sign_typed_data decode_data renaming and docstring, renaming unit test file using tuples instead of lists in elifs layout improvements excluding core/src/apps/common/confirm.py file from the PR True/False returning layout with Show more button code review layout improvements forgotten br_type argument to should_show_more
3 years ago
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")
feat(core/ethereum): EIP-712 Based on original contribution by Max Kupriianov <xlab@hey.com> Implemented EIP-712 typed data signatures in Ethereum app. Add eth_abi into pyproject deps device test for EIP 712 fixed hex decoding for address fixup! fixed hex decoding for address code quality, more pythonic code, removing unused imports running black and isort on changed files trezorctl file input for EIP 712 data signing fixup! code quality, more pythonic code, removing unused imports fixup! fixup! code quality, more pythonic code, removing unused imports necessary changes after rebase to master unit tests for sign_typed_data.py new protobuf messages, working for nonarray types simplified and verified solution for our simple data support for simple arrays, without their confirmation reverting protobuf value messages to bytes, appropriate changes showing arrays in Trezor, code quality improvements data validation on Trezor, minor improvements using custom types for storing type data instead of dicts, addressing feedback from review moving helper functions to its own file, tests for decode_data additional overall tests support for arrays of structs adding support for metamask_v4_compat variable using HashWriter object to collect the final hash continously minor improvements in code quality validate_field_type function streaming values from client without saving them, missing UI prototype of streamed UI using confirm_properties accounting for bytes in data, more data types in integration tests rebase on master, using f-strings minor fixes and improvements from code review StructHasher class for the whole hashing process mypy and style changes asking users whether to show structs and arrays protobuf descriptions to fix make defs_check unifying comments, mypy fix unit tests for StructHasher class UI fixtures, skipping device tests for T1 addressing majority of code review comments about code quality and structure changing file structure - layouts, helpers, sign_typed_data decode_data renaming and docstring, renaming unit test file using tuples instead of lists in elifs layout improvements excluding core/src/apps/common/confirm.py file from the PR True/False returning layout with Show more button code review layout improvements forgotten br_type argument to should_show_more
3 years ago
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")