What changed, and why it matters
This patch changes how a Trezor hardware wallet handles EIP-712 typed data signing for Ethereum. It now caches (stores) the values shown to the user for the domain's name and version fields, and reuses those same cached values when later computing the cryptographic hash. The goal is to prevent a malicious computer or app from showing the user one set of values on screen, but then tricking the device into signing a different set of values. The changelog file labels this as a security fix: 'Cache confirmed EIP-712 domain.'
Treat this as a security fix and ensure it is included in firmware builds. Users signing EIP-712 messages (e.g., MetaMask, DeFi, account abstraction) should update to a firmware release containing this patch. Developers should review whether similar confirmation-vs-signing value caching is needed elsewhere in the Ethereum signing paths.
Security signals we found
Changelog entry explicitly marked as security: 'Cache confirmed EIP-712 domain.'
Patch caches user-confirmed domain values and reuses them during hash computation
Potential TOCTOU/data-replay issue between confirmation and signing
Fix is a cherry-pick, indicating it was backported for a security/maintenance release
No CVE, advisory, or researcher attribution present in commit materials
Evidence from the diff
In core/src/apps/ethereum/sign_typed_data.py, the code adds a prefetched_eip712_values dictionary to the TypedDataEnvelope class. During _get_name_and_version_for_domain(), the name and version field values are fetched via get_value(), displayed/confirmed, and now also cached. Later, in hash_struct() for the EIP712Domain primary type, if a field’s path key exists in the cache, the cached value is used instead of calling get_value() again. This closes a potential TOCTOU-style issue where the host-supplied data stream could return different bytes on the second fetch than what was confirmed by the user, leading to a mismatch between confirmed UI and signed hash.
Changed components
core/src/apps/ethereum/sign_typed_data.pyTrezor Ethereum EIP-712 typed data signing flowInspect captured patch +20 / −6
diff --git a/core/.changelog.d/246.security b/core/.changelog.d/246.security
new file mode 100644
index 00000000..af6b6452
--- /dev/null
+++ b/core/.changelog.d/246.security
@@ -0,0 +1 @@
+Cache confirmed EIP-712 domain.
diff --git a/core/src/apps/ethereum/sign_typed_data.py b/core/src/apps/ethereum/sign_typed_data.py
index 0d8af5e0..276c8c3d 100644
--- a/core/src/apps/ethereum/sign_typed_data.py
+++ b/core/src/apps/ethereum/sign_typed_data.py
@@ -47,6 +47,7 @@ async def sign_typed_data(
# Display address so user can validate it
await require_confirm_address(address_bytes)
+
data_hash = await _generate_typed_data_hash(
msg.primary_type, msg.metamask_v4_compat, msg.show_message_hash
)
@@ -95,6 +96,7 @@ async def _generate_typed_data_hash(
name, version = await _get_name_and_version_for_domain(typed_data_envelope)
show_domain = await should_show_domain(name, version)
+
domain_separator = await typed_data_envelope.hash_struct(
"EIP712Domain",
[0],
@@ -161,6 +163,7 @@ class TypedDataEnvelope:
) -> None:
self.primary_type = primary_type
self.metamask_v4_compat = metamask_v4_compat
+ self.prefetched_eip712_values: dict[tuple[int, ...], AnyBytes] = {}
self.types: dict[str, EthereumTypedDataStructAck] = {}
async def collect_types(
@@ -387,7 +390,14 @@ class TypedDataEnvelope:
)
w.extend(arr_w.get_digest())
else:
- value = await get_value(field_type, member_value_path)
+ path_key = tuple(member_value_path)
+ if (
+ primary_type == "EIP712Domain"
+ and path_key in self.prefetched_eip712_values
+ ):
+ value = self.prefetched_eip712_values[path_key]
+ else:
+ value = await get_value(field_type, member_value_path)
encode_field(w, field_type, value)
if show_data:
await confirm_typed_value(
@@ -576,9 +586,12 @@ async def _get_name_and_version_for_domain(
member_value_path = [0, 0]
for member_index, member in enumerate(domain_members):
member_value_path[-1] = member_index
- if member.name == "name":
- domain_name = await get_value(member.type, member_value_path)
- elif member.name == "version":
- domain_version = await get_value(member.type, member_value_path)
-
+ if member.name in ("name", "version"):
+ value = await get_value(member.type, member_value_path)
+ path_key = tuple(member_value_path)
+ typed_data_envelope.prefetched_eip712_values[path_key] = value
+ if member.name == "name":
+ domain_name = value
+ elif member.name == "version":
+ domain_version = value
return domain_name, domain_version
Why this scored 57/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.