Skip to content

Commit

Permalink
changing file structure - layouts, helpers, sign_typed_data
Browse files Browse the repository at this point in the history
  • Loading branch information
grdddj committed Oct 21, 2021
1 parent 7f1fc8d commit 2b5a0f1
Show file tree
Hide file tree
Showing 11 changed files with 715 additions and 715 deletions.
54 changes: 0 additions & 54 deletions core/src/apps/ethereum/address.py

This file was deleted.

2 changes: 1 addition & 1 deletion core/src/apps/ethereum/get_address.py
Expand Up @@ -6,7 +6,7 @@
from apps.common import paths

from . import networks
from .address import address_from_bytes
from .helpers import address_from_bytes
from .keychain import PATTERNS_ADDRESS, with_keychain_from_path

if False:
Expand Down
125 changes: 125 additions & 0 deletions core/src/apps/ethereum/helpers.py
@@ -0,0 +1,125 @@
from ubinascii import hexlify, unhexlify

from trezor import wire
from trezor.enums import EthereumDataType
from trezor.messages import EthereumFieldType

if False:
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
entry_type = field.entry_type
type_name = get_type_name(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_data(data: bytes, type_name: str) -> str:
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")
132 changes: 129 additions & 3 deletions core/src/apps/ethereum/layout.py
@@ -1,21 +1,26 @@
from ubinascii import hexlify

from trezor import ui
from trezor.enums import ButtonRequestType
from trezor.enums import ButtonRequestType, EthereumDataType
from trezor.messages import EthereumFieldType, EthereumStructMember
from trezor.strings import format_amount
from trezor.ui.components.tt.text import Text
from trezor.ui.layouts import (
confirm_address,
confirm_amount,
confirm_blob,
confirm_output,
confirm_text,
)
from trezor.ui.layouts.tt.altcoin import confirm_total_ethereum

from apps.common.confirm import confirm

from . import networks, tokens
from .address import address_from_bytes
from .helpers import address_from_bytes, decode_data, get_type_name

if False:
from typing import Awaitable
from typing import Awaitable, Iterable, Optional
from trezor.wire import Context


Expand Down Expand Up @@ -105,6 +110,120 @@ def require_confirm_data(ctx: Context, data: bytes, data_total: int) -> Awaitabl
)


async def confirm_hash(ctx: Context, primary_type: str, typed_data_hash: bytes) -> None:
data = "0x" + hexlify(typed_data_hash).decode()
await confirm_blob(
ctx,
"confirm_resulting_hash",
title="Sign typed data?",
description=f"Hashed {primary_type}:",
data=data,
hold=True,
icon=ui.ICON_CONFIG,
icon_color=ui.GREEN,
)


async def should_show_domain(ctx: Context, name: bytes, version: bytes) -> bool:
page = Text("Typed Data", ui.ICON_SEND, icon_color=ui.GREEN)

domain_name = decode_data(name, "string")
domain_version = decode_data(version, "string")

page.bold(f"Name: {domain_name}")
page.normal(f"Version: {domain_version}")
page.br()
page.mono("View EIP712Domain?")

return await confirm(ctx, page, ButtonRequestType.Other)


async def should_show_struct(
ctx: Context,
primary_type: str,
parent_objects: Iterable[str],
data_members: list[EthereumStructMember],
) -> bool:
title = f"{'.'.join(parent_objects)} - {primary_type}"
page = Text(title, ui.ICON_SEND, icon_color=ui.GREEN)

# We have limited screen space, so showing only a preview when having lot of fields
MAX_FIELDS_TO_SHOW = 3
fields_amount = len(data_members)
if fields_amount > MAX_FIELDS_TO_SHOW:
for field in data_members[:MAX_FIELDS_TO_SHOW]:
page.bold(limit_str(field.name))
page.mono(f"...and {fields_amount - MAX_FIELDS_TO_SHOW} more.")
else:
for field in data_members:
page.bold(limit_str(field.name))

page.mono("View full struct?")

return await confirm(ctx, page, ButtonRequestType.Other)


async def should_show_array(
ctx: Context,
name: str,
parent_objects: Iterable[str],
data_type: str,
size: int,
) -> bool:
title = f"{'.'.join(parent_objects)} - {name}"
page = Text(title, ui.ICON_SEND, icon_color=ui.GREEN)

page.bold(limit_str(f"Type: {data_type}"))
page.bold(limit_str(f"Size: {size}"))
page.br()
page.mono("View full array?")

return await confirm(ctx, page, ButtonRequestType.Other)


async def confirm_typed_value(
ctx: Context,
name: str,
value: bytes,
parent_objects: Iterable[str],
primary_type: str,
field: EthereumFieldType,
array_index: Optional[int] = None,
) -> None:
type_name = get_type_name(field)
if parent_objects:
title = f"{'.'.join(parent_objects)} - {primary_type}"
else:
title = primary_type

if array_index is not None:
array_str = f"[{array_index}]"
else:
array_str = ""

description = f"{name}{array_str} ({type_name})"
data = decode_data(value, type_name)

if field.data_type in (EthereumDataType.ADDRESS, EthereumDataType.BYTES):
await confirm_blob(
ctx,
"show_data",
title=title,
data=data,
description=description,
br_code=ButtonRequestType.Other,
)
else:
await confirm_text(
ctx,
"show_data",
title=title,
data=data,
description=description,
br_code=ButtonRequestType.Other,
)


def format_ethereum_amount(
value: int, token: tokens.TokenInfo | None, chain_id: int
) -> str:
Expand All @@ -121,3 +240,10 @@ def format_ethereum_amount(
decimals = 0

return f"{format_amount(value, decimals)} {suffix}"


def limit_str(s: str, limit: int = 16) -> str:
if len(s) <= limit + 2:
return s

return s[:limit] + ".."
4 changes: 2 additions & 2 deletions core/src/apps/ethereum/sign_message.py
Expand Up @@ -7,7 +7,7 @@
from apps.common import paths
from apps.common.signverify import decode_message

from . import address
from .helpers import address_from_bytes
from .keychain import PATTERNS_ADDRESS, with_keychain_from_path

if False:
Expand Down Expand Up @@ -42,6 +42,6 @@ async def sign_message(
)

return EthereumMessageSignature(
address=address.address_from_bytes(node.ethereum_pubkeyhash()),
address=address_from_bytes(node.ethereum_pubkeyhash()),
signature=signature[1:] + bytearray([signature[0]]),
)
7 changes: 4 additions & 3 deletions core/src/apps/ethereum/sign_tx.py
Expand Up @@ -7,7 +7,8 @@

from apps.common import paths

from . import address, tokens
from . import tokens
from .helpers import bytes_from_address
from .keychain import with_keychain_from_chain_id
from .layout import (
require_confirm_data,
Expand Down Expand Up @@ -96,7 +97,7 @@ async def handle_erc20(
ctx: wire.Context, msg: EthereumSignTxAny
) -> Tuple[tokens.TokenInfo | None, bytes, bytes, int]:
token = None
address_bytes = recipient = address.bytes_from_address(msg.to)
address_bytes = recipient = bytes_from_address(msg.to)
value = int.from_bytes(msg.value, "big")
if (
len(msg.to) in (40, 42)
Expand Down Expand Up @@ -125,7 +126,7 @@ def get_total_length(msg: EthereumSignTx, data_total: int) -> int:
msg.nonce,
msg.gas_price,
msg.gas_limit,
address.bytes_from_address(msg.to),
bytes_from_address(msg.to),
msg.value,
msg.chain_id,
0,
Expand Down

0 comments on commit 2b5a0f1

Please sign in to comment.