diff --git a/common/protob/messages-cardano.proto b/common/protob/messages-cardano.proto index 39eb13025..f70aac826 100644 --- a/common/protob/messages-cardano.proto +++ b/common/protob/messages-cardano.proto @@ -44,9 +44,9 @@ enum CardanoPoolRelayType { * @embed */ message CardanoBlockchainPointerType { - optional uint32 block_index = 1; - optional uint32 tx_index = 2; - optional uint32 certificate_index = 3; + required uint32 block_index = 1; + required uint32 tx_index = 2; + required uint32 certificate_index = 3; } /** @@ -58,7 +58,7 @@ message CardanoBlockchainPointerType { * @embed */ message CardanoAddressParametersType { - optional CardanoAddressType address_type = 1; // one of the CardanoAddressType-s + required CardanoAddressType address_type = 1; // one of the CardanoAddressType-s repeated uint32 address_n = 2; // BIP-32-style path to derive the spending key from master node repeated uint32 address_n_staking = 3; // BIP-32-style path to derive staking key from master node optional bytes staking_key_hash = 4; // staking key can be derived from address_n_staking, or @@ -75,10 +75,10 @@ message CardanoAddressParametersType { */ message CardanoGetAddress { // repeated uint32 address_n = 1; // moved to address_parameters - optional bool show_display = 2; // optionally prompt for confirmation on trezor display - optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets - optional uint32 network_id = 4; // network id - mainnet or testnet - optional CardanoAddressParametersType address_parameters = 5; // parameters used to derive the address + optional bool show_display = 2 [default=false]; // optionally prompt for confirmation on trezor display + required uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets + required uint32 network_id = 4; // network id - mainnet or testnet + required CardanoAddressParametersType address_parameters = 5; // parameters used to derive the address } /** @@ -86,7 +86,7 @@ message CardanoGetAddress { * @end */ message CardanoAddress { - optional string address = 1; // Base58 cardano address + required string address = 1; // Base58 cardano address } /** @@ -105,8 +105,8 @@ message CardanoGetPublicKey { * @end */ message CardanoPublicKey { - optional string xpub = 1; // Xpub key - optional hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 public node + required string xpub = 1; // Xpub key + required hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 public node } /** @@ -119,10 +119,10 @@ message CardanoSignTx { repeated CardanoTxInputType inputs = 1; // inputs to be used in transaction repeated CardanoTxOutputType outputs = 2; // outputs to be used in transaction // optional uint32 transactions_count = 3; // left as a comment so we know to skip the id 3 in the future - optional uint32 protocol_magic = 5; // network's protocol magic - optional uint64 fee = 6; // transaction fee - added in shelley + required uint32 protocol_magic = 5; // network's protocol magic + required uint64 fee = 6; // transaction fee - added in shelley optional uint64 ttl = 7; // transaction ttl - added in shelley - optional uint32 network_id = 8; // network id - mainnet or testnet + required uint32 network_id = 8; // network id - mainnet or testnet repeated CardanoTxCertificateType certificates = 9; // transaction certificates - added in shelley repeated CardanoTxWithdrawalType withdrawals = 10; // transaction withdrawals - added in shelley optional bytes metadata = 11; // transaction metadata - added in shelley @@ -133,8 +133,8 @@ message CardanoSignTx { */ message CardanoTxInputType { repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node - optional bytes prev_hash = 2; // hash of previous transaction output to spend by this input - optional uint32 prev_index = 3; // index of previous output to spend + required bytes prev_hash = 2; // hash of previous transaction output to spend by this input + required uint32 prev_index = 3; // index of previous output to spend // left as a comment so we know to skip the id 4 in the future // optional uint32 type = 4; } @@ -144,7 +144,7 @@ message CardanoSignTx { message CardanoTxOutputType { optional string address = 1; // target coin address in bech32 or base58 // repeated uint32 address_n = 2; // moved to address_parameters - optional uint64 amount = 3; // amount to spend + required uint64 amount = 3; // amount to spend optional CardanoAddressParametersType address_parameters = 4; // parameters used to derive the address repeated CardanoAssetGroupType token_bundle = 5; // custom assets - added in mary } @@ -206,7 +206,7 @@ message CardanoSignTx { * Structure representing cardano transaction certificate */ message CardanoTxCertificateType { - optional CardanoCertificateType type = 1; // certificate type + required CardanoCertificateType type = 1; // certificate type repeated uint32 path = 2; // BIP-32 path to derive (staking) key optional bytes pool = 3; // pool hash optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate @@ -216,7 +216,7 @@ message CardanoSignTx { */ message CardanoTxWithdrawalType { repeated uint32 path = 1; - optional uint64 amount = 2; + required uint64 amount = 2; } } @@ -225,6 +225,6 @@ message CardanoSignTx { * @end */ message CardanoSignedTx { - optional bytes tx_hash = 1; // hash of the transaction body - optional bytes serialized_tx = 2; // serialized, signed transaction + required bytes tx_hash = 1; // hash of the transaction body + required bytes serialized_tx = 2; // serialized, signed transaction } diff --git a/core/Makefile b/core/Makefile index 6a09e8bec..d1eb83423 100644 --- a/core/Makefile +++ b/core/Makefile @@ -107,6 +107,7 @@ mypy: mypy --config-file ../setup.cfg \ src/main.py \ src/apps/bitcoin \ + src/apps/cardano \ src/apps/webauthn ## code generation: diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h index 21fa26e9a..d2a554f42 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h @@ -586,7 +586,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_bip32_from_seed_obj, #if !BITCOIN_ONLY -/// def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> bytes: +/// def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> HDNode: /// """ /// Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation /// scheme, aka v2 derivation scheme. diff --git a/core/mocks/generated/trezorcrypto/bip32.pyi b/core/mocks/generated/trezorcrypto/bip32.pyi index cb1bcf270..84a116681 100644 --- a/core/mocks/generated/trezorcrypto/bip32.pyi +++ b/core/mocks/generated/trezorcrypto/bip32.pyi @@ -117,7 +117,7 @@ def from_seed(seed: bytes, curve_name: str) -> HDNode: # extmod/modtrezorcrypto/modtrezorcrypto-bip32.h -def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> bytes: +def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> HDNode: """ Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation scheme, aka v2 derivation scheme. diff --git a/core/src/apps/cardano/address.py b/core/src/apps/cardano/address.py index 31eebea91..3067b5288 100644 --- a/core/src/apps/cardano/address.py +++ b/core/src/apps/cardano/address.py @@ -1,6 +1,6 @@ from trezor import wire from trezor.crypto import base58, hashlib -from trezor.messages import CardanoAddressParametersType, CardanoAddressType +from trezor.messages import CardanoAddressType from apps.common.seed import remove_ed25519_prefix @@ -11,9 +11,14 @@ from .helpers.utils import variable_length_encode from .seed import is_byron_path, is_shelley_path if False: - from typing import List - from trezor.messages import CardanoBlockchainPointerType - from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType + from typing import List, Optional + from trezor.messages.CardanoBlockchainPointerType import ( + CardanoBlockchainPointerType, + ) + from trezor.messages.CardanoAddressParametersType import ( + CardanoAddressParametersType, + EnumTypeCardanoAddressType, + ) from . import seed ADDRESS_TYPES_SHELLEY = ( @@ -84,8 +89,8 @@ def get_address_bytes_unsafe(address: str) -> bytes: return address_bytes -def _get_address_type(address: bytes) -> int: - return address[0] >> 4 +def _get_address_type(address: bytes) -> EnumTypeCardanoAddressType: + return address[0] >> 4 # type: ignore def _validate_shelley_address( @@ -93,14 +98,12 @@ def _validate_shelley_address( ) -> None: address_type = _get_address_type(address_bytes) - _validate_address_size(address_bytes, address_type) + _validate_address_size(address_bytes) _validate_address_bech32_hrp(address_str, address_type, network_id) _validate_address_network_id(address_bytes, network_id) -def _validate_address_size( - address_bytes: bytes, address_type: EnumTypeCardanoAddressType -) -> None: +def _validate_address_size(address_bytes: bytes) -> None: if not (MIN_ADDRESS_BYTES_LENGTH <= len(address_bytes) <= MAX_ADDRESS_BYTES_LENGTH): raise INVALID_ADDRESS @@ -220,6 +223,8 @@ def _derive_shelley_address( elif parameters.address_type == CardanoAddressType.ENTERPRISE: address = _derive_enterprise_address(keychain, parameters.address_n, network_id) elif parameters.address_type == CardanoAddressType.POINTER: + if parameters.certificate_pointer is None: + raise wire.DataError("Certificate pointer data missing!") address = _derive_pointer_address( keychain, parameters.address_n, @@ -245,7 +250,7 @@ def _derive_base_address( keychain: seed.Keychain, path: List[int], staking_path: List[int], - staking_key_hash: bytes, + staking_key_hash: Optional[bytes], network_id: int, ) -> bytes: header = _create_address_header(CardanoAddressType.BASE, network_id) @@ -261,7 +266,7 @@ def _derive_base_address( def _validate_base_address_staking_info( staking_path: List[int], - staking_key_hash: bytes, + staking_key_hash: Optional[bytes], ) -> None: if (staking_key_hash is None) == (not staking_path): raise wire.DataError( @@ -286,14 +291,6 @@ def _derive_pointer_address( def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes: - if ( - pointer is None - or pointer.block_index is None - or pointer.tx_index is None - or pointer.certificate_index is None - ): - raise wire.DataError("Invalid pointer!") - block_index_encoded = variable_length_encode(pointer.block_index) tx_index_encoded = variable_length_encode(pointer.tx_index) certificate_index_encoded = variable_length_encode(pointer.certificate_index) diff --git a/core/src/apps/cardano/byron_address.py b/core/src/apps/cardano/byron_address.py index 47e46c190..b0d2eac59 100644 --- a/core/src/apps/cardano/byron_address.py +++ b/core/src/apps/cardano/byron_address.py @@ -21,7 +21,7 @@ with base58 encoding and all the nuances of Byron addresses. """ -def _encode_address_raw(address_data_encoded) -> bytes: +def _encode_address_raw(address_data_encoded: bytes) -> bytes: return cbor.encode( [cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)] ) diff --git a/core/src/apps/cardano/certificates.py b/core/src/apps/cardano/certificates.py index af6acc5eb..7b64b41cd 100644 --- a/core/src/apps/cardano/certificates.py +++ b/core/src/apps/cardano/certificates.py @@ -11,6 +11,8 @@ from .helpers import INVALID_CERTIFICATE, LOVELACE_MAX_SUPPLY from .helpers.paths import SCHEMA_STAKING if False: + from typing import List, Optional + from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType from trezor.messages.CardanoPoolParametersType import CardanoPoolParametersType from trezor.messages.CardanoPoolRelayParametersType import ( @@ -18,10 +20,10 @@ if False: ) from trezor.messages.CardanoPoolOwnerType import CardanoPoolOwnerType from trezor.messages.CardanoPoolMetadataType import CardanoPoolMetadataType - from typing import List, Optional, Union, Tuple, Any - from . import seed - CborSequence = Union[List[Any], Tuple[Any, ...]] + from apps.common.cbor import CborSequence + + from . import seed POOL_HASH_SIZE = 28 VRF_KEY_HASH_SIZE = 32 diff --git a/core/src/apps/cardano/get_address.py b/core/src/apps/cardano/get_address.py index 2fa6e8e0d..03ddcc6da 100644 --- a/core/src/apps/cardano/get_address.py +++ b/core/src/apps/cardano/get_address.py @@ -17,7 +17,10 @@ from .layout import ( from .sign_tx import validate_network_info if False: - from trezor.messages import CardanoAddressParametersType, CardanoGetAddress + from trezor.messages.CardanoAddressParametersType import ( + CardanoAddressParametersType, + ) + from trezor.messages.CardanoGetAddress import CardanoGetAddress @seed.with_keychain @@ -95,4 +98,6 @@ async def _show_staking_warnings( address_parameters.staking_key_hash, ) elif staking_type == staking_use_cases.POINTER_ADDRESS: + # ensured in _derive_shelley_address: + assert address_parameters.certificate_pointer is not None await show_warning_address_pointer(ctx, address_parameters.certificate_pointer) diff --git a/core/src/apps/cardano/get_public_key.py b/core/src/apps/cardano/get_public_key.py index e5a3688cc..550fde5c5 100644 --- a/core/src/apps/cardano/get_public_key.py +++ b/core/src/apps/cardano/get_public_key.py @@ -12,13 +12,13 @@ from .helpers.paths import SCHEMA_PUBKEY if False: from typing import List - from trezor.messages import CardanoGetPublicKey + from trezor.messages.CardanoGetPublicKey import CardanoGetPublicKey @seed.with_keychain async def get_public_key( ctx: wire.Context, msg: CardanoGetPublicKey, keychain: seed.Keychain -): +) -> CardanoPublicKey: await paths.validate_path( ctx, keychain, diff --git a/core/src/apps/cardano/helpers/bech32.py b/core/src/apps/cardano/helpers/bech32.py index 165cb4717..3850b633f 100644 --- a/core/src/apps/cardano/helpers/bech32.py +++ b/core/src/apps/cardano/helpers/bech32.py @@ -10,6 +10,8 @@ HRP_TESTNET_REWARD_ADDRESS = "stake_test" def encode(hrp: str, data: bytes) -> str: converted_bits = bech32.convertbits(data, 8, 5) + if converted_bits is None: + raise ValueError return bech32.bech32_encode(hrp, converted_bits) @@ -18,12 +20,14 @@ def decode_unsafe(bech: str) -> bytes: return decode(hrp, bech) -def get_hrp(bech: str): +def get_hrp(bech: str) -> str: return bech.rsplit(HRP_SEPARATOR, 1)[0] def decode(hrp: str, bech: str) -> bytes: decoded_hrp, data = bech32.bech32_decode(bech, 130) + if data is None: + raise ValueError if decoded_hrp != hrp: raise ValueError diff --git a/core/src/apps/cardano/helpers/staking_use_cases.py b/core/src/apps/cardano/helpers/staking_use_cases.py index defece9d7..5c776e77d 100644 --- a/core/src/apps/cardano/helpers/staking_use_cases.py +++ b/core/src/apps/cardano/helpers/staking_use_cases.py @@ -7,8 +7,10 @@ from .utils import to_account_path if False: from typing import List - from trezor.messages import CardanoAddressParametersType - from .. import seed + from trezor.messages.CardanoAddressParametersType import ( + CardanoAddressParametersType, + ) + from ..seed import Keychain """ @@ -24,9 +26,7 @@ MISMATCH = 2 POINTER_ADDRESS = 3 -def get( - keychain: seed.Keychain, address_parameters: CardanoAddressParametersType -) -> int: +def get(keychain: Keychain, address_parameters: CardanoAddressParametersType) -> int: address_type = address_parameters.address_type if address_type == CardanoAddressType.BASE: if not SCHEMA_ADDRESS.match(address_parameters.address_n): diff --git a/core/src/apps/cardano/layout.py b/core/src/apps/cardano/layout.py index 9c6217b84..3c01b74ba 100644 --- a/core/src/apps/cardano/layout.py +++ b/core/src/apps/cardano/layout.py @@ -6,8 +6,6 @@ from trezor.messages import ( ButtonRequestType, CardanoAddressType, CardanoCertificateType, - CardanoPoolMetadataType, - CardanoPoolOwnerType, ) from trezor.strings import format_amount from trezor.ui.button import ButtonDefault @@ -30,14 +28,16 @@ from .helpers.utils import format_account_number, format_optional_int, to_accoun if False: from typing import List, Optional from trezor import wire - from trezor.messages import ( + from trezor.messages.CardanoBlockchainPointerType import ( CardanoBlockchainPointerType, - CardanoTxCertificateType, - CardanoTxWithdrawalType, - CardanoPoolParametersType, - CardanoAssetGroupType, - CardanoTokenType, ) + from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType + from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType + from trezor.messages.CardanoPoolParametersType import CardanoPoolParametersType + from trezor.messages.CardanoPoolOwnerType import CardanoPoolOwnerType + from trezor.messages.CardanoPoolMetadataType import CardanoPoolMetadataType + from trezor.messages.CardanoAssetGroupType import CardanoAssetGroupType + from trezor.messages.CardanoTokenType import CardanoTokenType from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType @@ -85,7 +85,8 @@ async def confirm_sending( to_lines = list(chunks(to, 17)) page1.bold(to_lines[0]) - pages = [page1] + _paginate_lines(to_lines, 1, "Confirm transaction", ui.ICON_SEND) + comp: ui.Component = page1 # otherwise `[page1]` is of the wrong type + pages = [comp] + _paginate_lines(to_lines, 1, "Confirm transaction", ui.ICON_SEND) await require_confirm(ctx, Paginated(pages)) @@ -215,7 +216,7 @@ async def show_warning_tx_staking_key_hash( async def confirm_transaction( - ctx, + ctx: wire.Context, amount: int, fee: int, protocol_magic: int, @@ -224,7 +225,7 @@ async def confirm_transaction( has_metadata: bool, is_network_id_verifiable: bool, ) -> None: - pages = [] + pages: List[ui.Component] = [] page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page1.normal("Transaction amount:") @@ -257,7 +258,7 @@ async def confirm_certificate( # in this call assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION - pages = [] + pages: List[ui.Component] = [] page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page1.normal("Confirm:") @@ -267,6 +268,7 @@ async def confirm_certificate( pages.append(page1) if certificate.type == CardanoCertificateType.STAKE_DELEGATION: + assert certificate.pool is not None # validate_certificate page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page2.normal("to pool:") page2.bold(hexlify(certificate.pool).decode()) @@ -304,11 +306,11 @@ async def confirm_stake_pool_parameters( async def confirm_stake_pool_owners( ctx: wire.Context, - keychain: seed.keychain, + keychain: seed.Keychain, owners: List[CardanoPoolOwnerType], network_id: int, ) -> None: - pages = [] + pages: List[ui.Component] = [] for index, owner in enumerate(owners, 1): page = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page.normal("Pool owner #%d:" % (index)) @@ -324,6 +326,7 @@ async def confirm_stake_pool_owners( ) ) else: + assert owner.staking_key_hash is not None # validate_pool_owners page.bold( encode_human_readable_address( pack_reward_address_bytes(owner.staking_key_hash, network_id) @@ -360,7 +363,10 @@ async def confirm_stake_pool_metadata( async def confirm_transaction_network_ttl( - ctx, protocol_magic: int, ttl: Optional[int], validity_interval_start: Optional[int] + ctx: wire.Context, + protocol_magic: int, + ttl: Optional[int], + validity_interval_start: Optional[int], ) -> None: page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page1.normal("Network:") @@ -428,13 +434,17 @@ async def show_address( for address_line in address_lines[: lines_per_page - lines_used_on_first_page]: page1.bold(address_line) + pages: List[ui.Component] = [] + pages.append(page1) # append remaining pages containing the rest of the address - pages = [page1] + _paginate_lines( - address_lines, - lines_per_page - lines_used_on_first_page, - address_type_label, - ui.ICON_RECEIVE, - lines_per_page, + pages.extend( + _paginate_lines( + address_lines, + lines_per_page - lines_used_on_first_page, + address_type_label, + ui.ICON_RECEIVE, + lines_per_page, + ) ) return await confirm( @@ -449,7 +459,7 @@ async def show_address( def _paginate_lines( lines: List[str], offset: int, desc: str, icon: str, lines_per_page: int = 4 ) -> List[ui.Component]: - pages = [] + pages: List[ui.Component] = [] if len(lines) > offset: to_pages = list(chunks(lines[offset:], lines_per_page)) for page in to_pages: @@ -465,7 +475,7 @@ async def show_warning_address_foreign_staking_key( ctx: wire.Context, account_path: List[int], staking_account_path: List[int], - staking_key_hash: bytes, + staking_key_hash: Optional[bytes], ) -> None: page1 = Text("Warning", ui.ICON_WRONG, ui.RED) page1.normal("Stake rights associated") @@ -480,6 +490,7 @@ async def show_warning_address_foreign_staking_key( page2.bold(address_n_to_str(staking_account_path)) page2.br_half() else: + assert staking_key_hash is not None # _validate_base_address_staking_info page2.normal("Staking key:") page2.bold(hexlify(staking_key_hash).decode()) page2.normal("Continue?") diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index 34f9dd511..b12e26372 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -9,8 +9,12 @@ from apps.common.seed import get_seed from .helpers import paths if False: + from typing import Callable, Awaitable + from apps.common.paths import Bip32Path - from apps.common.keychain import MsgIn, MsgOut, Handler, HandlerWithKeychain + from apps.common.keychain import MsgIn, MsgOut, Handler + + HandlerWithKeychain = Callable[[wire.Context, MsgIn, "Keychain"], Awaitable[MsgOut]] class Keychain: @@ -52,11 +56,11 @@ class Keychain: # self.root.__del__() -def is_byron_path(path: Bip32Path): +def is_byron_path(path: Bip32Path) -> bool: return path[: len(paths.BYRON_ROOT)] == paths.BYRON_ROOT -def is_shelley_path(path: Bip32Path): +def is_shelley_path(path: Bip32Path) -> bool: return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT @@ -75,7 +79,9 @@ async def get_keychain(ctx: wire.Context) -> Keychain: if mnemonic.is_bip39(): passphrase = await get_passphrase(ctx) # derive the root node from mnemonic and passphrase via Cardano Icarus algorithm - root = bip32.from_mnemonic_cardano(mnemonic.get_secret().decode(), passphrase) + secret_bytes = mnemonic.get_secret() + assert secret_bytes is not None + root = bip32.from_mnemonic_cardano(secret_bytes.decode(), passphrase) else: # derive the root node via SLIP-0023 seed = await get_seed(ctx) diff --git a/core/src/apps/cardano/sign_tx.py b/core/src/apps/cardano/sign_tx.py index 69cebda34..f34a5e6eb 100644 --- a/core/src/apps/cardano/sign_tx.py +++ b/core/src/apps/cardano/sign_tx.py @@ -57,13 +57,16 @@ from .layout import ( from .seed import is_byron_path, is_shelley_path if False: + from typing import Any, Dict, List, Optional, Tuple, Union + from trezor.messages.CardanoSignTx import CardanoSignTx from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType from trezor.messages.CardanoTxInputType import CardanoTxInputType from trezor.messages.CardanoTxOutputType import CardanoTxOutputType from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType from trezor.messages.CardanoAssetGroupType import CardanoAssetGroupType - from typing import Dict, List, Tuple, Union + + from apps.common.cbor import CborSequence CborizedTokenBundle = Dict[bytes, Dict[bytes, int]] CborizedTxOutput = Tuple[bytes, Union[int, Tuple[int, CborizedTokenBundle]]] @@ -156,7 +159,7 @@ async def _sign_stake_pool_registration_tx( return tx -def _has_stake_pool_registration(msg: CardanoSignTx): +def _has_stake_pool_registration(msg: CardanoSignTx) -> bool: return any( cert.type == CardanoCertificateType.STAKE_POOL_REGISTRATION for cert in msg.certificates @@ -176,7 +179,9 @@ def validate_network_info(network_id: int, protocol_magic: int) -> None: raise wire.ProcessError("Invalid network id/protocol magic combination!") -def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTx): +def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTx) -> None: + # ensures that there is exactly one certificate, which is stake pool registration, + # and no withdrawals if ( len(msg.certificates) != 1 or not _has_stake_pool_registration(msg) @@ -264,7 +269,7 @@ def _validate_max_tx_output_size( ) -def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]): +def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]) -> None: if any(i.address_n for i in inputs): raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS @@ -285,7 +290,7 @@ def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None: raise INVALID_WITHDRAWAL -def _validate_metadata(metadata: bytes) -> None: +def _validate_metadata(metadata: Optional[bytes]) -> None: if not metadata: return @@ -388,7 +393,7 @@ def _cborize_output( keychain, output.address_parameters, protocol_magic, network_id ) else: - # output address is validated in _validate_outputs before this happens + assert output.address is not None # _validate_outputs address = get_address_bytes_unsafe(output.address) if not output.token_bundle: @@ -400,11 +405,11 @@ def _cborize_output( def _cborize_token_bundle( token_bundle: List[CardanoAssetGroupType], ) -> CborizedTokenBundle: - result = {} + result: CborizedTokenBundle = {} for token_group in token_bundle: cborized_policy_id = bytes(token_group.policy_id) - result[cborized_policy_id] = cborized_token_group = {} + cborized_token_group = result[cborized_policy_id] = {} for token in token_group.tokens: cborized_asset_name = bytes(token.asset_name_bytes) @@ -416,7 +421,7 @@ def _cborize_token_bundle( def _cborize_certificates( keychain: seed.Keychain, certificates: List[CardanoTxCertificateType], -) -> List[Tuple]: +) -> List[CborSequence]: return [cborize_certificate(keychain, cert) for cert in certificates] @@ -469,7 +474,7 @@ def _cborize_witnesses( # use key 0 for shelley witnesses and key 2 for byron witnesses # according to the spec in shelley.cddl in cardano-ledger-specs - witnesses = {} + witnesses: Dict[Any, Any] = {} if shelley_witnesses: witnesses[0] = shelley_witnesses if byron_witnesses: @@ -499,6 +504,8 @@ def _cborize_shelley_witnesses( ): paths.add(tuple(certificate.path)) elif certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: + # ensured by validate_certificate: + assert certificate.pool_parameters is not None # validate_certificate for pool_owner in certificate.pool_parameters.owners: if pool_owner.staking_key_path: paths.add(tuple(pool_owner.staking_key_path)) @@ -514,7 +521,7 @@ def _cborize_shelley_witnesses( def _cborize_shelley_witness( keychain: seed.Keychain, tx_body_hash: bytes, path: List[int] -) -> List[Tuple[bytes, bytes]]: +) -> Tuple[bytes, bytes]: node = keychain.derive(path) signature = ed25519.sign_ext( @@ -587,6 +594,9 @@ async def _show_stake_pool_registration_tx( ) -> None: stake_pool_registration_certificate = msg.certificates[0] pool_parameters = stake_pool_registration_certificate.pool_parameters + # _validate_stake_pool_registration_tx_structure ensures that there is only one + # certificate, and validate_certificate ensures that the structure is valid + assert pool_parameters is not None # display the transaction (certificate) in UI await confirm_stake_pool_parameters( @@ -619,6 +629,7 @@ async def _show_outputs( if _should_hide_output(output.address_parameters.address_n, msg.inputs): continue else: + assert output.address is not None # _validate_outputs address = output.address total_amount += output.amount @@ -637,13 +648,15 @@ async def _show_change_output_staking_warnings( address_parameters: CardanoAddressParametersType, address: str, amount: int, -): +) -> None: address_type = address_parameters.address_type staking_use_case = staking_use_cases.get(keychain, address_parameters) if staking_use_case == staking_use_cases.NO_STAKING: await show_warning_tx_no_staking_info(ctx, address_type, amount) elif staking_use_case == staking_use_cases.POINTER_ADDRESS: + # ensured in _derive_shelley_address: + assert address_parameters.certificate_pointer is not None await show_warning_tx_pointer_address( ctx, address_parameters.certificate_pointer, @@ -657,6 +670,8 @@ async def _show_change_output_staking_warnings( amount, ) else: + # ensured in _validate_base_address_staking_info: + assert address_parameters.staking_key_hash await show_warning_tx_staking_key_hash( ctx, address_parameters.staking_key_hash, diff --git a/core/src/apps/common/cbor.py b/core/src/apps/common/cbor.py index a14497a7a..6a78c4612 100644 --- a/core/src/apps/common/cbor.py +++ b/core/src/apps/common/cbor.py @@ -8,9 +8,10 @@ from micropython import const from trezor import log if False: - from typing import Any, Iterable, List, Tuple + from typing import Any, Iterable, List, Tuple, Union Value = Any + CborSequence = Union[List[Value], Tuple[Value, ...]] _CBOR_TYPE_MASK = const(0xE0) _CBOR_INFO_BITS = const(0x1F) diff --git a/core/src/apps/common/paths.py b/core/src/apps/common/paths.py index 95d470f3f..cd6a42df0 100644 --- a/core/src/apps/common/paths.py +++ b/core/src/apps/common/paths.py @@ -22,9 +22,6 @@ if False: from typing_extensions import Protocol from trezor import wire - # XXX this is a circular import, but it's only for typing - from .keychain import Keychain - Bip32Path = Sequence[int] Slip21Path = Sequence[bytes] PathType = TypeVar("PathType", Bip32Path, Slip21Path) @@ -33,6 +30,13 @@ if False: def match(self, path: Bip32Path) -> bool: ... + class KeychainValidatorType(Protocol): + def is_in_keychain(self, path: Bip32Path) -> bool: + ... + + def verify_path(self, path: Bip32Path) -> None: + ... + class Interval: """Helper for testing membership in an interval.""" @@ -247,7 +251,10 @@ PATTERN_SEP5 = "m/44'/coin_type'/account'" async def validate_path( - ctx: wire.Context, keychain: Keychain, path: Bip32Path, *additional_checks: bool + ctx: wire.Context, + keychain: KeychainValidatorType, + path: Bip32Path, + *additional_checks: bool, ) -> None: keychain.verify_path(path) if not keychain.is_in_keychain(path) or not all(additional_checks): diff --git a/core/tests/test_apps.cardano.address.py b/core/tests/test_apps.cardano.address.py index 73f799149..a3dc13118 100644 --- a/core/tests/test_apps.cardano.address.py +++ b/core/tests/test_apps.cardano.address.py @@ -419,33 +419,6 @@ class TestCardanoAddress(unittest.TestCase): ) derive_human_readable_address(keychain, address_parameters, 0, 0) - # block index is None - with self.assertRaises(wire.DataError): - address_parameters = CardanoAddressParametersType( - address_type=CardanoAddressType.POINTER, - address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], - certificate_pointer=CardanoBlockchainPointerType(block_index=None, tx_index=2, certificate_index=3), - ) - derive_human_readable_address(keychain, address_parameters, 0, 0) - - # tx index is None - with self.assertRaises(wire.DataError): - address_parameters = CardanoAddressParametersType( - address_type=CardanoAddressType.POINTER, - address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], - certificate_pointer=CardanoBlockchainPointerType(block_index=1, tx_index=None, certificate_index=3), - ) - derive_human_readable_address(keychain, address_parameters, 0, 0) - - # certificate index is None - with self.assertRaises(wire.DataError): - address_parameters = CardanoAddressParametersType( - address_type=CardanoAddressType.POINTER, - address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], - certificate_pointer=CardanoBlockchainPointerType(block_index=1, tx_index=2, certificate_index=None), - ) - derive_human_readable_address(keychain, address_parameters, 0, 0) - def test_reward_address(self): mnemonic = "test walk nut penalty hip pave soap entry language right filter choice" passphrase = ""