feat(clear_signing): externalise provider names
What changed, and why it matters
This commit adds a new optional 'provider_name' field to the data structure used for Ethereum 'clear signing' on Trezor devices. When a provider name is supplied, the device will show that name instead of the usual recipient address lookup during transaction confirmation. The change itself is a feature addition, but it creates a new channel where a connected computer could tell the Trezor screen to display an arbitrary label for the recipient. There is no evidence in the commit that the input is validated, length-limited, or checked against the actual blockchain address, which could allow a malicious or compromised host application to mislabel a transaction recipient.
Treat this as a UI-spoofing risk requiring follow-up review. Verify whether provider_name is validated (length, charset, matching address or registry entry) before display, whether the device still shows the underlying address somewhere on the confirmation screen, and whether the host-side code that populates provider_name is trustworthy. If no validation exists, add allowlist/registry checks or at least co-display the real address to prevent recipient spoofing.
Security signals we found
New untrusted string field (provider_name) rendered in transaction UI
Provider name replaces address-book lookup, potentially hiding true recipient
No visible input validation, length limits, or allowlist in the diff
Clear-signing UI is a security-critical user confirmation surface
Commit is marked [no changelog] and contains no security discussion
Evidence from the diff
The patch extends the EthereumDisplayFormatInfo protobuf message with an optional string provider_name (field 7) and threads it through generated Python and Rust message classes into the DisplayFormat class in core/src/apps/ethereum/clear_signing.py. In _handle_generic_ui, the code now uses display_format.provider_name in place of the address-book lookup (lookup_known_address) when the field is set. The two built-in display formats (approve and transfer) pass provider_name=None, preserving prior behavior. The new field is optional and defaults to None across bindings. No sanitization, length limits, or correlation checks against msg.to or the address lookup are visible in the diff.
Changed components
core/src/apps/ethereum/clear_signing.pycore/src/apps/ethereum/clear_signing_definitions.pycommon/protob/messages-definitions.protocore/src/trezor/messages.pypython/src/trezorlib/messages.pyrust/trezor-client/src/protos/generated/messages_definitions.rsInspect captured patch +90 / −23
diff --git a/common/protob/messages-definitions.proto b/common/protob/messages-definitions.proto
index a5791718..c1410490 100644
--- a/common/protob/messages-definitions.proto
+++ b/common/protob/messages-definitions.proto
@@ -201,4 +201,5 @@ message EthereumDisplayFormatInfo {
required string intent = 4;
repeated EthereumABIValueInfo parameter_definitions = 5;
repeated EthereumERC7730FieldInfo field_definitions = 6;
+ optional string provider_name = 7;
}
diff --git a/core/src/apps/ethereum/clear_signing.py b/core/src/apps/ethereum/clear_signing.py
index 25b11dd8..cd1e1541 100644
--- a/core/src/apps/ethereum/clear_signing.py
+++ b/core/src/apps/ethereum/clear_signing.py
@@ -805,11 +805,13 @@ class DisplayFormat:
self,
binding_context: BindingContext | None,
func_sig: bytes,
+ provider_name: str | None,
intent: str,
parameter_definitions: list[ABIValue],
field_definitions: list[FieldDefinition],
) -> None:
self.binding_context = binding_context
+ self.provider_name = provider_name
self.func_sig = func_sig
self.intent = intent
self.parameter_definitions = parameter_definitions
@@ -956,6 +958,7 @@ class DisplayFormat:
binding_context=BindingContext([(proto.chain_id, bytes(proto.address))]),
func_sig=bytes(proto.func_sig),
intent=proto.intent,
+ provider_name=proto.provider_name,
parameter_definitions=[
ABIValue.from_proto(p) for p in proto.parameter_definitions
],
@@ -1275,7 +1278,9 @@ async def _handle_generic_ui(
properties_to_confirm.append(token_address_property)
recipient_str = (
- lookup_known_address(msg.chain_id, bytes_from_address(msg.to)) or msg.to
+ (lookup_known_address(msg.chain_id, bytes_from_address(msg.to)) or msg.to)
+ if display_format.provider_name is None
+ else display_format.provider_name
)
await require_confirm_clear_signing(
diff --git a/core/src/apps/ethereum/clear_signing_definitions.py b/core/src/apps/ethereum/clear_signing_definitions.py
index b09ae49d..9b69d308 100644
--- a/core/src/apps/ethereum/clear_signing_definitions.py
+++ b/core/src/apps/ethereum/clear_signing_definitions.py
@@ -17,6 +17,7 @@ APPROVE_DISPLAY_FORMAT = DisplayFormat(
binding_context=None,
func_sig=b"\x09\x5e\xa7\xb3", # approve(address,uint256)
intent="Approve",
+ provider_name=None,
parameter_definitions=[
Atomic(parse_address), # _spender
Atomic(parse_uint256), # _value
@@ -38,6 +39,7 @@ TRANSFER_DISPLAY_FORMAT = DisplayFormat(
binding_context=None,
func_sig=b"\xa9\x05\x9c\xbb", # transfer(address,uint256)
intent="Send",
+ provider_name=None,
parameter_definitions=[
Atomic(parse_address), # _to
Atomic(parse_uint256), # _value
diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py
index fdd5a56f..eaab35eb 100644
--- a/core/src/trezor/messages.py
+++ b/core/src/trezor/messages.py
@@ -3425,6 +3425,7 @@ if TYPE_CHECKING:
intent: "str"
parameter_definitions: "list[EthereumABIValueInfo]"
field_definitions: "list[EthereumERC7730FieldInfo]"
+ provider_name: "str | None"
def __init__(
self,
@@ -3435,6 +3436,7 @@ if TYPE_CHECKING:
intent: "str",
parameter_definitions: "list[EthereumABIValueInfo] | None" = None,
field_definitions: "list[EthereumERC7730FieldInfo] | None" = None,
+ provider_name: "str | None" = None,
) -> None:
pass
diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py
index 6c53b458..fe940ab4 100644
--- a/python/src/trezorlib/messages.py
+++ b/python/src/trezorlib/messages.py
@@ -4886,6 +4886,7 @@ class EthereumDisplayFormatInfo(protobuf.MessageType):
4: protobuf.Field("intent", "string", repeated=False, required=True),
5: protobuf.Field("parameter_definitions", "EthereumABIValueInfo", repeated=True, required=False, default=None),
6: protobuf.Field("field_definitions", "EthereumERC7730FieldInfo", repeated=True, required=False, default=None),
+ 7: protobuf.Field("provider_name", "string", repeated=False, required=False, default=None),
}
def __init__(
@@ -4897,6 +4898,7 @@ class EthereumDisplayFormatInfo(protobuf.MessageType):
intent: "str",
parameter_definitions: Optional[Sequence["EthereumABIValueInfo"]] = None,
field_definitions: Optional[Sequence["EthereumERC7730FieldInfo"]] = None,
+ provider_name: Optional["str"] = None,
) -> None:
self.parameter_definitions: Sequence["EthereumABIValueInfo"] = parameter_definitions if parameter_definitions is not None else []
self.field_definitions: Sequence["EthereumERC7730FieldInfo"] = field_definitions if field_definitions is not None else []
@@ -4904,6 +4906,7 @@ class EthereumDisplayFormatInfo(protobuf.MessageType):
self.address = address
self.func_sig = func_sig
self.intent = intent
+ self.provider_name = provider_name
class EosGetPublicKey(protobuf.MessageType):
diff --git a/rust/trezor-client/src/protos/generated/messages_definitions.rs b/rust/trezor-client/src/protos/generated/messages_definitions.rs
index 869ca675..dd754c67 100644
--- a/rust/trezor-client/src/protos/generated/messages_definitions.rs
+++ b/rust/trezor-client/src/protos/generated/messages_definitions.rs
@@ -2078,6 +2078,8 @@ pub struct EthereumDisplayFormatInfo {
pub parameter_definitions: ::std::vec::Vec<EthereumABIValueInfo>,
// @@protoc_insertion_point(field:hw.trezor.messages.definitions.EthereumDisplayFormatInfo.field_definitions)
pub field_definitions: ::std::vec::Vec<EthereumERC7730FieldInfo>,
+ // @@protoc_insertion_point(field:hw.trezor.messages.definitions.EthereumDisplayFormatInfo.provider_name)
+ pub provider_name: ::std::option::Option<::std::string::String>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.definitions.EthereumDisplayFormatInfo.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@@ -2221,8 +2223,44 @@ impl EthereumDisplayFormatInfo {
self.intent.take().unwrap_or_else(|| ::std::string::String::new())
}
+ // optional string provider_name = 7;
+
+ pub fn provider_name(&self) -> &str {
+ match self.provider_name.as_ref() {
+ Some(v) => v,
+ None => "",
+ }
+ }
+
+ pub fn clear_provider_name(&mut self) {
+ self.provider_name = ::std::option::Option::None;
+ }
+
+ pub fn has_provider_name(&self) -> bool {
+ self.provider_name.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_provider_name(&mut self, v: ::std::string::String) {
+ self.provider_name = ::std::option::Option::Some(v);
+ }
+
+ // Mutable pointer to the field.
+ // If field is not initialized, it is initialized with default value first.
+ pub fn mut_provider_name(&mut self) -> &mut ::std::string::String {
+ if self.provider_name.is_none() {
+ self.provider_name = ::std::option::Option::Some(::std::string::String::new());
+ }
+ self.provider_name.as_mut().unwrap()
+ }
+
+ // Take field
+ pub fn take_provider_name(&mut self) -> ::std::string::String {
+ self.provider_name.take().unwrap_or_else(|| ::std::string::String::new())
+ }
+
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
- let mut fields = ::std::vec::Vec::with_capacity(6);
+ let mut fields = ::std::vec::Vec::with_capacity(7);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"chain_id",
@@ -2254,6 +2292,11 @@ impl EthereumDisplayFormatInfo {
|m: &EthereumDisplayFormatInfo| { &m.field_definitions },
|m: &mut EthereumDisplayFormatInfo| { &mut m.field_definitions },
));
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "provider_name",
+ |m: &EthereumDisplayFormatInfo| { &m.provider_name },
+ |m: &mut EthereumDisplayFormatInfo| { &mut m.provider_name },
+ ));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EthereumDisplayFormatInfo>(
"EthereumDisplayFormatInfo",
fields,
@@ -2312,6 +2355,9 @@ impl ::protobuf::Message for EthereumDisplayFormatInfo {
50 => {
self.field_definitions.push(is.read_message()?);
},
+ 58 => {
+ self.provider_name = ::std::option::Option::Some(is.read_string()?);
+ },
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
@@ -2344,6 +2390,9 @@ impl ::protobuf::Message for EthereumDisplayFormatInfo {
let len = value.compute_size();
my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len;
};
+ if let Some(v) = self.provider_name.as_ref() {
+ my_size += ::protobuf::rt::string_size(7, &v);
+ }
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
@@ -2368,6 +2417,9 @@ impl ::protobuf::Message for EthereumDisplayFormatInfo {
for v in &self.field_definitions {
::protobuf::rt::write_message_field_with_cached_size(6, v, os)?;
};
+ if let Some(v) = self.provider_name.as_ref() {
+ os.write_string(7, v)?;
+ }
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
@@ -2391,6 +2443,7 @@ impl ::protobuf::Message for EthereumDisplayFormatInfo {
self.intent = ::std::option::Option::None;
self.parameter_definitions.clear();
self.field_definitions.clear();
+ self.provider_name = ::std::option::Option::None;
self.special_fields.clear();
}
@@ -2402,6 +2455,7 @@ impl ::protobuf::Message for EthereumDisplayFormatInfo {
intent: ::std::option::Option::None,
parameter_definitions: ::std::vec::Vec::new(),
field_definitions: ::std::vec::Vec::new(),
+ provider_name: ::std::option::Option::None,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
@@ -2875,7 +2929,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\n\tthreshold\x18\x05\x20\x01(\x0cR\tthreshold\x12\x1a\n\x08decimals\x18\
\x06\x20\x01(\rR\x08decimals\x12\x12\n\x04base\x18\x07\x20\x01(\tR\x04ba\
se\x12\x16\n\x06prefix\x18\x08\x20\x01(\x08R\x06prefix\x12.\n\x13const_t\
- oken_address\x18\t\x20\x01(\x0cR\x11constTokenAddress\"\xd5\x02\n\x19Eth\
+ oken_address\x18\t\x20\x01(\x0cR\x11constTokenAddress\"\xfa\x02\n\x19Eth\
ereumDisplayFormatInfo\x12\x19\n\x08chain_id\x18\x01\x20\x02(\x04R\x07ch\
ainId\x12\x18\n\x07address\x18\x02\x20\x02(\x0cR\x07address\x12\x19\n\
\x08func_sig\x18\x03\x20\x02(\x0cR\x07funcSig\x12\x16\n\x06intent\x18\
@@ -2883,26 +2937,26 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\x03(\x0b24.hw.trezor.messages.definitions.EthereumABIValueInfoR\x14para\
meterDefinitions\x12e\n\x11field_definitions\x18\x06\x20\x03(\x0b28.hw.t\
rezor.messages.definitions.EthereumERC7730FieldInfoR\x10fieldDefinitions\
- *i\n\x0eDefinitionType\x12\x14\n\x10ETHEREUM_NETWORK\x10\0\x12\x12\n\x0e\
- ETHEREUM_TOKEN\x10\x01\x12\x10\n\x0cSOLANA_TOKEN\x10\x02\x12\x1b\n\x17ET\
- HEREUM_DISPLAY_FORMAT\x10\x03*\x86\x03\n\x0fEthereumABIType\x12\x0f\n\
- \x0bABI_ADDRESS\x10\0\x12\x0f\n\x0bABI_UINT256\x10\x01\x12\x0f\n\x0bABI_\
- UINT248\x10\x02\x12\x0f\n\x0bABI_UINT160\x10\x03\x12\x0f\n\x0bABI_UINT12\
- 8\x10\x04\x12\x0f\n\x0bABI_UINT120\x10\x05\x12\x0f\n\x0bABI_UINT112\x10\
- \x06\x12\x0e\n\nABI_UINT96\x10\x07\x12\x0e\n\nABI_UINT72\x10\x08\x12\x0e\
- \n\nABI_UINT64\x10\t\x12\x0e\n\nABI_UINT48\x10\n\x12\x0e\n\nABI_UINT40\
- \x10\x0b\x12\x0e\n\nABI_UINT32\x10\x0c\x12\x0e\n\nABI_UINT24\x10\r\x12\
- \x0e\n\nABI_UINT16\x10\x0e\x12\r\n\tABI_UINT8\x10\x0f\x12\x0c\n\x08ABI_B\
- OOL\x10\x10\x12\x0f\n\x0bABI_BYTES32\x10\x14\x12\x0f\n\x0bABI_BYTES16\
- \x10\x15\x12\x0e\n\nABI_BYTES8\x10\x16\x12\x0e\n\nABI_BYTES4\x10\x17\x12\
- \r\n\tABI_BYTES\x10\x1e\x12\x0e\n\nABI_STRING\x10\x1f*\xac\x01\n!Ethereu\
- mERC7730FieldFormatterType\x12\x1a\n\x16FORMATTER_ADDRESS_NAME\x10\0\x12\
- \x14\n\x10FORMATTER_AMOUNT\x10\x01\x12\x1a\n\x16FORMATTER_TOKEN_AMOUNT\
- \x10\x02\x12\x12\n\x0eFORMATTER_UNIT\x10\x03\x12\x11\n\rFORMATTER_RAW\
- \x10\x04\x12\x12\n\x0eFORMATTER_DATE\x10\x05*;\n\x1cEthereumERC7730Conta\
- inerPath\x12\x08\n\x04FROM\x10\x01\x12\t\n\x05VALUE\x10\x02\x12\x06\n\
- \x02TO\x10\x03B?\n#com.satoshilabs.trezor.lib.protobufB\x18TrezorMessage\
- Definitions\
+ \x12#\n\rprovider_name\x18\x07\x20\x01(\tR\x0cproviderName*i\n\x0eDefini\
+ tionType\x12\x14\n\x10ETHEREUM_NETWORK\x10\0\x12\x12\n\x0eETHEREUM_TOKEN\
+ \x10\x01\x12\x10\n\x0cSOLANA_TOKEN\x10\x02\x12\x1b\n\x17ETHEREUM_DISPLAY\
+ _FORMAT\x10\x03*\x86\x03\n\x0fEthereumABIType\x12\x0f\n\x0bABI_ADDRESS\
+ \x10\0\x12\x0f\n\x0bABI_UINT256\x10\x01\x12\x0f\n\x0bABI_UINT248\x10\x02\
+ \x12\x0f\n\x0bABI_UINT160\x10\x03\x12\x0f\n\x0bABI_UINT128\x10\x04\x12\
+ \x0f\n\x0bABI_UINT120\x10\x05\x12\x0f\n\x0bABI_UINT112\x10\x06\x12\x0e\n\
+ \nABI_UINT96\x10\x07\x12\x0e\n\nABI_UINT72\x10\x08\x12\x0e\n\nABI_UINT64\
+ \x10\t\x12\x0e\n\nABI_UINT48\x10\n\x12\x0e\n\nABI_UINT40\x10\x0b\x12\x0e\
+ \n\nABI_UINT32\x10\x0c\x12\x0e\n\nABI_UINT24\x10\r\x12\x0e\n\nABI_UINT16\
+ \x10\x0e\x12\r\n\tABI_UINT8\x10\x0f\x12\x0c\n\x08ABI_BOOL\x10\x10\x12\
+ \x0f\n\x0bABI_BYTES32\x10\x14\x12\x0f\n\x0bABI_BYTES16\x10\x15\x12\x0e\n\
+ \nABI_BYTES8\x10\x16\x12\x0e\n\nABI_BYTES4\x10\x17\x12\r\n\tABI_BYTES\
+ \x10\x1e\x12\x0e\n\nABI_STRING\x10\x1f*\xac\x01\n!EthereumERC7730FieldFo\
+ rmatterType\x12\x1a\n\x16FORMATTER_ADDRESS_NAME\x10\0\x12\x14\n\x10FORMA\
+ TTER_AMOUNT\x10\x01\x12\x1a\n\x16FORMATTER_TOKEN_AMOUNT\x10\x02\x12\x12\
+ \n\x0eFORMATTER_UNIT\x10\x03\x12\x11\n\rFORMATTER_RAW\x10\x04\x12\x12\n\
+ \x0eFORMATTER_DATE\x10\x05*;\n\x1cEthereumERC7730ContainerPath\x12\x08\n\
+ \x04FROM\x10\x01\x12\t\n\x05VALUE\x10\x02\x12\x06\n\x02TO\x10\x03B?\n#co\
+ m.satoshilabs.trezor.lib.protobufB\x18TrezorMessageDefinitions\
";
/// `FileDescriptorProto` object which was a source for this generated file
Why this scored 17/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.