diff --git a/common/protob/messages-ethereum-eip712.proto b/common/protob/messages-ethereum-eip712.proto index 48b2fb08aa..7fc710257c 100644 --- a/common/protob/messages-ethereum-eip712.proto +++ b/common/protob/messages-ethereum-eip712.proto @@ -23,6 +23,7 @@ message EthereumSignTypedData { repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node 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 bytes encoded_network = 4; // encoded Ethereum network, see protocol.md for details } /** diff --git a/common/protob/messages-ethereum.proto b/common/protob/messages-ethereum.proto index 8ac7d768e1..e250ce026c 100644 --- a/common/protob/messages-ethereum.proto +++ b/common/protob/messages-ethereum.proto @@ -15,8 +15,9 @@ import "messages-common.proto"; * @next Failure */ message EthereumGetPublicKey { - repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node - optional bool show_display = 2; // optionally show on display before sending the result + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bool show_display = 2; // optionally show on display before sending the result + optional bytes encoded_network = 3; // encoded Ethereum network, see protocol.md for details } /** @@ -35,8 +36,9 @@ message EthereumPublicKey { * @next Failure */ message EthereumGetAddress { - repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node - optional bool show_display = 2; // optionally show on display before sending the result + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bool show_display = 2; // optionally show on display before sending the result + optional bytes encoded_network = 3; // encoded Ethereum network, see protocol.md for details } /** @@ -48,6 +50,48 @@ message EthereumAddress { optional string address = 2; // Ethereum address as hex-encoded string } +/** + * Ethereum definitions type enum. + * Used to check the encoded EthereumNetworkInfo and/or EthereumTokenInfo message. + */ +enum EthereumDefinitionType { + NETWORK = 0; + TOKEN = 1; +} + +/** + * Ethereum network definition. Used to (de)serialize the definition. + * @embed + */ +message EthereumNetworkInfo { + required uint64 chain_id = 1; + required uint64 slip44 = 2; + required string shortcut = 3; + required string name = 4; + required bool rskip60 = 5; +} + +/** + * Ethereum token definition. Used to (de)serialize the definition. + * @embed + */ +message EthereumTokenInfo { + required string symbol = 1; + required uint32 decimals = 2; + required bytes address = 3; + required uint64 chain_id = 4; + required string name = 5; +} + +/** + * Contains an encoded Ethereum network and/or token definition. See protocol.md for details. + * @embed + */ +message EthereumEncodedDefinitions { + optional bytes encoded_network = 1; // encoded Ethereum network + optional bytes encoded_token = 2; // encoded Ethereum token +} + /** * Request: Ask device to sign transaction * gas_price, gas_limit and chain_id must be provided and non-zero. @@ -58,16 +102,17 @@ message EthereumAddress { * @next Failure */ message EthereumSignTx { - repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node - optional bytes nonce = 2 [default='']; // <=256 bit unsigned big endian - required bytes gas_price = 3; // <=256 bit unsigned big endian (in wei) - required bytes gas_limit = 4; // <=256 bit unsigned big endian - optional string to = 11 [default='']; // recipient address - optional bytes value = 6 [default='']; // <=256 bit unsigned big endian (in wei) - optional bytes data_initial_chunk = 7 [default='']; // The initial data chunk (<= 1024 bytes) - optional uint32 data_length = 8 [default=0]; // Length of transaction payload - required uint64 chain_id = 9; // Chain Id for EIP 155 - optional uint32 tx_type = 10; // Used for Wanchain + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bytes nonce = 2 [default='']; // <=256 bit unsigned big endian + required bytes gas_price = 3; // <=256 bit unsigned big endian (in wei) + required bytes gas_limit = 4; // <=256 bit unsigned big endian + optional string to = 11 [default='']; // recipient address + optional bytes value = 6 [default='']; // <=256 bit unsigned big endian (in wei) + optional bytes data_initial_chunk = 7 [default='']; // The initial data chunk (<= 1024 bytes) + optional uint32 data_length = 8 [default=0]; // Length of transaction payload + required uint64 chain_id = 9; // Chain Id for EIP 155 + optional uint32 tx_type = 10; // Used for Wanchain + optional EthereumEncodedDefinitions definitions = 12; // network and/or token definitions for tx } /** @@ -78,17 +123,18 @@ message EthereumSignTx { * @next Failure */ message EthereumSignTxEIP1559 { - repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node - required bytes nonce = 2; // <=256 bit unsigned big endian - required bytes max_gas_fee = 3; // <=256 bit unsigned big endian (in wei) - required bytes max_priority_fee = 4; // <=256 bit unsigned big endian (in wei) - required bytes gas_limit = 5; // <=256 bit unsigned big endian - optional string to = 6 [default='']; // recipient address - required bytes value = 7; // <=256 bit unsigned big endian (in wei) - optional bytes data_initial_chunk = 8 [default='']; // The initial data chunk (<= 1024 bytes) - required uint32 data_length = 9; // Length of transaction payload - required uint64 chain_id = 10; // Chain Id for EIP 155 - repeated EthereumAccessList access_list = 11; // Access List + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes nonce = 2; // <=256 bit unsigned big endian + required bytes max_gas_fee = 3; // <=256 bit unsigned big endian (in wei) + required bytes max_priority_fee = 4; // <=256 bit unsigned big endian (in wei) + required bytes gas_limit = 5; // <=256 bit unsigned big endian + optional string to = 6 [default='']; // recipient address + required bytes value = 7; // <=256 bit unsigned big endian (in wei) + optional bytes data_initial_chunk = 8 [default='']; // The initial data chunk (<= 1024 bytes) + required uint32 data_length = 9; // Length of transaction payload + required uint64 chain_id = 10; // Chain Id for EIP 155 + repeated EthereumAccessList access_list = 11; // Access List + optional EthereumEncodedDefinitions definitions = 12; // network and/or token definitions for tx message EthereumAccessList { required string address = 1; @@ -125,8 +171,10 @@ message EthereumTxAck { * @next Failure */ message EthereumSignMessage { - repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node - required bytes message = 2; // message to be signed + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + required bytes message = 2; // message to be signed + // TODO: send also token to be able to verify that provided address belongs to this network?? + optional bytes encoded_network = 3; // encoded Ethereum network, see protocol.md for details } /** @@ -145,9 +193,11 @@ message EthereumMessageSignature { * @next Failure */ message EthereumVerifyMessage { - required bytes signature = 2; // signature to verify - required bytes message = 3; // message to verify - required string address = 4; // address to verify + required bytes signature = 2; // signature to verify + required bytes message = 3; // message to verify + required string address = 4; // address to verify + // TODO: send also token to be able to verify that provided address belongs to this network?? + optional bytes encoded_network = 5; // encoded Ethereum network, see protocol.md for details } /** diff --git a/common/protob/protocol.md b/common/protob/protocol.md index 82fff6c94b..45ecd8961c 100644 --- a/common/protob/protocol.md +++ b/common/protob/protocol.md @@ -20,6 +20,18 @@ Following packets has the following structure: | 0 | 1 | char[1] | '?' magic constant | | 1 | 63 | uint8_t[63] | following bytes of message encoded in Protocol Buffers (padded with zeroes if shorter) | +## Ethereum network and token definitions +Ethereum network and token definitions could be sent from host to device. Definitions are generated in our CI job and are provided +on publicly accessible website - on `data.trezor.io`. To ensure, that the definitions send to device are genuine, every generated +definition is signed and checked in FW. +Every definition (network/token) is encoded and has special format: +1. prefix: + 1. part: format version of the definition (UTF-8 string `trzd` + version number, padded with zeroes if shorter, 8 bytes) + 2. part: type of data (unsigned integer, 1 byte) + 3. part: data version of the definition (unsigned integer, 4 bytes) +2. payload: serialized form of protobuf message EthereumNetworkInfo and/or EthereumTokenInfo (N bytes) +3. suffix: signature of prefix+payload (plain bits, 64 bytes) + ## Adding new message To add new message to Trezor protocol follow these steps: diff --git a/core/src/all_modules.py b/core/src/all_modules.py index c40e665b27..50a7aa3b7f 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -383,6 +383,8 @@ if not utils.BITCOIN_ONLY: import trezor.enums.CardanoTxWitnessType trezor.enums.EthereumDataType import trezor.enums.EthereumDataType + trezor.enums.EthereumDefinitionType + import trezor.enums.EthereumDefinitionType trezor.enums.MoneroNetworkType import trezor.enums.MoneroNetworkType trezor.enums.NEMImportanceTransferMode diff --git a/core/src/trezor/enums/EthereumDefinitionType.py b/core/src/trezor/enums/EthereumDefinitionType.py new file mode 100644 index 0000000000..3876e08720 --- /dev/null +++ b/core/src/trezor/enums/EthereumDefinitionType.py @@ -0,0 +1,6 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +NETWORK = 0 +TOKEN = 1 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 224a1374e0..d1f6370863 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -461,6 +461,10 @@ if TYPE_CHECKING: ARRAY = 7 STRUCT = 8 + class EthereumDefinitionType(IntEnum): + NETWORK = 0 + TOKEN = 1 + class MoneroNetworkType(IntEnum): MAINNET = 0 TESTNET = 1 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 8963ea4cb9..b0a701e54d 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -38,6 +38,7 @@ if TYPE_CHECKING: from trezor.enums import DebugSwipeDirection # noqa: F401 from trezor.enums import DecredStakingSpendType # noqa: F401 from trezor.enums import EthereumDataType # noqa: F401 + from trezor.enums import EthereumDefinitionType # noqa: F401 from trezor.enums import FailureType # noqa: F401 from trezor.enums import InputScriptType # noqa: F401 from trezor.enums import MessageType # noqa: F401 @@ -3367,6 +3368,7 @@ if TYPE_CHECKING: address_n: "list[int]" primary_type: "str" metamask_v4_compat: "bool" + encoded_network: "bytes | None" def __init__( self, @@ -3374,6 +3376,7 @@ if TYPE_CHECKING: primary_type: "str", address_n: "list[int] | None" = None, metamask_v4_compat: "bool | None" = None, + encoded_network: "bytes | None" = None, ) -> None: pass @@ -3476,12 +3479,14 @@ if TYPE_CHECKING: class EthereumGetPublicKey(protobuf.MessageType): address_n: "list[int]" show_display: "bool | None" + encoded_network: "bytes | None" def __init__( self, *, address_n: "list[int] | None" = None, show_display: "bool | None" = None, + encoded_network: "bytes | None" = None, ) -> None: pass @@ -3508,12 +3513,14 @@ if TYPE_CHECKING: class EthereumGetAddress(protobuf.MessageType): address_n: "list[int]" show_display: "bool | None" + encoded_network: "bytes | None" def __init__( self, *, address_n: "list[int] | None" = None, show_display: "bool | None" = None, + encoded_network: "bytes | None" = None, ) -> None: pass @@ -3535,6 +3542,66 @@ if TYPE_CHECKING: def is_type_of(cls, msg: Any) -> TypeGuard["EthereumAddress"]: return isinstance(msg, cls) + class EthereumNetworkInfo(protobuf.MessageType): + chain_id: "int" + slip44: "int" + shortcut: "str" + name: "str" + rskip60: "bool" + + def __init__( + self, + *, + chain_id: "int", + slip44: "int", + shortcut: "str", + name: "str", + rskip60: "bool", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumNetworkInfo"]: + return isinstance(msg, cls) + + class EthereumTokenInfo(protobuf.MessageType): + symbol: "str" + decimals: "int" + address: "bytes" + chain_id: "int" + name: "str" + + def __init__( + self, + *, + symbol: "str", + decimals: "int", + address: "bytes", + chain_id: "int", + name: "str", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumTokenInfo"]: + return isinstance(msg, cls) + + class EthereumEncodedDefinitions(protobuf.MessageType): + encoded_network: "bytes | None" + encoded_token: "bytes | None" + + def __init__( + self, + *, + encoded_network: "bytes | None" = None, + encoded_token: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumEncodedDefinitions"]: + return isinstance(msg, cls) + class EthereumSignTx(protobuf.MessageType): address_n: "list[int]" nonce: "bytes" @@ -3546,6 +3613,7 @@ if TYPE_CHECKING: data_length: "int" chain_id: "int" tx_type: "int | None" + definitions: "EthereumEncodedDefinitions | None" def __init__( self, @@ -3560,6 +3628,7 @@ if TYPE_CHECKING: data_initial_chunk: "bytes | None" = None, data_length: "int | None" = None, tx_type: "int | None" = None, + definitions: "EthereumEncodedDefinitions | None" = None, ) -> None: pass @@ -3579,6 +3648,7 @@ if TYPE_CHECKING: data_length: "int" chain_id: "int" access_list: "list[EthereumAccessList]" + definitions: "EthereumEncodedDefinitions | None" def __init__( self, @@ -3594,6 +3664,7 @@ if TYPE_CHECKING: access_list: "list[EthereumAccessList] | None" = None, to: "str | None" = None, data_initial_chunk: "bytes | None" = None, + definitions: "EthereumEncodedDefinitions | None" = None, ) -> None: pass @@ -3638,12 +3709,14 @@ if TYPE_CHECKING: class EthereumSignMessage(protobuf.MessageType): address_n: "list[int]" message: "bytes" + encoded_network: "bytes | None" def __init__( self, *, message: "bytes", address_n: "list[int] | None" = None, + encoded_network: "bytes | None" = None, ) -> None: pass @@ -3671,6 +3744,7 @@ if TYPE_CHECKING: signature: "bytes" message: "bytes" address: "str" + encoded_network: "bytes | None" def __init__( self, @@ -3678,6 +3752,7 @@ if TYPE_CHECKING: signature: "bytes", message: "bytes", address: "str", + encoded_network: "bytes | None" = None, ) -> None: pass diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index a060c6c308..dfc37319a0 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -498,6 +498,11 @@ class EthereumDataType(IntEnum): STRUCT = 8 +class EthereumDefinitionType(IntEnum): + NETWORK = 0 + TOKEN = 1 + + class MoneroNetworkType(IntEnum): MAINNET = 0 TESTNET = 1 @@ -4544,6 +4549,7 @@ class EthereumSignTypedData(protobuf.MessageType): 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 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("encoded_network", "bytes", repeated=False, required=False), } def __init__( @@ -4552,10 +4558,12 @@ class EthereumSignTypedData(protobuf.MessageType): primary_type: "str", address_n: Optional[Sequence["int"]] = None, metamask_v4_compat: Optional["bool"] = True, + encoded_network: 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.encoded_network = encoded_network class EthereumTypedDataStructRequest(protobuf.MessageType): @@ -4659,6 +4667,7 @@ class EthereumGetPublicKey(protobuf.MessageType): FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), + 3: protobuf.Field("encoded_network", "bytes", repeated=False, required=False), } def __init__( @@ -4666,9 +4675,11 @@ class EthereumGetPublicKey(protobuf.MessageType): *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, + encoded_network: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display + self.encoded_network = encoded_network class EthereumPublicKey(protobuf.MessageType): @@ -4693,6 +4704,7 @@ class EthereumGetAddress(protobuf.MessageType): FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("show_display", "bool", repeated=False, required=False, default=None), + 3: protobuf.Field("encoded_network", "bytes", repeated=False, required=False), } def __init__( @@ -4700,9 +4712,11 @@ class EthereumGetAddress(protobuf.MessageType): *, address_n: Optional[Sequence["int"]] = None, show_display: Optional["bool"] = None, + encoded_network: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.show_display = show_display + self.encoded_network = encoded_network class EthereumAddress(protobuf.MessageType): @@ -4722,6 +4736,75 @@ class EthereumAddress(protobuf.MessageType): self.address = address +class EthereumNetworkInfo(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("chain_id", "uint64", repeated=False, required=True), + 2: protobuf.Field("slip44", "uint64", repeated=False, required=True), + 3: protobuf.Field("shortcut", "string", repeated=False, required=True), + 4: protobuf.Field("name", "string", repeated=False, required=True), + 5: protobuf.Field("rskip60", "bool", repeated=False, required=True), + } + + def __init__( + self, + *, + chain_id: "int", + slip44: "int", + shortcut: "str", + name: "str", + rskip60: "bool", + ) -> None: + self.chain_id = chain_id + self.slip44 = slip44 + self.shortcut = shortcut + self.name = name + self.rskip60 = rskip60 + + +class EthereumTokenInfo(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("symbol", "string", repeated=False, required=True), + 2: protobuf.Field("decimals", "uint32", repeated=False, required=True), + 3: protobuf.Field("address", "bytes", repeated=False, required=True), + 4: protobuf.Field("chain_id", "uint64", repeated=False, required=True), + 5: protobuf.Field("name", "string", repeated=False, required=True), + } + + def __init__( + self, + *, + symbol: "str", + decimals: "int", + address: "bytes", + chain_id: "int", + name: "str", + ) -> None: + self.symbol = symbol + self.decimals = decimals + self.address = address + self.chain_id = chain_id + self.name = name + + +class EthereumEncodedDefinitions(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("encoded_network", "bytes", repeated=False, required=False), + 2: protobuf.Field("encoded_token", "bytes", repeated=False, required=False), + } + + def __init__( + self, + *, + encoded_network: Optional["bytes"] = None, + encoded_token: Optional["bytes"] = None, + ) -> None: + self.encoded_network = encoded_network + self.encoded_token = encoded_token + + class EthereumSignTx(protobuf.MessageType): MESSAGE_WIRE_TYPE = 58 FIELDS = { @@ -4735,6 +4818,7 @@ class EthereumSignTx(protobuf.MessageType): 8: protobuf.Field("data_length", "uint32", repeated=False, required=False, default=0), 9: protobuf.Field("chain_id", "uint64", repeated=False, required=True), 10: protobuf.Field("tx_type", "uint32", repeated=False, required=False, default=None), + 12: protobuf.Field("definitions", "EthereumEncodedDefinitions", repeated=False, required=False), } def __init__( @@ -4750,6 +4834,7 @@ class EthereumSignTx(protobuf.MessageType): data_initial_chunk: Optional["bytes"] = b'', data_length: Optional["int"] = 0, tx_type: Optional["int"] = None, + definitions: Optional["EthereumEncodedDefinitions"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.gas_price = gas_price @@ -4761,6 +4846,7 @@ class EthereumSignTx(protobuf.MessageType): self.data_initial_chunk = data_initial_chunk self.data_length = data_length self.tx_type = tx_type + self.definitions = definitions class EthereumSignTxEIP1559(protobuf.MessageType): @@ -4777,6 +4863,7 @@ class EthereumSignTxEIP1559(protobuf.MessageType): 9: protobuf.Field("data_length", "uint32", repeated=False, required=True), 10: protobuf.Field("chain_id", "uint64", repeated=False, required=True), 11: protobuf.Field("access_list", "EthereumAccessList", repeated=True, required=False, default=None), + 12: protobuf.Field("definitions", "EthereumEncodedDefinitions", repeated=False, required=False), } def __init__( @@ -4793,6 +4880,7 @@ class EthereumSignTxEIP1559(protobuf.MessageType): access_list: Optional[Sequence["EthereumAccessList"]] = None, to: Optional["str"] = '', data_initial_chunk: Optional["bytes"] = b'', + definitions: Optional["EthereumEncodedDefinitions"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.access_list: Sequence["EthereumAccessList"] = access_list if access_list is not None else [] @@ -4805,6 +4893,7 @@ class EthereumSignTxEIP1559(protobuf.MessageType): self.chain_id = chain_id self.to = to self.data_initial_chunk = data_initial_chunk + self.definitions = definitions class EthereumTxRequest(protobuf.MessageType): @@ -4849,6 +4938,7 @@ class EthereumSignMessage(protobuf.MessageType): FIELDS = { 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), 2: protobuf.Field("message", "bytes", repeated=False, required=True), + 3: protobuf.Field("encoded_network", "bytes", repeated=False, required=False), } def __init__( @@ -4856,9 +4946,11 @@ class EthereumSignMessage(protobuf.MessageType): *, message: "bytes", address_n: Optional[Sequence["int"]] = None, + encoded_network: Optional["bytes"] = None, ) -> None: self.address_n: Sequence["int"] = address_n if address_n is not None else [] self.message = message + self.encoded_network = encoded_network class EthereumMessageSignature(protobuf.MessageType): @@ -4884,6 +4976,7 @@ class EthereumVerifyMessage(protobuf.MessageType): 2: protobuf.Field("signature", "bytes", repeated=False, required=True), 3: protobuf.Field("message", "bytes", repeated=False, required=True), 4: protobuf.Field("address", "string", repeated=False, required=True), + 5: protobuf.Field("encoded_network", "bytes", repeated=False, required=False), } def __init__( @@ -4892,10 +4985,12 @@ class EthereumVerifyMessage(protobuf.MessageType): signature: "bytes", message: "bytes", address: "str", + encoded_network: Optional["bytes"] = None, ) -> None: self.signature = signature self.message = message self.address = address + self.encoded_network = encoded_network class EthereumSignTypedHash(protobuf.MessageType):