fix(core): confirm Ethereum data during its hashing
What changed, and why it matters
This update changes how Trezor hardware wallets confirm Ethereum transaction data. Previously, the device asked the user to approve the entire data payload before it began hashing the transaction. Now, it confirms data piece by piece while the transaction is being hashed, and only shows the final transaction summary right before signing. The change is marked as a security fix in the project's changelog, suggesting the old behavior could let a user approve data that does not match what is actually signed.
Treat this as a security fix and include it in release notes. Users should upgrade firmware. Developers should verify that the new `confirm_blob_prefix` flow correctly handles very large data payloads and that cancellation at any chunk aborts signing. Review whether the legacy (Trezor One) code path has a corresponding fix, since this patch only touches core/ code.
Security signals we found
Changelog entry explicitly labeled `.security`: 'Confirm all data during Ethereum transaction hashing.'
UI confirmation order changed from 'confirm all data, then hash' to 'hash and confirm chunks together, then confirm summary, then sign'.
New `confirm_blob_prefix` UI primitive confirms only a prefix of each chunk and tracks `confirmed_len` across chunks.
Old `require_confirm_other_data()` removed; data confirmation is now tied to the streaming hash loop.
Tests updated to expect additional ButtonRequest prompts during data streaming on non-legacy models.
Evidence from the diff
The patch refactors Ethereum transaction signing in trezor-core. It removes the old pre-hashing require_confirm_other_data() prompt and introduces make_confirm_data() / make_progress() callbacks that are invoked on each data chunk as it is fed into the Keccak/SHA3 hash writer. confirm_tx_data() now returns a tuple (confirm_data_chunk, confirm_summary) instead of completing all UI up front. The summary layout is awaited only after the digest is complete, just before _sign_digest() is called. This ensures the user confirms the same bytes that are hashed, closing a window where the displayed data and the hashed data could diverge (for example, through host-supplied chunk substitution or truncation).
Changed components
core/src/apps/ethereum/sign_tx.pycore/src/apps/ethereum/sign_tx_eip1559.pycore/src/apps/ethereum/layout.pycore/src/trezor/ui/layouts (confirm_blob_prefix, progress)Ethereum transaction signing flow on Trezor Model T / Safe familyInspect captured patch +207 / −238
diff --git a/core/.changelog.d/222.security b/core/.changelog.d/222.security
new file mode 100644
index 00000000..d2ec61c9
--- /dev/null
+++ b/core/.changelog.d/222.security
@@ -0,0 +1 @@
+Confirm all data during Ethereum transaction hashing.
diff --git a/core/src/apps/ethereum/layout.py b/core/src/apps/ethereum/layout.py
index 895d7298..f473f2c3 100644
--- a/core/src/apps/ethereum/layout.py
+++ b/core/src/apps/ethereum/layout.py
@@ -301,20 +301,6 @@ def require_confirm_address(
)
-def require_confirm_other_data(data: AnyBytes, data_total: int) -> Awaitable[None]:
- return confirm_blob(
- "confirm_data",
- TR.ethereum__title_input_data,
- data,
- description=TR.ethereum__data_size_template.format(data_total),
- subtitle=TR.ethereum__title_all_input_data_template.format(data_total),
- verb=TR.buttons__confirm,
- verb_cancel=TR.send__cancel_sign,
- br_code=ButtonRequestType.SignTx,
- ask_pagination=True,
- )
-
-
async def confirm_message_hash(message_hash: bytes) -> None:
from ubinascii import hexlify
diff --git a/core/src/apps/ethereum/sign_tx.py b/core/src/apps/ethereum/sign_tx.py
index ff27a51e..3d619ac4 100644
--- a/core/src/apps/ethereum/sign_tx.py
+++ b/core/src/apps/ethereum/sign_tx.py
@@ -1,5 +1,6 @@
from typing import TYPE_CHECKING
+from trezor import TR
from trezor.crypto import rlp
from trezor.messages import EthereumTxRequest
from trezor.utils import BufferReader
@@ -12,7 +13,7 @@ from .keychain import with_keychain_from_chain_id
if TYPE_CHECKING:
from buffer_types import AnyBytes
- from typing import Any, Coroutine, Iterable
+ from typing import Any, Awaitable, Callable, Coroutine, Iterable
from trezor.messages import (
EthereumNetworkInfo,
@@ -28,6 +29,8 @@ if TYPE_CHECKING:
from .definitions import Definitions
from .keychain import MsgInSignTx
+ ConfirmDataFn = Callable[[AnyBytes], Awaitable[None]]
+
# Maximum chain_id which returns the full signature_v (which must fit into an uint32).
# chain_ids larger than this will only return one bit and the caller must recalculate
@@ -41,10 +44,8 @@ async def sign_tx(
keychain: Keychain,
defs: Definitions,
) -> EthereumTxRequest:
- from trezor import TR
from trezor.crypto.hashlib import sha3_256
from trezor.ui.layouts import show_continue_in_app
- from trezor.ui.layouts.progress import progress
from trezor.utils import HashWriter
from apps.common import paths, safety_checks
@@ -92,7 +93,9 @@ async def sign_tx(
amount_size_bytes=32,
)
- await confirm_tx_data(
+ # data chunks will be confirmed during digest (see below)
+ # tx summary will confirmed before signing the digest (see below)
+ confirm_data_chunk, confirm_summary = await confirm_tx_data(
msg,
defs,
tx_type,
@@ -103,14 +106,7 @@ async def sign_tx(
payment_req_verifier,
)
- progress_obj = progress(title=TR.progress__signing_transaction)
- progress_obj.report(100)
-
- # sign
- data = bytearray()
- data += msg.data_initial_chunk
- data_left = data_total - len(msg.data_initial_chunk)
-
+ # digest
total_length = _get_total_length(msg, data_total)
sha = HashWriter(sha3_256(keccak=True))
@@ -122,22 +118,16 @@ async def sign_tx(
for field in (msg.nonce, msg.gas_price, msg.gas_limit, address_bytes, msg.value):
rlp.write(sha, field)
- if data_left == 0:
- rlp.write(sha, data)
- else:
- rlp.write_header(sha, data_total, rlp.STRING_HEADER_BYTE, data)
- sha.extend(data)
-
- progress_obj.report(500)
+ await confirm_data_chunk(msg.data_initial_chunk)
+ data_left = data_total - len(msg.data_initial_chunk)
+ rlp.write_header(sha, data_total, rlp.STRING_HEADER_BYTE, msg.data_initial_chunk)
+ sha.extend(msg.data_initial_chunk)
- initial_data_left = data_left
while data_left > 0:
resp = await send_request_chunk(data_left)
+ await confirm_data_chunk(resp.data_chunk)
data_left -= len(resp.data_chunk)
sha.extend(resp.data_chunk)
- progress_obj.report(
- 500 + int((initial_data_left - data_left) / initial_data_left * 400)
- )
# eip 155 replay protection
rlp.write(sha, msg.chain_id)
@@ -145,14 +135,88 @@ async def sign_tx(
rlp.write(sha, 0)
digest = sha.get_digest()
- result = _sign_digest(msg, keychain, digest)
- progress_obj.stop()
+ # show tx summary and confirm
+ await confirm_summary
+ # transaction data confirmed, proceed with signing
+ result = _sign_digest(msg, keychain, digest)
show_continue_in_app(TR.send__transaction_signed)
return result
+def make_progress(total_len: int, progress_len: int = 0) -> ConfirmDataFn:
+ from trezor.ui.layouts.progress import progress
+
+ if __debug__:
+ from trezor import log
+
+ def _progress_value() -> int:
+ assert 0 <= progress_len <= total_len
+ if total_len == 0:
+ return 1000
+ return (1000 * progress_len) // total_len
+
+ layout = progress(title=TR.progress__loading_transaction)
+ layout.value = _progress_value()
+
+ async def confirm_fn(chunk: AnyBytes) -> None:
+ nonlocal progress_len
+
+ if __debug__:
+ log.debug(
+ __name__,
+ "chunk=%d [%d/%d]",
+ len(chunk),
+ progress_len,
+ total_len,
+ )
+ progress_len += len(chunk)
+ layout.report(_progress_value())
+
+ return confirm_fn
+
+
+def make_confirm_data(total_len: int) -> ConfirmDataFn:
+ from trezor.enums import ButtonRequestType
+ from trezor.ui.layouts import confirm_blob_prefix
+
+ confirmed_len = 0
+ progress_bar: ConfirmDataFn | None = None
+
+ async def confirm_fn(chunk: AnyBytes) -> None:
+ nonlocal confirmed_len
+ nonlocal progress_bar
+
+ if progress_bar is not None:
+ return await progress_bar(chunk)
+
+ # for efficient chunk slicing (see below)
+ chunk = memoryview(chunk)
+ while True:
+ assert 0 <= confirmed_len <= total_len
+ prefix_len = await confirm_blob_prefix(
+ title=TR.ethereum__title_input_data,
+ data=chunk,
+ total_len=total_len,
+ confirmed_len=confirmed_len,
+ br_name="confirm_data",
+ br_code=ButtonRequestType.SignTx,
+ )
+ if prefix_len is None:
+ # skip this and following chunks confirmation - use a progress bar instead
+ assert progress_bar is None
+ progress_bar = make_progress(total_len, confirmed_len)
+ return await progress_bar(chunk)
+ else:
+ confirmed_len += prefix_len
+ chunk = chunk[prefix_len:]
+ if not chunk:
+ return
+
+ return confirm_fn
+
+
async def confirm_tx_data(
msg: MsgInSignTx,
defs: Definitions,
@@ -162,15 +226,15 @@ async def confirm_tx_data(
fee_items: Iterable[StrPropertyType],
data_total_len: int,
payment_req_verifier: PaymentRequestVerifier | None,
-) -> None:
- from trezor import TR
+) -> tuple[ConfirmDataFn, Coroutine[Any, Any, None]]:
+ """Returns data chunk callback and transaction summary layout to be awaited."""
+
from trezor.ui.layouts import confirm_value, ethereum_address_title
from . import tokens
from .layout import (
require_confirm_address,
require_confirm_approve,
- require_confirm_other_data,
require_confirm_payment_request,
require_confirm_tx,
require_confirm_unknown_token,
@@ -186,7 +250,7 @@ async def confirm_tx_data(
msg, defs.network, address_bytes, maximum_fee, fee_items
)
if staking_approver is not None:
- return await staking_approver
+ return make_progress(data_total_len), staking_approver
if tx_type == EIP_7702_TX_TYPE:
# we have already made sure that the address is a known address
@@ -230,7 +294,7 @@ async def confirm_tx_data(
if payment_req_verifier is not None:
raise DataError("Payment Requests not supported for the APPROVE call")
- await require_confirm_approve(
+ return make_progress(data_total_len), require_confirm_approve(
recipient,
value,
msg.address_n,
@@ -261,7 +325,7 @@ async def confirm_tx_data(
assert recipient_str is not None
payment_req_verifier.add_output(value, recipient_str or "")
payment_req_verifier.verify()
- await require_confirm_payment_request(
+ return make_progress(data_total_len), require_confirm_payment_request(
recipient_str,
payment_req,
msg.address_n,
@@ -274,9 +338,11 @@ async def confirm_tx_data(
)
else:
if is_contract_interaction:
- await require_confirm_other_data(msg.data_initial_chunk, data_total_len)
+ confirm_data_chunk = make_confirm_data(data_total_len)
+ else:
+ confirm_data_chunk = make_progress(data_total_len)
- await require_confirm_tx(
+ return confirm_data_chunk, require_confirm_tx(
recipient_str,
value,
msg.address_n,
diff --git a/core/src/apps/ethereum/sign_tx_eip1559.py b/core/src/apps/ethereum/sign_tx_eip1559.py
index d2b99f99..860af3c0 100644
--- a/core/src/apps/ethereum/sign_tx_eip1559.py
+++ b/core/src/apps/ethereum/sign_tx_eip1559.py
@@ -39,7 +39,6 @@ async def sign_tx_eip1559(
from trezor.crypto import rlp # local_cache_global
from trezor.crypto.hashlib import sha3_256
from trezor.ui.layouts import show_continue_in_app
- from trezor.ui.layouts.progress import progress
from trezor.utils import HashWriter
from apps.common import paths
@@ -81,7 +80,9 @@ async def sign_tx_eip1559(
msg.payment_req, slip44_id, keychain, amount_size_bytes=32
)
- await confirm_tx_data(
+ # data chunks will be confirmed during digest (see below)
+ # tx summary will approved before signing the digest (see below)
+ confirm_data_chunk, approve_summary = await confirm_tx_data(
msg,
defs,
None,
@@ -92,14 +93,7 @@ async def sign_tx_eip1559(
payment_req_verifier,
)
- progress_obj = progress(title=TR.progress__signing_transaction)
- progress_obj.report(100)
-
- # transaction data confirmed, proceed with signing
- data = bytearray()
- data += msg.data_initial_chunk
- data_left = data_total - len(msg.data_initial_chunk)
-
+ # digest
total_length = _get_total_length(msg, data_total)
sha = HashWriter(sha3_256(keccak=True))
@@ -120,22 +114,16 @@ async def sign_tx_eip1559(
for field in fields:
rlp.write(sha, field)
- if data_left == 0:
- rlp.write(sha, data)
- else:
- rlp.write_header(sha, data_total, rlp.STRING_HEADER_BYTE, data)
- sha.extend(data)
-
- progress_obj.report(500)
+ await confirm_data_chunk(msg.data_initial_chunk)
+ data_left = data_total - len(msg.data_initial_chunk)
+ rlp.write_header(sha, data_total, rlp.STRING_HEADER_BYTE, msg.data_initial_chunk)
+ sha.extend(msg.data_initial_chunk)
- initial_data_left = data_left
while data_left > 0:
resp = await send_request_chunk(data_left)
+ await confirm_data_chunk(resp.data_chunk)
data_left -= len(resp.data_chunk)
sha.extend(resp.data_chunk)
- progress_obj.report(
- 500 + int((initial_data_left - data_left) / initial_data_left * 400)
- )
# write_access_list
payload_length = sum(access_list_item_length(i) for i in msg.access_list)
@@ -149,10 +137,10 @@ async def sign_tx_eip1559(
rlp.write(sha, item.storage_keys)
digest = sha.get_digest()
+ await approve_summary
+ # transaction data confirmed, proceed with signing
result = _sign_digest(msg, keychain, digest)
- progress_obj.stop()
-
show_continue_in_app(TR.send__transaction_signed)
return result
diff --git a/tests/device_tests/ethereum/test_signtx.py b/tests/device_tests/ethereum/test_signtx.py
index af45da7e..b44149db 100644
--- a/tests/device_tests/ethereum/test_signtx.py
+++ b/tests/device_tests/ethereum/test_signtx.py
@@ -16,13 +16,13 @@
from __future__ import annotations
+import typing as t
from itertools import product
import pytest
from trezorlib import device, ethereum, exceptions, messages, models
from trezorlib.debuglink import DebugSession as Session
-from trezorlib.debuglink import TrezorTestContext as Client
from trezorlib.debuglink import message_filters
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path, unharden
@@ -31,14 +31,15 @@ from ...common import parametrize_using_common_fixtures
from ...definitions import encode_eth_network
from ...input_flows import (
InputFlowConfirmAllWarnings,
- InputFlowEthereumSignTxDataGoBack,
- InputFlowEthereumSignTxDataScrollDown,
- InputFlowEthereumSignTxDataSkip,
+ InputFlowEthereumSignTxData,
InputFlowEthereumSignTxGoBackFromSummary,
InputFlowEthereumSignTxShowFeeInfo,
InputFlowEthereumSignTxStaking,
)
+if t.TYPE_CHECKING:
+ from trezorlib.debuglink import ExpectedResponse
+
TO_ADDR = "0x1d1c328764a41bda0492b66baa30c4a339ff85ef"
@@ -276,39 +277,33 @@ def test_data_streaming(session: Session):
checked in vectorized function above.
"""
with session.test_ctx as client:
- client.set_expected_responses(
- [
- messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
- messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
- messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
- message_filters.EthereumTxRequest(
- data_length=1_024,
- signature_r=None,
- signature_s=None,
- signature_v=None,
- ),
- message_filters.EthereumTxRequest(
- data_length=1_024,
- signature_r=None,
- signature_s=None,
- signature_v=None,
- ),
- message_filters.EthereumTxRequest(
- data_length=1_024,
- signature_r=None,
- signature_s=None,
- signature_v=None,
- ),
- message_filters.EthereumTxRequest(
- data_length=3,
- signature_r=None,
- signature_s=None,
- signature_v=None,
- ),
- message_filters.EthereumTxRequest(data_length=None),
- ]
+ flow = InputFlowEthereumSignTxData(client, scroll=False, cancel=False)
+ flow.confirm_tx = client.ui.default_input_flow()
+ client.set_input_flow(flow.get())
+ is_legacy = client.model in models.LEGACY_MODELS
+
+ br_sign_tx = messages.ButtonRequest(code=messages.ButtonRequestType.SignTx)
+
+ expected_responses: list[ExpectedResponse] = [br_sign_tx]
+ if is_legacy:
+ expected_responses += [br_sign_tx, br_sign_tx]
+
+ expected_responses.extend(
+ message_filters.EthereumTxRequest(
+ data_length=data_length,
+ signature_r=None,
+ signature_s=None,
+ signature_v=None,
+ )
+ for data_length in (1_024, 1_024, 1_024, 3)
)
+ if not is_legacy:
+ expected_responses += [br_sign_tx, br_sign_tx]
+
+ expected_responses += [message_filters.EthereumTxRequest(data_length=None)]
+ client.set_expected_responses(expected_responses)
+
ethereum.sign_tx(
session,
n=parse_path("m/44h/60h/0h/0/0"),
@@ -483,26 +478,12 @@ def test_sanity_checks_eip1559(session: Session):
)
-def input_flow_data_skip(client: Client | Session, cancel: bool = False):
- return InputFlowEthereumSignTxDataSkip(client, cancel).get()
-
-
-def input_flow_data_scroll_down(client: Client | Session, cancel: bool = False):
- return InputFlowEthereumSignTxDataScrollDown(client, cancel).get()
-
-
-def input_flow_data_go_back(client: Client | Session, cancel: bool = False):
- return InputFlowEthereumSignTxDataGoBack(client, cancel).get()
-
-
HEXDATA = "0123456789abcd000023456789abcd010003456789abcd020000456789abcd030000056789abcd040000006789abcd050000000789abcd060000000089abcd070000000009abcd080000000000abcd090000000001abcd0a0000000011abcd0b0000000111abcd0c0000001111abcd0d0000011111abcd0e0000111111abcd0f0000000002abcd100000000022abcd110000000222abcd120000002222abcd130000022222abcd140000222222abcd15"
-@pytest.mark.parametrize(
- "flow", (input_flow_data_skip, input_flow_data_scroll_down, input_flow_data_go_back)
-)
+@pytest.mark.parametrize("scroll", [True, False])
@pytest.mark.models("core")
-def test_signtx_data_pagination(session: Session, flow):
+def test_signtx_data_pagination(session: Session, scroll: bool):
def _sign_tx_call():
ethereum.sign_tx(
session,
@@ -517,16 +498,19 @@ def test_signtx_data_pagination(session: Session, flow):
data=bytes.fromhex(HEXDATA),
)
+ # test pagination
+ flow = InputFlowEthereumSignTxData(session, scroll=scroll, cancel=False)
with session.test_ctx as client:
client.watch_layout()
- client.set_input_flow(flow(client))
+ client.set_input_flow(flow.get())
_sign_tx_call()
- if flow is not input_flow_data_scroll_down:
- with client, pytest.raises(exceptions.Cancelled):
- client.watch_layout()
- client.set_input_flow(flow(session, cancel=True))
- _sign_tx_call()
+ # test cancellation
+ flow = InputFlowEthereumSignTxData(session, scroll=scroll, cancel=True)
+ with client, pytest.raises(exceptions.Cancelled):
+ client.watch_layout()
+ client.set_input_flow(flow.get())
+ _sign_tx_call()
@parametrize_using_common_fixtures("ethereum/sign_tx_staking.json")
diff --git a/tests/input_flows.py b/tests/input_flows.py
index 7611c240..9757eb89 100644
--- a/tests/input_flows.py
+++ b/tests/input_flows.py
@@ -1706,46 +1706,69 @@ class InputFlowEthereumSignTxGoBackFromSummary(InputFlowBase):
yield from self.ETH.confirm_tx(go_back_from_summary=True)
-class InputFlowEthereumSignTxDataSkip(InputFlowBase):
- def __init__(self, client: Client | DebugSession, cancel: bool = False):
+class InputFlowEthereumSignTxData(InputFlowBase):
+ def __init__(self, client: Client | DebugSession, *, scroll: bool, cancel: bool):
super().__init__(client)
+ self.scroll = scroll
self.cancel = cancel
+ self.confirm_tx = self.ETH.confirm_tx()
def input_flow_common(self) -> BRGeneratorType:
- yield from self.ETH.confirm_data()
- yield from self.ETH.confirm_tx(cancel=self.cancel)
-
-
-class InputFlowEthereumSignTxDataScrollDown(InputFlowBase):
- def __init__(self, client: Client | DebugSession, cancel: bool = False):
- super().__init__(client)
- self.cancel = cancel
-
- def input_flow_common(self) -> BRGeneratorType:
- # this flow will not test for the cancel case,
- # because once we enter the "view all data",
- # the only way to cancel is by going back to the 1st page view
- # but that case would be covered by InputFlowEthereumSignTxDataGoBack
- assert not self.cancel
-
- yield from self.ETH.confirm_data(info=True)
- yield from self.ETH.paginate_data()
- yield from self.ETH.confirm_tx()
+ confirm_tx = None # will be used to confirm tx details
+ while True:
+ # first BRs are related to data confirmation
+ br = yield
+ if br.name == "confirm_data":
+ assert br.pages == 1
+ assert confirm_tx is None
+
+ if self.client.layout_type is LayoutType.Eckhart:
+ TR.regexp("ethereum__title_all_input_data_template").fullmatch(
+ self.debug.read_layout().title().strip()
+ )
+ else:
+ assert (
+ TR.ethereum__title_input_data
+ in self.debug.read_layout().title()
+ )
+
+ if self.scroll:
+ self._go_to_next_page()
+ if self.cancel:
+ self.scroll = False # stop pagination & cancel on next page
+ else:
+ if self.cancel:
+ self._cancel_flow()
+ else:
+ self._confirm_all()
+ continue
+
+ # data confirmation is over - confirm tx details
+ if confirm_tx is None:
+ confirm_tx = self.confirm_tx
+ next(confirm_tx)
+
+ confirm_tx.send(br)
+
+ def _go_to_next_page(self):
+ if self.client.layout_type in (LayoutType.Bolt, LayoutType.Caesar):
+ self.debug.press_info() # pagination is a special button
+ elif self.client.layout_type in (LayoutType.Delizia, LayoutType.Eckhart):
+ self.debug.press_yes() # pagination is a regular button
+ else:
+ raise RuntimeError
-class InputFlowEthereumSignTxDataGoBack(InputFlowBase):
- def __init__(self, client: Client | DebugSession, cancel: bool = False):
- super().__init__(client)
- self.cancel = cancel
+ def _cancel_flow(self):
+ self.debug.press_no()
- def input_flow_common(self) -> BRGeneratorType:
- yield from self.ETH.confirm_data(info=True)
- yield from self.ETH.paginate_data_go_back()
- if self.cancel:
- yield from self.ETH.confirm_data(cancel=True)
+ def _confirm_all(self):
+ if self.client.layout_type in (LayoutType.Bolt, LayoutType.Caesar):
+ self.debug.press_yes() # confirmation is a regular button
+ elif self.client.layout_type in (LayoutType.Delizia, LayoutType.Eckhart):
+ self.debug.press_info() # confirmation is available via menu
else:
- yield from self.ETH.confirm_data()
- yield from self.ETH.confirm_tx()
+ raise RuntimeError
class InputFlowEthereumSignTxStaking(InputFlowBase):
diff --git a/tests/input_flows_helpers.py b/tests/input_flows_helpers.py
index e78bae98..04ee2d20 100644
--- a/tests/input_flows_helpers.py
+++ b/tests/input_flows_helpers.py
@@ -5,7 +5,6 @@ from trezorlib.debuglink import LayoutType
from trezorlib.debuglink import TrezorTestContext as Client
from . import translations as TR
-from .click_tests.common import go_next
from .common import BRGeneratorType, get_text_possible_pagination
B = messages.ButtonRequestType
@@ -419,88 +418,10 @@ class RecoveryFlow:
class EthereumFlow:
- GO_BACK = (16, 220)
-
def __init__(self, client: Client):
self.client = client
self.debug = self.client.debug
- def confirm_data(self, info: bool = False, cancel: bool = False) -> BRGeneratorType:
- assert (yield).name == "confirm_data"
- if self.client.layout_type is LayoutType.Eckhart:
- TR.regexp("ethereum__title_all_input_data_template").fullmatch(
- self.debug.read_layout().title().strip()
- )
- else:
- assert TR.ethereum__title_input_data in self.debug.read_layout().title()
- if info:
- self.debug.press_info()
- elif cancel:
- self.debug.press_no()
- else:
- self.debug.press_yes()
-
- def paginate_data(self) -> BRGeneratorType:
- br = yield
- assert br.name == "confirm_data"
- assert br.pages is not None
- if self.client.layout_type is LayoutType.Eckhart:
- TR.regexp("ethereum__title_all_input_data_template").fullmatch(
- self.debug.read_layout().title().strip()
- )
- else:
- assert TR.ethereum__title_input_data in self.debug.read_layout().title()
- for _ in range(br.pages - 1):
- self.debug.read_layout()
- go_next(self.debug)
- if self.client.layout_type in (LayoutType.Bolt, LayoutType.Caesar):
- self.debug.read_layout()
- go_next(self.debug)
- self.debug.read_layout()
- elif self.client.layout_type is LayoutType.Delizia:
- self.debug.read_layout()
- self.debug.click(self.debug.screen_buttons.tap_to_confirm())
- elif self.client.layout_type is LayoutType.Eckhart:
- self.debug.read_layout()
- self.debug.click(self.debug.screen_buttons.ok())
-
- def paginate_data_go_back(self) -> BRGeneratorType:
- br = yield
- assert br.name == "confirm_data"
- assert br.pages is not None
- assert br.pages > 2
- if self.client.layout_type is LayoutType.Eckhart:
- TR.regexp("ethereum__title_all_input_data_template").fullmatch(
- self.debug.read_layout().title().strip()
- )
- else:
- assert TR.ethereum__title_input_data in self.debug.read_layout().title()
- if self.client.layout_type is LayoutType.Bolt:
- self.debug.swipe_up()
- self.debug.swipe_up()
- self.debug.click(self.GO_BACK)
- elif self.client.layout_type is LayoutType.Caesar:
- self.debug.press_right()
- self.debug.press_right()
- self.debug.press_left()
- self.debug.press_left()
- self.debug.press_left()
- elif self.client.layout_type is LayoutType.Delizia:
- # Scroll to the last page data page
- for _ in range(br.pages - 2):
- self.debug.swipe_up()
- # Close the menu wuth the cross button
- self.debug.click(self.debug.screen_buttons.menu())
- elif self.client.layout_type is LayoutType.Eckhart:
- # Scroll to the last page
- for _ in range(br.pages - 1):
- self.debug.click(self.debug.screen_buttons.ok())
- # Go back to the first page and then cancel
- for _ in range(br.pages):
- self.debug.click(self.debug.screen_buttons.cancel())
- else:
- raise ValueError(f"Unknown layout: {self.client.layout_type}")
-
def _confirm_tx_bolt(
self, cancel: bool, info: bool, go_back_from_summary: bool
) -> BRGeneratorType:
Why this scored 72/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.