refactor(core/ethereum): rename and move keccak-related helper functions
What changed, and why it matters
This is a clean internal code cleanup in the Ethereum app of the Trezor firmware. It moves the Keccak hashing helper to a shared location and updates callers to use it. There is no user-facing behavior change and no security fix.
No security action needed. Treat as normal maintenance/refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors Keccak-256 usage across core/src/apps/ethereum/*.py by introducing a single keccak256() helper in helpers.py that returns a HashWriter. Call sites are updated to use this helper instead of directly instantiating HashWriter(sha3_256(keccak=True)). In address_from_bytes, the implementation now streams the prefix and hex address into the HashWriter rather than concatenating first. The functional output is unchanged; tests are updated only to call .get_digest() on the new helper’s return value.
Changed components
core/src/apps/ethereum/helpers.pycore/src/apps/ethereum/sign_message.pycore/src/apps/ethereum/sign_tx.pycore/src/apps/ethereum/sign_tx_eip1559.pycore/src/apps/ethereum/sign_typed_data.pycore/src/apps/ethereum/verify_message.pycore/tests/test_apps.ethereum.sign_typed_data.pyInspect captured patch +35 / −46
diff --git a/core/src/apps/ethereum/helpers.py b/core/src/apps/ethereum/helpers.py
index 2b0b6776..c476c4ce 100644
--- a/core/src/apps/ethereum/helpers.py
+++ b/core/src/apps/ethereum/helpers.py
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
from trezor.messages import EthereumFieldType, EthereumTokenInfo
from trezor.ui.layouts import StrPropertyType
+ from trezor.utils import HashWriter
from .networks import EthereumNetworkInfo
@@ -27,21 +28,21 @@ def address_from_bytes(
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.chain_id in RSKIP60_NETWORKS:
# rskip60 is a different way to calculate checksum
prefix = str(network.chain_id) + "0x"
else:
prefix = ""
- address_hex = hexlify(address_bytes).decode()
- digest = sha3_256((prefix + address_hex).encode(), keccak=True).digest()
+ address_hex = hexlify(address_bytes)
+ writer = keccak256(prefix.encode())
+ writer.extend(address_hex)
+ digest = writer.get_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]
+ hex_letter = chr(address_hex[i])
if i % 2 == 0:
# even letter -> high nibble
bit = 0x80
@@ -303,3 +304,10 @@ def get_data_confirmer(total_len: int) -> ConfirmDataFn:
return
return confirm_fn
+
+
+def keccak256(data: AnyBytes | None = None) -> HashWriter:
+ from trezor.crypto.hashlib import sha3_256
+ from trezor.utils import HashWriter
+
+ return HashWriter(sha3_256(data=data, keccak=True))
diff --git a/core/src/apps/ethereum/sign_message.py b/core/src/apps/ethereum/sign_message.py
index ce7a7949..312a1f12 100644
--- a/core/src/apps/ethereum/sign_message.py
+++ b/core/src/apps/ethereum/sign_message.py
@@ -13,12 +13,9 @@ if TYPE_CHECKING:
def message_digest(message: AnyBytes) -> bytes:
- from trezor.crypto.hashlib import sha3_256
- from trezor.utils import HashWriter
+ from .helpers import keccak256
- h = HashWriter(sha3_256(keccak=True))
- signed_message_header = b"\x19Ethereum Signed Message:\n"
- h.extend(signed_message_header)
+ h = keccak256(b"\x19Ethereum Signed Message:\n")
h.extend(str(len(message)).encode())
h.extend(message)
return h.get_digest()
diff --git a/core/src/apps/ethereum/sign_tx.py b/core/src/apps/ethereum/sign_tx.py
index 22016159..6e4979ef 100644
--- a/core/src/apps/ethereum/sign_tx.py
+++ b/core/src/apps/ethereum/sign_tx.py
@@ -42,13 +42,11 @@ async def sign_tx(
keychain: Keychain,
defs: Definitions,
) -> EthereumTxRequest:
- from trezor.crypto.hashlib import sha3_256
from trezor.ui.layouts import show_continue_in_app
- from trezor.utils import HashWriter
from apps.common import paths
- from .helpers import format_ethereum_amount, get_fee_items_regular
+ from .helpers import format_ethereum_amount, get_fee_items_regular, keccak256
# local_cache_attribute
data_length = msg.data_length
@@ -89,7 +87,7 @@ async def sign_tx(
amount_size_bytes=32,
)
- sha = HashWriter(sha3_256(keccak=True))
+ sha = keccak256()
rlp.write_header(sha, _get_digest_length(msg, data_length), rlp.LIST_HEADER_BYTE)
if tx_type is not None:
diff --git a/core/src/apps/ethereum/sign_tx_eip1559.py b/core/src/apps/ethereum/sign_tx_eip1559.py
index abd259dd..252ae9eb 100644
--- a/core/src/apps/ethereum/sign_tx_eip1559.py
+++ b/core/src/apps/ethereum/sign_tx_eip1559.py
@@ -37,13 +37,11 @@ async def sign_tx_eip1559(
) -> EthereumTxRequest:
from trezor import TR, wire
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.utils import HashWriter
from apps.common import paths
- from .helpers import format_ethereum_amount, get_fee_items_eip1559
+ from .helpers import format_ethereum_amount, get_fee_items_eip1559, keccak256
from .sign_tx import (
check_common_fields,
confirm_data_and_summary,
@@ -86,7 +84,7 @@ async def sign_tx_eip1559(
msg.payment_req, slip44_id, keychain, amount_size_bytes=32
)
- sha = HashWriter(sha3_256(keccak=True))
+ sha = keccak256()
rlp.write(sha, _TX_TYPE)
rlp.write_header(sha, _get_digest_length(msg, data_length), rlp.LIST_HEADER_BYTE)
diff --git a/core/src/apps/ethereum/sign_typed_data.py b/core/src/apps/ethereum/sign_typed_data.py
index 276c8c3d..7786c94c 100644
--- a/core/src/apps/ethereum/sign_typed_data.py
+++ b/core/src/apps/ethereum/sign_typed_data.py
@@ -4,7 +4,7 @@ from trezor.enums import EthereumDataType
from trezor.wire import DataError
from trezor.wire.context import call
-from .helpers import get_type_name
+from .helpers import get_type_name, keccak256
from .keychain import PATTERNS_ADDRESS, with_keychain_from_path
from .layout import should_show_struct
@@ -137,19 +137,9 @@ async def _generate_typed_data_hash(
await confirm_typed_data_final()
- return keccak256(b"\x19\x01" + domain_separator + message_hash)
-
-
-def get_hash_writer() -> HashWriter:
- from trezor.crypto.hashlib import sha3_256
- from trezor.utils import HashWriter
-
- return HashWriter(sha3_256(keccak=True))
-
-
-def keccak256(message: AnyBytes) -> bytes:
- h = get_hash_writer()
- h.extend(message)
+ h = keccak256(b"\x19\x01")
+ h.extend(domain_separator)
+ h.extend(message_hash)
return h.get_digest()
@@ -215,7 +205,7 @@ class TypedDataEnvelope:
report_progress: Callable[[float], None] | None = None,
) -> bytes:
"""Generate a hash representation of the whole struct."""
- w = get_hash_writer()
+ w = keccak256()
self.hash_type(w, primary_type)
await self.get_and_encode_data(
w,
@@ -229,8 +219,7 @@ class TypedDataEnvelope:
def hash_type(self, w: HashWriter, primary_type: str) -> None:
"""Create a representation of a type."""
- result = keccak256(self.encode_type(primary_type))
- w.extend(result)
+ w.extend(keccak256(self.encode_type(primary_type)).get_digest())
def encode_type(self, primary_type: str) -> bytes:
"""
@@ -348,7 +337,7 @@ class TypedDataEnvelope:
else:
show_array = False
- arr_w = get_hash_writer()
+ arr_w = keccak256()
el_member_path = member_value_path + [0]
for i in range(array_size):
el_member_path[-1] = i
@@ -435,7 +424,7 @@ def encode_field(
if data_type == EDT.BYTES:
if field.size is None:
- w.extend(keccak256(value))
+ w.extend(keccak256(value).get_digest())
else:
# write_rightpad32
assert len(value) <= 32
@@ -443,7 +432,7 @@ def encode_field(
for _ in range(32 - len(value)):
w.append(0x00)
elif data_type == EDT.STRING:
- w.extend(keccak256(value))
+ w.extend(keccak256(value).get_digest())
elif data_type == EDT.INT:
write_leftpad32(w, value, signed=True)
elif data_type in (
diff --git a/core/src/apps/ethereum/verify_message.py b/core/src/apps/ethereum/verify_message.py
index 227e45bf..6f9bdc4a 100644
--- a/core/src/apps/ethereum/verify_message.py
+++ b/core/src/apps/ethereum/verify_message.py
@@ -7,14 +7,13 @@ if TYPE_CHECKING:
async def verify_message(msg: EthereumVerifyMessage) -> Success:
from trezor import TR
from trezor.crypto.curve import secp256k1
- from trezor.crypto.hashlib import sha3_256
from trezor.messages import Success
from trezor.ui.layouts import confirm_signverify, show_success
from trezor.wire import DataError
from apps.common.signverify import decode_message
- from .helpers import address_from_bytes, bytes_from_address
+ from .helpers import address_from_bytes, bytes_from_address, keccak256
from .sign_message import message_digest
digest = message_digest(msg.message)
@@ -27,7 +26,7 @@ async def verify_message(msg: EthereumVerifyMessage) -> Success:
if not pubkey:
raise DataError("Invalid signature")
- pkh = sha3_256(pubkey[1:], keccak=True).digest()[-20:]
+ pkh = keccak256(pubkey[1:]).get_digest()[-20:]
address_bytes = bytes_from_address(msg.address)
if address_bytes != pkh:
diff --git a/core/tests/test_apps.ethereum.sign_typed_data.py b/core/tests/test_apps.ethereum.sign_typed_data.py
index f992028c..cde9763c 100644
--- a/core/tests/test_apps.ethereum.sign_typed_data.py
+++ b/core/tests/test_apps.ethereum.sign_typed_data.py
@@ -441,14 +441,14 @@ class TestEthereumSignTypedData(unittest.TestCase):
"EIP712Domain",
keccak256(
b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
- ),
+ ).get_digest(),
),
- ("Person", keccak256(b"Person(string name,address wallet)")),
+ ("Person", keccak256(b"Person(string name,address wallet)").get_digest()),
(
"Mail",
keccak256(
b"Mail(Person from,Person to,string contents)Person(string name,address wallet)"
- ),
+ ).get_digest(),
),
)
@@ -497,12 +497,12 @@ class TestEthereumSignTypedData(unittest.TestCase):
(
EFT(data_type=EDT.STRING, size=None),
b"Ether Mail",
- keccak256(b"Ether Mail"),
+ keccak256(b"Ether Mail").get_digest(),
),
(
EFT(data_type=EDT.STRING, size=None),
b"1",
- keccak256(b"1"),
+ keccak256(b"1").get_digest(),
),
(
EFT(data_type=EDT.UINT, size=32),
Why this scored 15/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.