From ccd79ca324b8297624369204e441ad5ddf8b2078 Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Tue, 15 Jan 2019 14:10:46 +0100 Subject: [PATCH] eth: get public key --- src/apps/ethereum/__init__.py | 6 +++++- src/apps/ethereum/address.py | 27 ++++++++++++++++++++--- src/apps/ethereum/get_public_key.py | 32 ++++++++++++++++++++++++++++ src/apps/ethereum/networks.py | 7 ++++++ src/apps/ethereum/networks.py.mako | 7 ++++++ src/apps/wallet/sign_tx/addresses.py | 1 - tests/test_apps.ethereum.address.py | 5 ++++- 7 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/apps/ethereum/get_public_key.py diff --git a/src/apps/ethereum/__init__.py b/src/apps/ethereum/__init__.py index 04b07a773d..8c9be34c77 100644 --- a/src/apps/ethereum/__init__.py +++ b/src/apps/ethereum/__init__.py @@ -2,11 +2,15 @@ from trezor import wire from trezor.messages import MessageType from apps.common import HARDENED +from apps.ethereum.networks import all_slip44_ids_hardened def boot(): - ns = [["secp256k1", HARDENED | 44, HARDENED | 60]] + ns = [] + for i in all_slip44_ids_hardened(): + ns.append(["secp256k1", HARDENED | 44, i]) wire.add(MessageType.EthereumGetAddress, __name__, "get_address", ns) + wire.add(MessageType.EthereumGetPublicKey, __name__, "get_public_key", ns) wire.add(MessageType.EthereumSignTx, __name__, "sign_tx", ns) wire.add(MessageType.EthereumSignMessage, __name__, "sign_message", ns) wire.add(MessageType.EthereumVerifyMessage, __name__, "verify_message") diff --git a/src/apps/ethereum/address.py b/src/apps/ethereum/address.py index 4a1043fc0b..d853256c0d 100644 --- a/src/apps/ethereum/address.py +++ b/src/apps/ethereum/address.py @@ -1,8 +1,9 @@ -from apps.common import HARDENED +from apps.common import HARDENED, paths +from apps.ethereum import networks """ -We believe Ethereum should use 44'/60'/a' for everything,because it is +We believe Ethereum should use 44'/60'/a' for everything, because it is account-based, rather than UTXO-based. Unfortunately, lot of Ethereum tools (MEW, Metamask) do not use such scheme and set a = 0 and then iterate the address index i. Therefore for compatibility reasons we use @@ -10,6 +11,26 @@ the same scheme: 44'/60'/0'/0/i and only the i is being iterated. """ +def validate_path_for_get_public_key(path: list) -> bool: + """ + This should be 44'/60'/0', but other non-hardened items are allowed. + """ + length = len(path) + if length < 3 or length > 5: + return False + if path[0] != 44 | HARDENED: + return False + if path[1] not in networks.all_slip44_ids_hardened(): + return False + if path[2] != 0 | HARDENED: + return False + if length > 3 and paths.is_hardened(path[3]): + return False + if length > 4 and paths.is_hardened(path[4]): + return False + return True + + def validate_full_path(path: list) -> bool: """ Validates derivation path to equal 44'/60'/0'/0/i, @@ -19,7 +40,7 @@ def validate_full_path(path: list) -> bool: return False if path[0] != 44 | HARDENED: return False - if path[1] != 60 | HARDENED: + if path[1] not in networks.all_slip44_ids_hardened(): return False if path[2] != 0 | HARDENED: return False diff --git a/src/apps/ethereum/get_public_key.py b/src/apps/ethereum/get_public_key.py new file mode 100644 index 0000000000..b16a8e40af --- /dev/null +++ b/src/apps/ethereum/get_public_key.py @@ -0,0 +1,32 @@ +from trezor.messages.EthereumPublicKey import EthereumPublicKey +from trezor.messages.HDNodeType import HDNodeType + +from apps.common import coins, layout, paths +from apps.ethereum import address + + +async def get_public_key(ctx, msg, keychain): + await paths.validate_path( + ctx, address.validate_path_for_get_public_key, path=msg.address_n + ) + node = keychain.derive(msg.address_n) + + # we use the Bitcoin format for Ethereum xpubs + btc = coins.by_name("Bitcoin") + node_xpub = node.serialize_public(btc.xpub_magic) + + pubkey = node.public_key() + if pubkey[0] == 1: + pubkey = b"\x00" + pubkey[1:] + node_type = HDNodeType( + depth=node.depth(), + child_num=node.child_num(), + fingerprint=node.fingerprint(), + chain_code=node.chain_code(), + public_key=pubkey, + ) + + if msg.show_display: + await layout.show_pubkey(ctx, pubkey) + + return EthereumPublicKey(node=node_type, xpub=node_xpub) diff --git a/src/apps/ethereum/networks.py b/src/apps/ethereum/networks.py index 2af9c7dc28..cd74200f18 100644 --- a/src/apps/ethereum/networks.py +++ b/src/apps/ethereum/networks.py @@ -1,6 +1,8 @@ # generated from networks.py.mako # do not edit manually! +from apps.common import HARDENED + def shortcut_by_chain_id(chain_id, tx_type=None): if tx_type in [1, 6] and chain_id in [1, 3]: @@ -24,6 +26,11 @@ def by_slip44(slip44): return None +def all_slip44_ids_hardened(): + for n in NETWORKS: + yield n.slip44 | HARDENED + + class NetworkInfo: def __init__( self, chain_id: int, slip44: int, shortcut: str, name: str, rskip60: bool diff --git a/src/apps/ethereum/networks.py.mako b/src/apps/ethereum/networks.py.mako index 2566ae88c4..fa0312eaf7 100644 --- a/src/apps/ethereum/networks.py.mako +++ b/src/apps/ethereum/networks.py.mako @@ -1,6 +1,8 @@ # generated from networks.py.mako # do not edit manually! +from apps.common import HARDENED + def shortcut_by_chain_id(chain_id, tx_type=None): if tx_type in [1, 6] and chain_id in [1, 3]: @@ -24,6 +26,11 @@ def by_slip44(slip44): return None +def all_slip44_ids_hardened(): + for n in NETWORKS: + yield n.slip44 | HARDENED + + class NetworkInfo: def __init__( self, chain_id: int, slip44: int, shortcut: str, name: str, rskip60: bool diff --git a/src/apps/wallet/sign_tx/addresses.py b/src/apps/wallet/sign_tx/addresses.py index 6a7db4b8e2..ff6e93c3ac 100644 --- a/src/apps/wallet/sign_tx/addresses.py +++ b/src/apps/wallet/sign_tx/addresses.py @@ -261,7 +261,6 @@ def validate_purpose_against_script_type( def validate_path_for_bitcoin_public_key(path: list, coin: CoinInfo) -> bool: """ Validates derivation path to fit Bitcoin-like coins for GetPublicKey. - Script type is omitted here because it is not usually sent. """ length = len(path) if length < 3 or length > 5: diff --git a/tests/test_apps.ethereum.address.py b/tests/test_apps.ethereum.address.py index a59d4c7771..8b9d3ad51b 100644 --- a/tests/test_apps.ethereum.address.py +++ b/tests/test_apps.ethereum.address.py @@ -1,7 +1,7 @@ from common import * from apps.common.paths import HARDENED from apps.ethereum.address import ethereum_address_hex, validate_full_path -from apps.ethereum.networks import NetworkInfo +from apps.ethereum.networks import NetworkInfo, by_chain_id class TestEthereumGetAddress(unittest.TestCase): @@ -62,11 +62,14 @@ class TestEthereumGetAddress(unittest.TestCase): [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 1, 0], [44 | HARDENED, 60 | HARDENED, 1 | HARDENED, 0, 0], [44 | HARDENED, 160 | HARDENED, 0 | HARDENED, 0, 0], + [44 | HARDENED, 199 | HARDENED, 0 | HARDENED, 0, 9999], # slip44 not one of ETH chains ] correct_paths = [ [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 0, 0], [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 0, 9], [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 0, 9999], + [44 | HARDENED, 6060 | HARDENED, 0 | HARDENED, 0, 0], + [44 | HARDENED, 1 | HARDENED, 0 | HARDENED, 0, 0], ] for path in incorrect_paths: