From c0879f862535ba2d7f5def5c2dcbf35defdb9f8c Mon Sep 17 00:00:00 2001 From: matejcik Date: Fri, 14 Aug 2020 15:40:00 +0200 Subject: [PATCH] feat(core/cardano): use path schemas --- core/src/apps/cardano/address.py | 50 ++----------------- core/src/apps/cardano/get_address.py | 11 ++-- core/src/apps/cardano/get_public_key.py | 33 ++---------- core/src/apps/cardano/helpers/paths.py | 14 ++++++ core/src/apps/cardano/helpers/purposes.py | 4 -- .../apps/cardano/helpers/seed_namespaces.py | 6 --- .../apps/cardano/helpers/staking_use_cases.py | 5 +- core/src/apps/cardano/seed.py | 19 ++++--- core/src/apps/cardano/sign_tx.py | 13 ++--- 9 files changed, 52 insertions(+), 103 deletions(-) create mode 100644 core/src/apps/cardano/helpers/paths.py delete mode 100644 core/src/apps/cardano/helpers/purposes.py delete mode 100644 core/src/apps/cardano/helpers/seed_namespaces.py diff --git a/core/src/apps/cardano/address.py b/core/src/apps/cardano/address.py index b67418d16..df601f4de 100644 --- a/core/src/apps/cardano/address.py +++ b/core/src/apps/cardano/address.py @@ -2,11 +2,11 @@ from trezor import wire from trezor.crypto import base58, hashlib from trezor.messages import CardanoAddressParametersType, CardanoAddressType -from apps.common import HARDENED from apps.common.seed import remove_ed25519_prefix from .byron_address import derive_byron_address, validate_output_byron_address -from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, bech32, network_ids, purposes +from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, bech32, network_ids +from .helpers.paths import SCHEMA_STAKING from .helpers.utils import variable_length_encode from .seed import is_byron_path, is_shelley_path @@ -32,27 +32,6 @@ MIN_ADDRESS_BYTES_LENGTH = 29 MAX_ADDRESS_BYTES_LENGTH = 65 -def validate_full_path(path: List[int]) -> bool: - """ - Validates derivation path to fit {44', 1852'}/1815'/a'/{0,1,2}/i, - where `a` is an account number and i an address index. - The max value for `a` is 20, 1 000 000 for `i`. - """ - if len(path) != 5: - return False - if path[0] not in (purposes.BYRON, purposes.SHELLEY): - return False - if path[1] != 1815 | HARDENED: - return False - if path[2] < HARDENED or path[2] > 20 | HARDENED: - return False - if path[3] not in (0, 1, 2): - return False - if path[4] > 1000000: - return False - return True - - def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None: if address is None or len(address) == 0: raise INVALID_ADDRESS @@ -262,31 +241,10 @@ def _validate_base_address_staking_info( "Base address needs either a staking path or a staking key hash!" ) - if staking_key_hash is None and not is_staking_path(staking_path): + if staking_key_hash is None and not SCHEMA_STAKING.match(staking_path): raise wire.DataError("Invalid staking path!") -def is_staking_path(path: List[int]) -> bool: - """ - Validates path to match 1852'/1815'/a'/2/0. Path must - be a valid Cardano path. It must have a Shelley purpose - (Byron paths are not valid staking paths), it must have - 2 as chain type and currently there is only one staking - path for each account so a 0 is required for address index. - """ - if not validate_full_path(path): - return False - - if path[0] != purposes.SHELLEY: - return False - if path[3] != 2: - return False - if path[4] != 0: - return False - - return True - - def _derive_pointer_address( keychain: seed.Keychain, path: List[int], @@ -332,7 +290,7 @@ def _derive_reward_address( path: List[int], network_id: int, ) -> bytes: - if not is_staking_path(path): + if not SCHEMA_STAKING.match(path): raise wire.DataError("Invalid path for reward address!") header = _create_address_header(CardanoAddressType.REWARD, network_id) diff --git a/core/src/apps/cardano/get_address.py b/core/src/apps/cardano/get_address.py index 38a5f5934..2fa6e8e0d 100644 --- a/core/src/apps/cardano/get_address.py +++ b/core/src/apps/cardano/get_address.py @@ -4,9 +4,10 @@ from trezor.messages.CardanoAddress import CardanoAddress from apps.common import paths from apps.common.layout import address_n_to_str, show_qr -from . import CURVE, seed -from .address import derive_human_readable_address, validate_full_path +from . import seed +from .address import derive_human_readable_address from .helpers import protocol_magics, staking_use_cases +from .helpers.paths import SCHEMA_ADDRESS from .helpers.utils import to_account_path from .layout import ( show_address, @@ -26,7 +27,11 @@ async def get_address( address_parameters = msg.address_parameters await paths.validate_path( - ctx, validate_full_path, keychain, address_parameters.address_n, CURVE + ctx, + keychain, + address_parameters.address_n, + # path must match the ADDRESS schema + SCHEMA_ADDRESS.match(address_parameters.address_n), ) validate_network_info(msg.network_id, msg.protocol_magic) diff --git a/core/src/apps/cardano/get_public_key.py b/core/src/apps/cardano/get_public_key.py index e5b961c54..e5a3688cc 100644 --- a/core/src/apps/cardano/get_public_key.py +++ b/core/src/apps/cardano/get_public_key.py @@ -4,11 +4,11 @@ from trezor import log, wire from trezor.messages.CardanoPublicKey import CardanoPublicKey from trezor.messages.HDNodeType import HDNodeType -from apps.common import HARDENED, layout, paths +from apps.common import layout, paths from apps.common.seed import remove_ed25519_prefix -from . import CURVE, seed -from .helpers import purposes +from . import seed +from .helpers.paths import SCHEMA_PUBKEY if False: from typing import List @@ -21,10 +21,10 @@ async def get_public_key( ): await paths.validate_path( ctx, - _validate_path_for_get_public_key, keychain, msg.address_n, - CURVE, + # path must match the PUBKEY schema + SCHEMA_PUBKEY.match(msg.address_n), ) try: @@ -57,26 +57,3 @@ def _get_public_key( ) return CardanoPublicKey(node=node_type, xpub=xpub_key) - - -def _validate_path_for_get_public_key(path: List[int]) -> bool: - """ - Modified version of paths.validate_path_for_get_public_key. - Checks if path has at least three hardened items, Byron or Shelley purpose - and slip44 id 1815. The path is allowed to have more than three items, - but all the following items have to be non-hardened. - """ - length = len(path) - if length < 3 or length > 5: - return False - if path[0] not in (purposes.BYRON, purposes.SHELLEY): - return False - if path[1] != 1815 | HARDENED: - return False - if path[2] < HARDENED or path[2] > 20 | 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 diff --git a/core/src/apps/cardano/helpers/paths.py b/core/src/apps/cardano/helpers/paths.py new file mode 100644 index 000000000..e2b34ad95 --- /dev/null +++ b/core/src/apps/cardano/helpers/paths.py @@ -0,0 +1,14 @@ +from apps.common import HARDENED +from apps.common.paths import PathSchema + +SLIP44_ID = 1815 + +BYRON_ROOT = [44 | HARDENED, SLIP44_ID | HARDENED] +SHELLEY_ROOT = [1852 | HARDENED, SLIP44_ID | HARDENED] + +# fmt: off +SCHEMA_PUBKEY = PathSchema("m/[44,1852]'/coin_type'/account'/*", SLIP44_ID) +SCHEMA_ADDRESS = PathSchema("m/[44,1852]'/coin_type'/account'/[0,1,2]/address_index", SLIP44_ID) +# staking is only allowed on Shelley paths with suffix /2/0 +SCHEMA_STAKING = PathSchema("m/1852'/coin_type'/account'/2/0", SLIP44_ID) +# fmt: on diff --git a/core/src/apps/cardano/helpers/purposes.py b/core/src/apps/cardano/helpers/purposes.py deleted file mode 100644 index 2f0742181..000000000 --- a/core/src/apps/cardano/helpers/purposes.py +++ /dev/null @@ -1,4 +0,0 @@ -from apps.common import HARDENED - -BYRON = 44 | HARDENED -SHELLEY = 1852 | HARDENED diff --git a/core/src/apps/cardano/helpers/seed_namespaces.py b/core/src/apps/cardano/helpers/seed_namespaces.py deleted file mode 100644 index 35e6ffede..000000000 --- a/core/src/apps/cardano/helpers/seed_namespaces.py +++ /dev/null @@ -1,6 +0,0 @@ -from apps.common import HARDENED - -from . import purposes - -BYRON = [purposes.BYRON, 1815 | HARDENED] -SHELLEY = [purposes.SHELLEY, 1815 | HARDENED] diff --git a/core/src/apps/cardano/helpers/staking_use_cases.py b/core/src/apps/cardano/helpers/staking_use_cases.py index e48938850..2c1d2ad67 100644 --- a/core/src/apps/cardano/helpers/staking_use_cases.py +++ b/core/src/apps/cardano/helpers/staking_use_cases.py @@ -1,7 +1,8 @@ from trezor.messages import CardanoAddressType -from ..address import get_public_key_hash, validate_full_path +from ..address import get_public_key_hash from ..seed import is_shelley_path +from .paths import SCHEMA_ADDRESS from .utils import to_account_path if False: @@ -28,7 +29,7 @@ def get( ) -> int: address_type = address_parameters.address_type if address_type == CardanoAddressType.BASE: - if not validate_full_path(address_parameters.address_n): + if not SCHEMA_ADDRESS.match(address_parameters.address_n): return MISMATCH if not is_shelley_path(address_parameters.address_n): return MISMATCH diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index 3dcb0b6e7..34f9dd511 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -6,7 +6,7 @@ from apps.common import mnemonic from apps.common.passphrase import get as get_passphrase from apps.common.seed import get_seed -from .helpers import seed_namespaces +from .helpers import paths if False: from apps.common.paths import Bip32Path @@ -17,15 +17,15 @@ class Keychain: """Cardano keychain hard-coded to Byron and Shelley seed namespaces.""" def __init__(self, root: bip32.HDNode) -> None: - self.byron_root = derive_path_cardano(root, seed_namespaces.BYRON) - self.shelley_root = derive_path_cardano(root, seed_namespaces.SHELLEY) + self.byron_root = derive_path_cardano(root, paths.BYRON_ROOT) + self.shelley_root = derive_path_cardano(root, paths.SHELLEY_ROOT) root.__del__() def verify_path(self, path: Bip32Path) -> None: if not is_byron_path(path) and not is_shelley_path(path): raise wire.DataError("Forbidden key path") - def _get_path_root(self, path: list): + def _get_path_root(self, path: Bip32Path) -> bip32.HDNode: if is_byron_path(path): return self.byron_root elif is_shelley_path(path): @@ -33,13 +33,16 @@ class Keychain: else: raise wire.DataError("Forbidden key path") + def is_in_keychain(self, path: Bip32Path) -> bool: + return is_byron_path(path) or is_shelley_path(path) + def derive(self, node_path: Bip32Path) -> bip32.HDNode: self.verify_path(node_path) path_root = self._get_path_root(node_path) # this is true now, so for simplicity we don't branch on path type - assert len(seed_namespaces.BYRON) == len(seed_namespaces.SHELLEY) - suffix = node_path[len(seed_namespaces.SHELLEY) :] + assert len(paths.BYRON_ROOT) == len(paths.SHELLEY_ROOT) + suffix = node_path[len(paths.SHELLEY_ROOT) :] # derive child node from the root return derive_path_cardano(path_root, suffix) @@ -50,11 +53,11 @@ class Keychain: def is_byron_path(path: Bip32Path): - return path[: len(seed_namespaces.BYRON)] == seed_namespaces.BYRON + return path[: len(paths.BYRON_ROOT)] == paths.BYRON_ROOT def is_shelley_path(path: Bip32Path): - return path[: len(seed_namespaces.SHELLEY)] == seed_namespaces.SHELLEY + return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT def derive_path_cardano(root: bip32.HDNode, path: Bip32Path) -> bip32.HDNode: diff --git a/core/src/apps/cardano/sign_tx.py b/core/src/apps/cardano/sign_tx.py index 7b3c0f761..48afa3d06 100644 --- a/core/src/apps/cardano/sign_tx.py +++ b/core/src/apps/cardano/sign_tx.py @@ -11,14 +11,12 @@ from apps.common import cbor from apps.common.paths import validate_path from apps.common.seed import remove_ed25519_prefix -from . import CURVE, seed +from . import seed from .address import ( derive_address_bytes, derive_human_readable_address, get_address_bytes_unsafe, get_public_key_hash, - is_staking_path, - validate_full_path, validate_output_address, ) from .byron_address import get_address_attributes @@ -30,6 +28,7 @@ from .helpers import ( protocol_magics, staking_use_cases, ) +from .helpers.paths import SCHEMA_ADDRESS, SCHEMA_STAKING from .helpers.utils import to_account_path from .layout import ( confirm_certificate, @@ -75,7 +74,9 @@ async def sign_tx( validate_network_info(msg.network_id, msg.protocol_magic) for i in msg.inputs: - await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE) + await validate_path( + ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n) + ) _validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id) _validate_certificates(msg.certificates) @@ -140,7 +141,7 @@ def _validate_outputs( def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None: for certificate in certificates: - if not is_staking_path(certificate.path): + if not SCHEMA_STAKING.match(certificate.path): raise INVALID_CERTIFICATE if certificate.type == CardanoCertificateType.STAKE_DELEGATION: @@ -150,7 +151,7 @@ def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None: for withdrawal in withdrawals: - if not is_staking_path(withdrawal.path): + if not SCHEMA_STAKING.match(withdrawal.path): raise INVALID_WITHDRAWAL if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY: