mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-28 09:28:13 +00:00
feat(core): support displaying EIP-712 message hash during signing
This commit is contained in:
parent
11bad835f1
commit
70ccc36ae4
@ -26,6 +26,7 @@ message EthereumSignTypedData {
|
||||
required string primary_type = 2; // name of the root message struct
|
||||
optional bool metamask_v4_compat = 3 [default=true]; // use MetaMask v4 (see https://github.com/MetaMask/eth-sig-util/issues/106)
|
||||
optional ethereum.EthereumDefinitions definitions = 4; // network and/or token definitions
|
||||
optional bytes show_message_hash = 5; // hash of the typed data to be signed (if set, user will be asked to confirm before signing)
|
||||
}
|
||||
|
||||
/**
|
||||
|
1
core/.changelog.d/5344.added
Normal file
1
core/.changelog.d/5344.added
Normal file
@ -0,0 +1 @@
|
||||
Add support for displaying the message hash when signing Ethereum EIP-712 typed data.
|
@ -1105,6 +1105,7 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_ethereum__title_all_input_data_template;
|
||||
MP_QSTR_ethereum__title_confirm_domain;
|
||||
MP_QSTR_ethereum__title_confirm_message;
|
||||
MP_QSTR_ethereum__title_confirm_message_hash;
|
||||
MP_QSTR_ethereum__title_confirm_struct;
|
||||
MP_QSTR_ethereum__title_confirm_typed_data;
|
||||
MP_QSTR_ethereum__title_input_data;
|
||||
|
@ -1450,6 +1450,8 @@ pub enum TranslatedString {
|
||||
address__title_provider_address = 1053, // "Provider address"
|
||||
address__title_refund_address = 1054, // "Refund address"
|
||||
words__assets = 1055, // "Assets"
|
||||
#[cfg(feature = "universal_fw")]
|
||||
ethereum__title_confirm_message_hash = 1056, // "Confirm message hash"
|
||||
}
|
||||
|
||||
impl TranslatedString {
|
||||
@ -3212,6 +3214,8 @@ impl TranslatedString {
|
||||
(Self::address__title_provider_address, "Provider address"),
|
||||
(Self::address__title_refund_address, "Refund address"),
|
||||
(Self::words__assets, "Assets"),
|
||||
#[cfg(feature = "universal_fw")]
|
||||
(Self::ethereum__title_confirm_message_hash, "Confirm message hash"),
|
||||
];
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
@ -3738,6 +3742,8 @@ impl TranslatedString {
|
||||
#[cfg(feature = "universal_fw")]
|
||||
(Qstr::MP_QSTR_ethereum__title_confirm_message, Self::ethereum__title_confirm_message),
|
||||
#[cfg(feature = "universal_fw")]
|
||||
(Qstr::MP_QSTR_ethereum__title_confirm_message_hash, Self::ethereum__title_confirm_message_hash),
|
||||
#[cfg(feature = "universal_fw")]
|
||||
(Qstr::MP_QSTR_ethereum__title_confirm_struct, Self::ethereum__title_confirm_struct),
|
||||
#[cfg(feature = "universal_fw")]
|
||||
(Qstr::MP_QSTR_ethereum__title_confirm_typed_data, Self::ethereum__title_confirm_typed_data),
|
||||
|
@ -330,6 +330,7 @@ class TR:
|
||||
ethereum__title_all_input_data_template: str = "All input data ({0} bytes)"
|
||||
ethereum__title_confirm_domain: str = "Confirm domain"
|
||||
ethereum__title_confirm_message: str = "Confirm message"
|
||||
ethereum__title_confirm_message_hash: str = "Confirm message hash"
|
||||
ethereum__title_confirm_struct: str = "Confirm struct"
|
||||
ethereum__title_confirm_typed_data: str = "Confirm typed data"
|
||||
ethereum__title_input_data: str = "Input data"
|
||||
|
@ -305,6 +305,24 @@ def require_confirm_other_data(data: bytes, data_total: int) -> Awaitable[None]:
|
||||
)
|
||||
|
||||
|
||||
async def confirm_message_hash(message_hash: bytes) -> None:
|
||||
from ubinascii import hexlify
|
||||
|
||||
from trezor.ui.layouts import confirm_value
|
||||
|
||||
message_hash_hex = "0x" + hexlify(message_hash).decode()
|
||||
|
||||
await confirm_value(
|
||||
TR.ethereum__title_confirm_message_hash,
|
||||
message_hash_hex,
|
||||
"",
|
||||
"confirm_message_hash",
|
||||
verb=TR.buttons__confirm,
|
||||
br_code=ButtonRequestType.SignTx,
|
||||
cancel=True,
|
||||
)
|
||||
|
||||
|
||||
async def confirm_typed_data_final() -> None:
|
||||
from trezor.ui.layouts import confirm_action
|
||||
|
||||
|
@ -45,7 +45,7 @@ async def sign_typed_data(
|
||||
await require_confirm_address(address_bytes)
|
||||
|
||||
data_hash = await _generate_typed_data_hash(
|
||||
msg.primary_type, msg.metamask_v4_compat
|
||||
msg.primary_type, msg.metamask_v4_compat, msg.show_message_hash
|
||||
)
|
||||
|
||||
signature = secp256k1.sign(
|
||||
@ -59,7 +59,9 @@ async def sign_typed_data(
|
||||
|
||||
|
||||
async def _generate_typed_data_hash(
|
||||
primary_type: str, metamask_v4_compat: bool = True
|
||||
primary_type: str,
|
||||
metamask_v4_compat: bool = True,
|
||||
show_message_hash: bytes | None = None,
|
||||
) -> bytes:
|
||||
"""
|
||||
Generate typed data hash according to EIP-712 specification
|
||||
@ -71,6 +73,7 @@ async def _generate_typed_data_hash(
|
||||
|
||||
from .layout import (
|
||||
confirm_empty_typed_message,
|
||||
confirm_message_hash,
|
||||
confirm_typed_data_final,
|
||||
should_show_domain,
|
||||
)
|
||||
@ -110,6 +113,11 @@ async def _generate_typed_data_hash(
|
||||
[primary_type],
|
||||
)
|
||||
|
||||
if show_message_hash is not None:
|
||||
if message_hash != show_message_hash:
|
||||
raise DataError("Message hash mismatch")
|
||||
await confirm_message_hash(message_hash)
|
||||
|
||||
await confirm_typed_data_final()
|
||||
|
||||
return keccak256(b"\x19\x01" + domain_separator + message_hash)
|
||||
|
2
core/src/trezor/messages.py
generated
2
core/src/trezor/messages.py
generated
@ -3877,6 +3877,7 @@ if TYPE_CHECKING:
|
||||
primary_type: "str"
|
||||
metamask_v4_compat: "bool"
|
||||
definitions: "EthereumDefinitions | None"
|
||||
show_message_hash: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -3885,6 +3886,7 @@ if TYPE_CHECKING:
|
||||
address_n: "list[int] | None" = None,
|
||||
metamask_v4_compat: "bool | None" = None,
|
||||
definitions: "EthereumDefinitions | None" = None,
|
||||
show_message_hash: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
@ -371,6 +371,7 @@
|
||||
"ethereum__title_all_input_data_template": "All input data ({0} bytes)",
|
||||
"ethereum__title_confirm_domain": "Confirm domain",
|
||||
"ethereum__title_confirm_message": "Confirm message",
|
||||
"ethereum__title_confirm_message_hash": "Confirm message hash",
|
||||
"ethereum__title_confirm_struct": "Confirm struct",
|
||||
"ethereum__title_confirm_typed_data": "Confirm typed data",
|
||||
"ethereum__title_input_data": "Input data",
|
||||
|
@ -1054,5 +1054,6 @@
|
||||
"1052": "words__swap",
|
||||
"1053": "address__title_provider_address",
|
||||
"1054": "address__title_refund_address",
|
||||
"1055": "words__assets"
|
||||
"1055": "words__assets",
|
||||
"1056": "ethereum__title_confirm_message_hash"
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"current": {
|
||||
"merkle_root": "e2482ef9c1fe4243754ccd9898b3c05ab854d59bf5b9afae67630262bdc47d4e",
|
||||
"datetime": "2025-07-14T09:23:54.196279+00:00",
|
||||
"commit": "265104d00d8ddbc47753055021ae67fffa67423f"
|
||||
"merkle_root": "aa1b72313fa6ff028138ae1e6f26362107ffa41f4d745aa4170036a15b6d7a6f",
|
||||
"datetime": "2025-07-17T02:01:07.922808+00:00",
|
||||
"commit": "6cc9c629414e9cf8bc66b22580555c098e0fb3eb"
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
|
3
python/src/trezorlib/messages.py
generated
3
python/src/trezorlib/messages.py
generated
@ -5293,6 +5293,7 @@ class EthereumSignTypedData(protobuf.MessageType):
|
||||
2: protobuf.Field("primary_type", "string", repeated=False, required=True),
|
||||
3: protobuf.Field("metamask_v4_compat", "bool", repeated=False, required=False, default=True),
|
||||
4: protobuf.Field("definitions", "EthereumDefinitions", repeated=False, required=False, default=None),
|
||||
5: protobuf.Field("show_message_hash", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
@ -5302,11 +5303,13 @@ class EthereumSignTypedData(protobuf.MessageType):
|
||||
address_n: Optional[Sequence["int"]] = None,
|
||||
metamask_v4_compat: Optional["bool"] = True,
|
||||
definitions: Optional["EthereumDefinitions"] = None,
|
||||
show_message_hash: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.address_n: Sequence["int"] = address_n if address_n is not None else []
|
||||
self.primary_type = primary_type
|
||||
self.metamask_v4_compat = metamask_v4_compat
|
||||
self.definitions = definitions
|
||||
self.show_message_hash = show_message_hash
|
||||
|
||||
|
||||
class EthereumTypedDataStructRequest(protobuf.MessageType):
|
||||
|
@ -36,6 +36,8 @@ pub struct EthereumSignTypedData {
|
||||
pub metamask_v4_compat: ::std::option::Option<bool>,
|
||||
// @@protoc_insertion_point(field:hw.trezor.messages.ethereum_eip712.EthereumSignTypedData.definitions)
|
||||
pub definitions: ::protobuf::MessageField<super::messages_ethereum::EthereumDefinitions>,
|
||||
// @@protoc_insertion_point(field:hw.trezor.messages.ethereum_eip712.EthereumSignTypedData.show_message_hash)
|
||||
pub show_message_hash: ::std::option::Option<::std::vec::Vec<u8>>,
|
||||
// special fields
|
||||
// @@protoc_insertion_point(special_field:hw.trezor.messages.ethereum_eip712.EthereumSignTypedData.special_fields)
|
||||
pub special_fields: ::protobuf::SpecialFields,
|
||||
@ -107,8 +109,44 @@ impl EthereumSignTypedData {
|
||||
self.metamask_v4_compat = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
// optional bytes show_message_hash = 5;
|
||||
|
||||
pub fn show_message_hash(&self) -> &[u8] {
|
||||
match self.show_message_hash.as_ref() {
|
||||
Some(v) => v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_show_message_hash(&mut self) {
|
||||
self.show_message_hash = ::std::option::Option::None;
|
||||
}
|
||||
|
||||
pub fn has_show_message_hash(&self) -> bool {
|
||||
self.show_message_hash.is_some()
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_show_message_hash(&mut self, v: ::std::vec::Vec<u8>) {
|
||||
self.show_message_hash = ::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_show_message_hash(&mut self) -> &mut ::std::vec::Vec<u8> {
|
||||
if self.show_message_hash.is_none() {
|
||||
self.show_message_hash = ::std::option::Option::Some(::std::vec::Vec::new());
|
||||
}
|
||||
self.show_message_hash.as_mut().unwrap()
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_show_message_hash(&mut self) -> ::std::vec::Vec<u8> {
|
||||
self.show_message_hash.take().unwrap_or_else(|| ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
|
||||
let mut fields = ::std::vec::Vec::with_capacity(4);
|
||||
let mut fields = ::std::vec::Vec::with_capacity(5);
|
||||
let mut oneofs = ::std::vec::Vec::with_capacity(0);
|
||||
fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>(
|
||||
"address_n",
|
||||
@ -130,6 +168,11 @@ impl EthereumSignTypedData {
|
||||
|m: &EthereumSignTypedData| { &m.definitions },
|
||||
|m: &mut EthereumSignTypedData| { &mut m.definitions },
|
||||
));
|
||||
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
|
||||
"show_message_hash",
|
||||
|m: &EthereumSignTypedData| { &m.show_message_hash },
|
||||
|m: &mut EthereumSignTypedData| { &mut m.show_message_hash },
|
||||
));
|
||||
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<EthereumSignTypedData>(
|
||||
"EthereumSignTypedData",
|
||||
fields,
|
||||
@ -171,6 +214,9 @@ impl ::protobuf::Message for EthereumSignTypedData {
|
||||
34 => {
|
||||
::protobuf::rt::read_singular_message_into_field(is, &mut self.definitions)?;
|
||||
},
|
||||
42 => {
|
||||
self.show_message_hash = ::std::option::Option::Some(is.read_bytes()?);
|
||||
},
|
||||
tag => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
|
||||
},
|
||||
@ -196,6 +242,9 @@ impl ::protobuf::Message for EthereumSignTypedData {
|
||||
let len = v.compute_size();
|
||||
my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len;
|
||||
}
|
||||
if let Some(v) = self.show_message_hash.as_ref() {
|
||||
my_size += ::protobuf::rt::bytes_size(5, &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
|
||||
@ -214,6 +263,9 @@ impl ::protobuf::Message for EthereumSignTypedData {
|
||||
if let Some(v) = self.definitions.as_ref() {
|
||||
::protobuf::rt::write_message_field_with_cached_size(4, v, os)?;
|
||||
}
|
||||
if let Some(v) = self.show_message_hash.as_ref() {
|
||||
os.write_bytes(5, v)?;
|
||||
}
|
||||
os.write_unknown_fields(self.special_fields.unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
@ -235,6 +287,7 @@ impl ::protobuf::Message for EthereumSignTypedData {
|
||||
self.primary_type = ::std::option::Option::None;
|
||||
self.metamask_v4_compat = ::std::option::Option::None;
|
||||
self.definitions.clear();
|
||||
self.show_message_hash = ::std::option::Option::None;
|
||||
self.special_fields.clear();
|
||||
}
|
||||
|
||||
@ -244,6 +297,7 @@ impl ::protobuf::Message for EthereumSignTypedData {
|
||||
primary_type: ::std::option::Option::None,
|
||||
metamask_v4_compat: ::std::option::Option::None,
|
||||
definitions: ::protobuf::MessageField::none(),
|
||||
show_message_hash: ::std::option::Option::None,
|
||||
special_fields: ::protobuf::SpecialFields::new(),
|
||||
};
|
||||
&instance
|
||||
@ -1399,12 +1453,13 @@ impl ::protobuf::reflect::ProtobufValue for EthereumTypedDataValueAck {
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x1emessages-ethereum-eip712.proto\x12\"hw.trezor.messages.ethereum_ei\
|
||||
p712\x1a\x17messages-ethereum.proto\"\xdf\x01\n\x15EthereumSignTypedData\
|
||||
p712\x1a\x17messages-ethereum.proto\"\x8b\x02\n\x15EthereumSignTypedData\
|
||||
\x12\x1b\n\taddress_n\x18\x01\x20\x03(\rR\x08addressN\x12!\n\x0cprimary_\
|
||||
type\x18\x02\x20\x02(\tR\x0bprimaryType\x122\n\x12metamask_v4_compat\x18\
|
||||
\x03\x20\x01(\x08:\x04trueR\x10metamaskV4Compat\x12R\n\x0bdefinitions\
|
||||
\x18\x04\x20\x01(\x0b20.hw.trezor.messages.ethereum.EthereumDefinitionsR\
|
||||
\x0bdefinitions\"4\n\x1eEthereumTypedDataStructRequest\x12\x12\n\x04name\
|
||||
\x0bdefinitions\x12*\n\x11show_message_hash\x18\x05\x20\x01(\x0cR\x0fsho\
|
||||
wMessageHash\"4\n\x1eEthereumTypedDataStructRequest\x12\x12\n\x04name\
|
||||
\x18\x01\x20\x02(\tR\x04name\"\xb4\x05\n\x1aEthereumTypedDataStructAck\
|
||||
\x12m\n\x07members\x18\x01\x20\x03(\x0b2S.hw.trezor.messages.ethereum_ei\
|
||||
p712.EthereumTypedDataStructAck.EthereumStructMemberR\x07members\x1a\x90\
|
||||
|
Loading…
Reference in New Issue
Block a user