diff --git a/common/protob/messages-cardano.proto b/common/protob/messages-cardano.proto index 21fdcc41b5..a4f57ba9dd 100644 --- a/common/protob/messages-cardano.proto +++ b/common/protob/messages-cardano.proto @@ -56,6 +56,16 @@ enum CardanoCertificateType { STAKE_DEREGISTRATION = 1; STAKE_DELEGATION = 2; STAKE_POOL_REGISTRATION = 3; + STAKE_REGISTRATION_CONWAY = 7; + STAKE_DEREGISTRATION_CONWAY = 8; + VOTE_DELEGATION = 9; +} + +enum CardanoDRepType { + KEY_HASH = 0; + SCRIPT_HASH = 1; + ABSTAIN = 2; + NO_CONFIDENCE = 3; } enum CardanoPoolRelayType { @@ -66,10 +76,10 @@ enum CardanoPoolRelayType { enum CardanoTxAuxiliaryDataSupplementType { NONE = 0; - GOVERNANCE_REGISTRATION_SIGNATURE = 1; + CVOTE_REGISTRATION_SIGNATURE = 1; } -enum CardanoGovernanceRegistrationFormat { +enum CardanoCVoteRegistrationFormat { CIP15 = 0; CIP36 = 1; } @@ -164,6 +174,7 @@ message CardanoGetAddress { required uint32 network_id = 4; // network id - mainnet or testnet required CardanoAddressParametersType address_parameters = 5; // parameters used to derive the address required CardanoDerivationType derivation_type = 6; + optional bool chunkify = 7; // display the address in chunks of 4 characters } /** @@ -223,6 +234,8 @@ message CardanoSignTxInit { optional bool has_collateral_return = 19 [default=false]; optional uint64 total_collateral = 20; optional uint32 reference_inputs_count = 21 [default=0]; + optional bool chunkify = 22; // display the address in chunks of 4 characters + optional bool tag_cbor_sets = 23 [default=false]; // use tag 258 for sets in cbor } /** @@ -333,6 +346,16 @@ message CardanoPoolParametersType { required uint32 relays_count = 12; // number of pool relays } +/** + * DRep delegation parameters + * @embed +*/ +message CardanoDRep { + required CardanoDRepType type = 1; // drep type + optional bytes key_hash = 2; // drep key hash + optional bytes script_hash = 3; // drep script hash +} + /** * Request: Transaction certificate data * @next CardanoTxItemAck @@ -344,6 +367,8 @@ message CardanoTxCertificate { optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate optional bytes script_hash = 5; // stake credential script hash optional bytes key_hash = 6; // stake credential key hash + optional uint64 deposit = 7; // used for stake key registration certificate + optional CardanoDRep drep = 8; // used for vote delegation certificate } /** @@ -360,22 +385,23 @@ message CardanoTxWithdrawal { /** * @embed */ -message CardanoGovernanceRegistrationDelegation { - required bytes voting_public_key = 1; +message CardanoCVoteRegistrationDelegation { + required bytes vote_public_key = 1; required uint32 weight = 2; } /** * @embed */ -message CardanoGovernanceRegistrationParametersType { - optional bytes voting_public_key = 1; +message CardanoCVoteRegistrationParametersType { + optional bytes vote_public_key = 1; // mutually exclusive with delegations repeated uint32 staking_path = 2; - required CardanoAddressParametersType reward_address_parameters = 3; + optional CardanoAddressParametersType payment_address_parameters = 3; // mutually exclusive with payment_address required uint64 nonce = 4; - optional CardanoGovernanceRegistrationFormat format = 5 [default=CIP15]; - repeated CardanoGovernanceRegistrationDelegation delegations = 6; // mutually exclusive with voting_public_key; max 32 delegations + optional CardanoCVoteRegistrationFormat format = 5 [default=CIP15]; + repeated CardanoCVoteRegistrationDelegation delegations = 6; // mutually exclusive with vote_public_key; max 32 delegations optional uint64 voting_purpose = 7; + optional string payment_address = 8; // mutually exclusive with payment_address_parameters } /** @@ -384,7 +410,7 @@ message CardanoGovernanceRegistrationParametersType { * @next CardanoTxAuxiliaryDataSupplement */ message CardanoTxAuxiliaryData { - optional CardanoGovernanceRegistrationParametersType governance_registration_parameters = 1; + optional CardanoCVoteRegistrationParametersType cvote_registration_parameters = 1; optional bytes hash = 2; } @@ -452,7 +478,7 @@ message CardanoTxItemAck { message CardanoTxAuxiliaryDataSupplement { required CardanoTxAuxiliaryDataSupplementType type = 1; optional bytes auxiliary_data_hash = 2; - optional bytes governance_signature = 3; + optional bytes cvote_registration_signature = 3; } /** @@ -504,7 +530,7 @@ message CardanoSignTxFinished { * @next CardanoMessageSignature * @next Failure */ - message CardanoSignMessage { +message CardanoSignMessage { repeated uint32 address_n = 1; required bytes message = 2; required CardanoDerivationType derivation_type = 3; diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 9888179bba..e06d7dd0a4 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -1023,12 +1023,14 @@ import trezor.enums.BinanceTimeInForce trezor.enums.CardanoAddressType import trezor.enums.CardanoAddressType + trezor.enums.CardanoCVoteRegistrationFormat + import trezor.enums.CardanoCVoteRegistrationFormat trezor.enums.CardanoCertificateType import trezor.enums.CardanoCertificateType + trezor.enums.CardanoDRepType + import trezor.enums.CardanoDRepType trezor.enums.CardanoDerivationType import trezor.enums.CardanoDerivationType - trezor.enums.CardanoGovernanceRegistrationFormat - import trezor.enums.CardanoGovernanceRegistrationFormat trezor.enums.CardanoNativeScriptHashDisplayFormat import trezor.enums.CardanoNativeScriptHashDisplayFormat trezor.enums.CardanoNativeScriptType diff --git a/core/src/apps/cardano/README.md b/core/src/apps/cardano/README.md index 01b07cb67f..25ac786a91 100644 --- a/core/src/apps/cardano/README.md +++ b/core/src/apps/cardano/README.md @@ -91,7 +91,7 @@ Since Alonzo era, network id may be included as an item in the transaction body. ## Key types -In Shelley two types of keys are used. Payment key and staking key. Payment keys are derived from _m/1852'/1815'/x/[0,1]/y_ paths and are used for holding/transferring funds. Staking keys are derived from _m/1852'/1815'/x/2/0_ paths, thus there is only one staking key per account. They are used for staking operations - certificates, withdrawals. Shelley addresses are built from the combination of hashes of these keys. +In Shelley two types of keys are used. Payment key and staking key. Payment keys are derived from _m/1852'/1815'/x/[0,1]/y_ paths and are used for holding/transferring funds. Staking keys are derived from _m/1852'/1815'/x/2/y_ paths (in the past, the only allowed value of `y` was `0`). They are used for staking operations - certificates, withdrawals. Shelley addresses are built from the combination of hashes of these keys. [Multi-sig paths (1854')](https://cips.cardano.org/cips/cip1854/) are used to generate keys which should be used in native scripts and also to sign multi-sig transactions. [Minting paths (1855')](https://cips.cardano.org/cips/cip1855/) are used for creating minting policies and for witnessing minting transactions. @@ -291,9 +291,9 @@ Each transaction may contain auxiliary data. Auxiliary data format can be found Auxiliary data can be sent to Trezor as a hash or as an object with parameters. The hash will be included in the transaction body as is and will be shown to the user. -The only object currently supported is governance voting key registration (currently, this is used only by Catalyst, but there may be other governance use cases in the future). To be in compliance with the CDDL and other Cardano tools, governance voting key registration object is being wrapped in a tuple and an empty tuple follows it. The empty tuple represents `auxiliary_scripts` which are not yet supported on Trezor and are thus always empty. Byron addresses are not supported as governance reward addresses. The governance registration signature is returned in the form of `CardanoTxAuxiliaryDataSupplement` which also contains the auxiliary data hash calculated by Trezor. +The only object currently supported is CIP-15/CIP-36 vote key registration (currently, this is used only by Catalyst, but there may be other voting use cases in the future). To be in compliance with the CDDL and other Cardano tools, vote key registration object is being wrapped in a tuple and an empty tuple follows it. The empty tuple represents `auxiliary_scripts` which are not yet supported on Trezor and are thus always empty. Byron addresses are not supported as the addresses to receive rewards. The registration signature is returned in the form of `CardanoTxAuxiliaryDataSupplement` which also contains the auxiliary data hash calculated by Trezor. -[Governance Registration Transaction Metadata Format](https://cips.cardano.org/cips/cip36/) +[CIP-36 Vote Registration Transaction Metadata Format](https://cips.cardano.org/cips/cip36/) ### Native scripts diff --git a/core/src/apps/cardano/addresses.py b/core/src/apps/cardano/addresses.py index f5ac5288aa..e237b2ec42 100644 --- a/core/src/apps/cardano/addresses.py +++ b/core/src/apps/cardano/addresses.py @@ -1,13 +1,22 @@ -from typing import Any +from micropython import const +from typing import TYPE_CHECKING -from trezor import messages, wire +from trezor import wire from trezor.crypto import base58 from trezor.enums import CardanoAddressType -from . import byron_addresses, seed -from .helpers import ADDRESS_KEY_HASH_SIZE, SCRIPT_HASH_SIZE, bech32, network_ids +from . import byron_addresses +from .helpers import bech32 from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT -from .helpers.utils import get_public_key_hash, variable_length_encode +from .helpers.utils import get_public_key_hash + +if TYPE_CHECKING: + from typing import Any + + from trezor import messages + + from .seed import Keychain + ADDRESS_TYPES_SHELLEY = ( CardanoAddressType.BASE, @@ -37,8 +46,10 @@ CardanoAddressType.ENTERPRISE_SCRIPT, ) -MIN_ADDRESS_BYTES_LENGTH = 29 -MAX_ADDRESS_BYTES_LENGTH = 65 +ADDRESS_TYPES_PAYMENT = ADDRESS_TYPES_PAYMENT_KEY + ADDRESS_TYPES_PAYMENT_SCRIPT + +_MIN_ADDRESS_BYTES_LENGTH = const(29) +_MAX_ADDRESS_BYTES_LENGTH = const(65) def assert_params_cond(condition: bool) -> None: @@ -49,52 +60,58 @@ def assert_params_cond(condition: bool) -> None: def validate_address_parameters( parameters: messages.CardanoAddressParametersType, ) -> None: - _validate_address_parameters_structure(parameters) + from . import seed - if parameters.address_type == CardanoAddressType.BYRON: - assert_params_cond(seed.is_byron_path(parameters.address_n)) - - elif parameters.address_type == CardanoAddressType.BASE: - assert_params_cond(seed.is_shelley_path(parameters.address_n)) + _validate_address_parameters_structure(parameters) + address_type = parameters.address_type # local_cache_attribute + address_n = parameters.address_n # local_cache_attribute + address_n_staking = parameters.address_n_staking # local_cache_attribute + script_payment_hash = parameters.script_payment_hash # local_cache_attribute + is_shelley_path = seed.is_shelley_path # local_cache_attribute + CAT = CardanoAddressType # local_cache_global + + if address_type == CAT.BYRON: + assert_params_cond(seed.is_byron_path(address_n)) + + elif address_type == CAT.BASE: + assert_params_cond(is_shelley_path(address_n)) _validate_base_address_staking_info( - parameters.address_n_staking, parameters.staking_key_hash + address_n_staking, parameters.staking_key_hash ) - elif parameters.address_type == CardanoAddressType.BASE_SCRIPT_KEY: - _validate_script_hash(parameters.script_payment_hash) + elif address_type == CAT.BASE_SCRIPT_KEY: + _validate_script_hash(script_payment_hash) _validate_base_address_staking_info( - parameters.address_n_staking, parameters.staking_key_hash + address_n_staking, parameters.staking_key_hash ) - elif parameters.address_type == CardanoAddressType.BASE_KEY_SCRIPT: - assert_params_cond(seed.is_shelley_path(parameters.address_n)) + elif address_type == CAT.BASE_KEY_SCRIPT: + assert_params_cond(is_shelley_path(address_n)) _validate_script_hash(parameters.script_staking_hash) - elif parameters.address_type == CardanoAddressType.BASE_SCRIPT_SCRIPT: - _validate_script_hash(parameters.script_payment_hash) + elif address_type == CAT.BASE_SCRIPT_SCRIPT: + _validate_script_hash(script_payment_hash) _validate_script_hash(parameters.script_staking_hash) - elif parameters.address_type == CardanoAddressType.POINTER: - assert_params_cond(seed.is_shelley_path(parameters.address_n)) + elif address_type == CAT.POINTER: + assert_params_cond(is_shelley_path(address_n)) assert_params_cond(parameters.certificate_pointer is not None) - elif parameters.address_type == CardanoAddressType.POINTER_SCRIPT: - _validate_script_hash(parameters.script_payment_hash) + elif address_type == CAT.POINTER_SCRIPT: + _validate_script_hash(script_payment_hash) assert_params_cond(parameters.certificate_pointer is not None) - elif parameters.address_type == CardanoAddressType.ENTERPRISE: - assert_params_cond(seed.is_shelley_path(parameters.address_n)) + elif address_type == CAT.ENTERPRISE: + assert_params_cond(is_shelley_path(address_n)) - elif parameters.address_type == CardanoAddressType.ENTERPRISE_SCRIPT: - _validate_script_hash(parameters.script_payment_hash) + elif address_type == CAT.ENTERPRISE_SCRIPT: + _validate_script_hash(script_payment_hash) - elif parameters.address_type == CardanoAddressType.REWARD: - assert_params_cond(seed.is_shelley_path(parameters.address_n_staking)) - assert_params_cond( - SCHEMA_STAKING_ANY_ACCOUNT.match(parameters.address_n_staking) - ) + elif address_type == CAT.REWARD: + assert_params_cond(is_shelley_path(address_n_staking)) + assert_params_cond(SCHEMA_STAKING_ANY_ACCOUNT.match(address_n_staking)) - elif parameters.address_type == CardanoAddressType.REWARD_SCRIPT: + elif address_type == CAT.REWARD_SCRIPT: _validate_script_hash(parameters.script_staking_hash) else: @@ -104,75 +121,76 @@ def validate_address_parameters( def _validate_address_parameters_structure( parameters: messages.CardanoAddressParametersType, ) -> None: - address_n = parameters.address_n - address_n_staking = parameters.address_n_staking - staking_key_hash = parameters.staking_key_hash - certificate_pointer = parameters.certificate_pointer - script_payment_hash = parameters.script_payment_hash - script_staking_hash = parameters.script_staking_hash + address_n = parameters.address_n # local_cache_attribute + address_n_staking = parameters.address_n_staking # local_cache_attribute + staking_key_hash = parameters.staking_key_hash # local_cache_attribute + certificate_pointer = parameters.certificate_pointer # local_cache_attribute + script_payment_hash = parameters.script_payment_hash # local_cache_attribute + script_staking_hash = parameters.script_staking_hash # local_cache_attribute + CAT = CardanoAddressType # local_cache_global fields_to_be_empty: dict[CardanoAddressType, tuple[Any, ...]] = { - CardanoAddressType.BASE: ( + CAT.BASE: ( certificate_pointer, script_payment_hash, script_staking_hash, ), - CardanoAddressType.BASE_KEY_SCRIPT: ( + CAT.BASE_KEY_SCRIPT: ( address_n_staking, certificate_pointer, script_payment_hash, ), - CardanoAddressType.BASE_SCRIPT_KEY: ( + CAT.BASE_SCRIPT_KEY: ( address_n, certificate_pointer, script_staking_hash, ), - CardanoAddressType.BASE_SCRIPT_SCRIPT: ( + CAT.BASE_SCRIPT_SCRIPT: ( address_n, address_n_staking, certificate_pointer, ), - CardanoAddressType.POINTER: ( + CAT.POINTER: ( address_n_staking, staking_key_hash, script_payment_hash, script_staking_hash, ), - CardanoAddressType.POINTER_SCRIPT: ( + CAT.POINTER_SCRIPT: ( address_n, address_n_staking, staking_key_hash, script_staking_hash, ), - CardanoAddressType.ENTERPRISE: ( + CAT.ENTERPRISE: ( address_n_staking, staking_key_hash, certificate_pointer, script_payment_hash, script_staking_hash, ), - CardanoAddressType.ENTERPRISE_SCRIPT: ( + CAT.ENTERPRISE_SCRIPT: ( address_n, address_n_staking, staking_key_hash, certificate_pointer, script_staking_hash, ), - CardanoAddressType.BYRON: ( + CAT.BYRON: ( address_n_staking, staking_key_hash, certificate_pointer, script_payment_hash, script_staking_hash, ), - CardanoAddressType.REWARD: ( + CAT.REWARD: ( address_n, staking_key_hash, certificate_pointer, script_payment_hash, script_staking_hash, ), - CardanoAddressType.REWARD_SCRIPT: ( + CAT.REWARD_SCRIPT: ( address_n, address_n_staking, staking_key_hash, @@ -189,6 +207,8 @@ def _validate_base_address_staking_info( staking_path: list[int], staking_key_hash: bytes | None, ) -> None: + from .helpers import ADDRESS_KEY_HASH_SIZE + assert_params_cond(not (staking_key_hash and staking_path)) if staking_key_hash: @@ -200,6 +220,8 @@ def _validate_base_address_staking_info( def _validate_script_hash(script_hash: bytes | None) -> None: + from .helpers import SCRIPT_HASH_SIZE + assert_params_cond(script_hash is not None and len(script_hash) == SCRIPT_HASH_SIZE) @@ -213,6 +235,13 @@ def validate_output_address_parameters( assert_params_cond(parameters.address_type in ADDRESS_TYPES_PAYMENT_KEY) +def validate_cvote_payment_address_parameters( + parameters: messages.CardanoAddressParametersType, +) -> None: + validate_address_parameters(parameters) + assert_params_cond(parameters.address_type in ADDRESS_TYPES_SHELLEY) + + def assert_cond(condition: bool) -> None: if not condition: raise wire.ProcessError("Invalid address") @@ -232,7 +261,22 @@ def _validate_and_get_type(address: str, protocol_magic: int, network_id: int) - if address_type == CardanoAddressType.BYRON: byron_addresses.validate(address_bytes, protocol_magic) elif address_type in ADDRESS_TYPES_SHELLEY: - _validate_shelley_address(address, address_bytes, network_id) + # _validate_shelley_address + + # _validate_size + assert_cond( + _MIN_ADDRESS_BYTES_LENGTH <= len(address_bytes) <= _MAX_ADDRESS_BYTES_LENGTH + ) + + # _validate_bech32_hrp + valid_hrp = _get_bech32_hrp(address_type, network_id) + # get_hrp + bech32_hrp = address.rsplit(bech32.HRP_SEPARATOR, 1)[0] + assert_cond(valid_hrp == bech32_hrp) + + # _validate_network_id + if _get_network_id(address_bytes) != network_id: + raise wire.ProcessError("Output address network mismatch") else: raise wire.ProcessError("Invalid address") @@ -254,6 +298,13 @@ def validate_reward_address(address: str, protocol_magic: int, network_id: int) ) +def validate_cvote_payment_address( + address: str, protocol_magic: int, network_id: int +) -> None: + address_type = _validate_and_get_type(address, protocol_magic, network_id) + assert_cond(address_type in ADDRESS_TYPES_SHELLEY) + + def get_bytes_unsafe(address: str) -> bytes: try: address_bytes = bech32.decode_unsafe(address) @@ -270,32 +321,9 @@ def get_type(address: bytes) -> CardanoAddressType: return address[0] >> 4 # type: ignore [int-into-enum] -def _validate_shelley_address( - address_str: str, address_bytes: bytes, network_id: int -) -> None: - address_type = get_type(address_bytes) - - _validate_size(address_bytes) - _validate_bech32_hrp(address_str, address_type, network_id) - _validate_network_id(address_bytes, network_id) - - -def _validate_size(address_bytes: bytes) -> None: - assert_cond( - MIN_ADDRESS_BYTES_LENGTH <= len(address_bytes) <= MAX_ADDRESS_BYTES_LENGTH - ) - - -def _validate_bech32_hrp( - address_str: str, address_type: CardanoAddressType, network_id: int -) -> None: - valid_hrp = _get_bech32_hrp(address_type, network_id) - bech32_hrp = bech32.get_hrp(address_str) - - assert_cond(valid_hrp == bech32_hrp) - - def _get_bech32_hrp(address_type: CardanoAddressType, network_id: int) -> str: + from .helpers import bech32, network_ids + if address_type == CardanoAddressType.BYRON: # Byron address uses base58 encoding raise ValueError @@ -312,17 +340,12 @@ def _get_bech32_hrp(address_type: CardanoAddressType, network_id: int) -> str: return bech32.HRP_TESTNET_ADDRESS -def _validate_network_id(address: bytes, network_id: int) -> None: - if _get_network_id(address) != network_id: - raise wire.ProcessError("Output address network mismatch") - - def _get_network_id(address: bytes) -> int: return address[0] & 0x0F def derive_human_readable( - keychain: seed.Keychain, + keychain: Keychain, parameters: messages.CardanoAddressParametersType, protocol_magic: int, network_id: int, @@ -343,7 +366,7 @@ def encode_human_readable(address_bytes: bytes) -> str: def derive_bytes( - keychain: seed.Keychain, + keychain: Keychain, parameters: messages.CardanoAddressParametersType, protocol_magic: int, network_id: int, @@ -359,11 +382,13 @@ def derive_bytes( def _derive_shelley_address( - keychain: seed.Keychain, + keychain: Keychain, parameters: messages.CardanoAddressParametersType, network_id: int, ) -> bytes: - header = _create_header(parameters.address_type, network_id) + # _create_header + header_int = parameters.address_type << 4 | network_id + header = header_int.to_bytes(1, "little") payment_part = _get_payment_part(keychain, parameters) staking_part = _get_staking_part(keychain, parameters) @@ -371,13 +396,8 @@ def _derive_shelley_address( return header + payment_part + staking_part -def _create_header(address_type: CardanoAddressType, network_id: int) -> bytes: - header: int = address_type << 4 | network_id - return header.to_bytes(1, "little") - - def _get_payment_part( - keychain: seed.Keychain, parameters: messages.CardanoAddressParametersType + keychain: Keychain, parameters: messages.CardanoAddressParametersType ) -> bytes: if parameters.address_n: return get_public_key_hash(keychain, parameters.address_n) @@ -388,8 +408,10 @@ def _get_payment_part( def _get_staking_part( - keychain: seed.Keychain, parameters: messages.CardanoAddressParametersType + keychain: Keychain, parameters: messages.CardanoAddressParametersType ) -> bytes: + from .helpers.utils import variable_length_encode + if parameters.staking_key_hash: return parameters.staking_key_hash elif parameters.address_n_staking: @@ -397,16 +419,11 @@ def _get_staking_part( elif parameters.script_staking_hash: return parameters.script_staking_hash elif parameters.certificate_pointer: - return _encode_certificate_pointer(parameters.certificate_pointer) + # _encode_certificate_pointer + pointer = parameters.certificate_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) + return bytes(block_index_encoded + tx_index_encoded + certificate_index_encoded) else: return bytes() - - -def _encode_certificate_pointer( - pointer: messages.CardanoBlockchainPointerType, -) -> bytes: - 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) - - return bytes(block_index_encoded + tx_index_encoded + certificate_index_encoded) diff --git a/core/src/apps/cardano/auxiliary_data.py b/core/src/apps/cardano/auxiliary_data.py index 54001a9b62..2155490cca 100644 --- a/core/src/apps/cardano/auxiliary_data.py +++ b/core/src/apps/cardano/auxiliary_data.py @@ -1,41 +1,32 @@ +from micropython import const from typing import TYPE_CHECKING from trezor import messages, wire from trezor.crypto import hashlib -from trezor.crypto.curve import ed25519 -from trezor.enums import ( - CardanoAddressType, - CardanoGovernanceRegistrationFormat, - CardanoTxAuxiliaryDataSupplementType, -) +from trezor.enums import CardanoAddressType, CardanoCVoteRegistrationFormat from apps.common import cbor from . import addresses, layout -from .helpers import bech32 from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT from .helpers.utils import derive_public_key if TYPE_CHECKING: Delegations = list[tuple[bytes, int]] - GovernanceRegistrationPayload = dict[int, Delegations | bytes | int] - SignedGovernanceRegistrationPayload = tuple[GovernanceRegistrationPayload, bytes] - GovernanceRegistrationSignature = dict[int, bytes] - GovernanceRegistration = dict[ - int, GovernanceRegistrationPayload | GovernanceRegistrationSignature - ] + CVoteRegistrationPayload = dict[int, Delegations | bytes | int] + SignedCVoteRegistrationPayload = tuple[CVoteRegistrationPayload, bytes] from . import seed -AUXILIARY_DATA_HASH_SIZE = 32 -GOVERNANCE_VOTING_PUBLIC_KEY_LENGTH = 32 -GOVERNANCE_REGISTRATION_HASH_SIZE = 32 +_AUXILIARY_DATA_HASH_SIZE = const(32) +_CVOTE_PUBLIC_KEY_LENGTH = const(32) +_CVOTE_REGISTRATION_HASH_SIZE = const(32) -METADATA_KEY_GOVERNANCE_REGISTRATION = 61284 -METADATA_KEY_GOVERNANCE_REGISTRATION_SIGNATURE = 61285 +_METADATA_KEY_CVOTE_REGISTRATION = const(61284) +_METADATA_KEY_CVOTE_REGISTRATION_SIGNATURE = const(61285) -MAX_DELEGATION_COUNT = 32 -DEFAULT_VOTING_PURPOSE = 0 +_MAX_DELEGATION_COUNT = const(32) +_DEFAULT_VOTING_PURPOSE = const(0) def assert_cond(condition: bool) -> None: @@ -43,65 +34,79 @@ def assert_cond(condition: bool) -> None: raise wire.ProcessError("Invalid auxiliary data") -def validate(auxiliary_data: messages.CardanoTxAuxiliaryData) -> None: +def validate( + auxiliary_data: messages.CardanoTxAuxiliaryData, + protocol_magic: int, + network_id: int, +) -> None: fields_provided = 0 if auxiliary_data.hash: fields_provided += 1 - _validate_hash(auxiliary_data.hash) - if auxiliary_data.governance_registration_parameters: + # _validate_hash + assert_cond(len(auxiliary_data.hash) == _AUXILIARY_DATA_HASH_SIZE) + if auxiliary_data.cvote_registration_parameters: fields_provided += 1 - _validate_governance_registration_parameters( - auxiliary_data.governance_registration_parameters + _validate_cvote_registration_parameters( + auxiliary_data.cvote_registration_parameters, + protocol_magic, + network_id, ) assert_cond(fields_provided == 1) -def _validate_hash(auxiliary_data_hash: bytes) -> None: - assert_cond(len(auxiliary_data_hash) == AUXILIARY_DATA_HASH_SIZE) - - -def _validate_governance_registration_parameters( - parameters: messages.CardanoGovernanceRegistrationParametersType, +def _validate_cvote_registration_parameters( + parameters: messages.CardanoCVoteRegistrationParametersType, + protocol_magic: int, + network_id: int, ) -> None: - voting_key_fields_provided = 0 - if parameters.voting_public_key is not None: - voting_key_fields_provided += 1 - _validate_voting_public_key(parameters.voting_public_key) + vote_key_fields_provided = 0 + if parameters.vote_public_key is not None: + vote_key_fields_provided += 1 + _validate_vote_public_key(parameters.vote_public_key) if parameters.delegations: - voting_key_fields_provided += 1 - assert_cond(parameters.format == CardanoGovernanceRegistrationFormat.CIP36) + vote_key_fields_provided += 1 + assert_cond(parameters.format == CardanoCVoteRegistrationFormat.CIP36) _validate_delegations(parameters.delegations) - assert_cond(voting_key_fields_provided == 1) + assert_cond(vote_key_fields_provided == 1) assert_cond(SCHEMA_STAKING_ANY_ACCOUNT.match(parameters.staking_path)) - address_parameters = parameters.reward_address_parameters - assert_cond(address_parameters.address_type != CardanoAddressType.BYRON) - addresses.validate_address_parameters(address_parameters) + payment_address_fields_provided = 0 + if parameters.payment_address is not None: + payment_address_fields_provided += 1 + addresses.validate_cvote_payment_address( + parameters.payment_address, protocol_magic, network_id + ) + if parameters.payment_address_parameters: + payment_address_fields_provided += 1 + addresses.validate_cvote_payment_address_parameters( + parameters.payment_address_parameters + ) + assert_cond(payment_address_fields_provided == 1) if parameters.voting_purpose is not None: - assert_cond(parameters.format == CardanoGovernanceRegistrationFormat.CIP36) + assert_cond(parameters.format == CardanoCVoteRegistrationFormat.CIP36) -def _validate_voting_public_key(key: bytes) -> None: - assert_cond(len(key) == GOVERNANCE_VOTING_PUBLIC_KEY_LENGTH) +def _validate_vote_public_key(key: bytes) -> None: + assert_cond(len(key) == _CVOTE_PUBLIC_KEY_LENGTH) def _validate_delegations( - delegations: list[messages.CardanoGovernanceDelegation], + delegations: list[messages.CardanoCVoteDelegation], ) -> None: - assert_cond(len(delegations) <= MAX_DELEGATION_COUNT) + assert_cond(len(delegations) <= _MAX_DELEGATION_COUNT) for delegation in delegations: - _validate_voting_public_key(delegation.voting_public_key) + _validate_vote_public_key(delegation.vote_public_key) def _get_voting_purpose_to_serialize( - parameters: messages.CardanoGovernanceRegistrationParametersType, + parameters: messages.CardanoCVoteRegistrationParametersType, ) -> int | None: - if parameters.format == CardanoGovernanceRegistrationFormat.CIP15: + if parameters.format == CardanoCVoteRegistrationFormat.CIP15: return None if parameters.voting_purpose is None: - return DEFAULT_VOTING_PURPOSE + return _DEFAULT_VOTING_PURPOSE return parameters.voting_purpose @@ -109,13 +114,13 @@ async def show( ctx: wire.Context, keychain: seed.Keychain, auxiliary_data_hash: bytes, - parameters: messages.CardanoGovernanceRegistrationParametersType | None, + parameters: messages.CardanoCVoteRegistrationParametersType | None, protocol_magic: int, network_id: int, should_show_details: bool, ) -> None: if parameters: - await _show_governance_registration( + await _show_cvote_registration( ctx, keychain, parameters, @@ -128,44 +133,69 @@ async def show( await layout.show_auxiliary_data_hash(ctx, auxiliary_data_hash) -async def _show_governance_registration( +def _should_show_payment_warning(address_type: CardanoAddressType) -> bool: + # For cvote payment addresses that are actually REWARD addresses, we show a warning that the + # address is not eligible for rewards. https://github.com/cardano-foundation/CIPs/pull/373 + # However, the registration is otherwise valid, so we allow such addresses since we don't + # want to prevent the user from voting just because they use an outdated SW wallet. + return address_type not in addresses.ADDRESS_TYPES_PAYMENT + + +async def _show_cvote_registration( ctx: wire.Context, keychain: seed.Keychain, - parameters: messages.CardanoGovernanceRegistrationParametersType, + parameters: messages.CardanoCVoteRegistrationParametersType, protocol_magic: int, network_id: int, should_show_details: bool, ) -> None: + from .helpers import bech32 + from .helpers.credential import Credential, should_show_credentials + for delegation in parameters.delegations: encoded_public_key = bech32.encode( - bech32.HRP_GOVERNANCE_PUBLIC_KEY, delegation.voting_public_key + bech32.HRP_CVOTE_PUBLIC_KEY, delegation.vote_public_key ) - await layout.confirm_governance_registration_delegation( + await layout.confirm_cvote_registration_delegation( ctx, encoded_public_key, delegation.weight ) + if parameters.payment_address: + show_payment_warning = _should_show_payment_warning( + addresses.get_type(addresses.get_bytes_unsafe(parameters.payment_address)) + ) + await layout.confirm_cvote_registration_payment_address( + ctx, parameters.payment_address, show_payment_warning + ) + else: + address_parameters = parameters.payment_address_parameters + assert address_parameters # _validate_cvote_registration_parameters + show_both_credentials = should_show_credentials(address_parameters) + show_payment_warning = _should_show_payment_warning( + address_parameters.address_type + ) + await layout.show_cvote_registration_payment_credentials( + ctx, + Credential.payment_credential(address_parameters), + Credential.stake_credential(address_parameters), + show_both_credentials, + show_payment_warning, + ) + encoded_public_key: str | None = None - if parameters.voting_public_key: + if parameters.vote_public_key: encoded_public_key = bech32.encode( - bech32.HRP_GOVERNANCE_PUBLIC_KEY, parameters.voting_public_key + bech32.HRP_CVOTE_PUBLIC_KEY, parameters.vote_public_key ) - reward_address = addresses.derive_human_readable( - keychain, - parameters.reward_address_parameters, - protocol_magic, - network_id, - ) - voting_purpose: int | None = ( _get_voting_purpose_to_serialize(parameters) if should_show_details else None ) - await layout.confirm_governance_registration( + await layout.confirm_cvote_registration( ctx, encoded_public_key, parameters.staking_path, - reward_address, parameters.nonce, voting_purpose, ) @@ -177,20 +207,22 @@ def get_hash_and_supplement( protocol_magic: int, network_id: int, ) -> tuple[bytes, messages.CardanoTxAuxiliaryDataSupplement]: - if parameters := auxiliary_data.governance_registration_parameters: + from trezor.enums import CardanoTxAuxiliaryDataSupplementType + + if parameters := auxiliary_data.cvote_registration_parameters: ( - governance_registration_payload, - governance_signature, - ) = _get_signed_governance_registration_payload( + cvote_registration_payload, + cvote_registration_signature, + ) = _get_signed_cvote_registration_payload( keychain, parameters, protocol_magic, network_id ) - auxiliary_data_hash = _get_governance_registration_hash( - governance_registration_payload, governance_signature + auxiliary_data_hash = _get_cvote_registration_hash( + cvote_registration_payload, cvote_registration_signature ) auxiliary_data_supplement = messages.CardanoTxAuxiliaryDataSupplement( - type=CardanoTxAuxiliaryDataSupplementType.GOVERNANCE_REGISTRATION_SIGNATURE, + type=CardanoTxAuxiliaryDataSupplementType.CVOTE_REGISTRATION_SIGNATURE, auxiliary_data_hash=auxiliary_data_hash, - governance_signature=governance_signature, + cvote_registration_signature=cvote_registration_signature, ) return auxiliary_data_hash, auxiliary_data_supplement else: @@ -200,67 +232,74 @@ def get_hash_and_supplement( ) -def _get_governance_registration_hash( - governance_registration_payload: GovernanceRegistrationPayload, - governance_registration_payload_signature: bytes, +def _get_cvote_registration_hash( + cvote_registration_payload: CVoteRegistrationPayload, + cvote_registration_payload_signature: bytes, ) -> bytes: - cborized_governance_registration = _cborize_governance_registration( - governance_registration_payload, - governance_registration_payload_signature, - ) - return _get_hash(cbor.encode(_wrap_metadata(cborized_governance_registration))) - - -def _cborize_governance_registration( - governance_registration_payload: GovernanceRegistrationPayload, - governance_registration_payload_signature: bytes, -) -> GovernanceRegistration: - governance_registration_signature = {1: governance_registration_payload_signature} - - return { - METADATA_KEY_GOVERNANCE_REGISTRATION: governance_registration_payload, - METADATA_KEY_GOVERNANCE_REGISTRATION_SIGNATURE: governance_registration_signature, + # _cborize_catalyst_registration + cvote_registration_signature = {1: cvote_registration_payload_signature} + cborized_catalyst_registration = { + _METADATA_KEY_CVOTE_REGISTRATION: cvote_registration_payload, + _METADATA_KEY_CVOTE_REGISTRATION_SIGNATURE: cvote_registration_signature, } + # _get_hash + # _wrap_metadata + # A new structure of metadata is used after Cardano Mary era. The metadata + # is wrapped in a tuple and auxiliary_scripts may follow it. Cardano + # tooling uses this new format of "wrapped" metadata even if no + # auxiliary_scripts are included. So we do the same here. + # https://github.com/input-output-hk/cardano-ledger-specs/blob/f7deb22be14d31b535f56edc3ca542c548244c67/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl#L212 + metadata = (cborized_catalyst_registration, ()) + auxiliary_data = cbor.encode(metadata) + return hashlib.blake2b( + data=auxiliary_data, outlen=_AUXILIARY_DATA_HASH_SIZE + ).digest() + -def _get_signed_governance_registration_payload( +def _get_signed_cvote_registration_payload( keychain: seed.Keychain, - parameters: messages.CardanoGovernanceRegistrationParametersType, + parameters: messages.CardanoCVoteRegistrationParametersType, protocol_magic: int, network_id: int, -) -> SignedGovernanceRegistrationPayload: +) -> SignedCVoteRegistrationPayload: delegations_or_key: Delegations | bytes if len(parameters.delegations) > 0: delegations_or_key = [ - (delegation.voting_public_key, delegation.weight) + (delegation.vote_public_key, delegation.weight) for delegation in parameters.delegations ] - elif parameters.voting_public_key: - delegations_or_key = parameters.voting_public_key + elif parameters.vote_public_key: + delegations_or_key = parameters.vote_public_key else: - raise RuntimeError # should not be reached - _validate_governance_registration_parameters + raise RuntimeError # should not be reached - _validate_cvote_registration_parameters staking_key = derive_public_key(keychain, parameters.staking_path) - reward_address = addresses.derive_bytes( - keychain, - parameters.reward_address_parameters, - protocol_magic, - network_id, - ) + if parameters.payment_address: + payment_address = addresses.get_bytes_unsafe(parameters.payment_address) + else: + address_parameters = parameters.payment_address_parameters + assert address_parameters # _validate_cvote_registration_parameters + payment_address = addresses.derive_bytes( + keychain, + address_parameters, + protocol_magic, + network_id, + ) voting_purpose = _get_voting_purpose_to_serialize(parameters) - payload: GovernanceRegistrationPayload = { + payload: CVoteRegistrationPayload = { 1: delegations_or_key, 2: staking_key, - 3: reward_address, + 3: payment_address, 4: parameters.nonce, } if voting_purpose is not None: payload[5] = voting_purpose - signature = _create_governance_registration_payload_signature( + signature = _create_cvote_registration_payload_signature( keychain, payload, parameters.staking_path, @@ -269,40 +308,24 @@ def _get_signed_governance_registration_payload( return payload, signature -def _create_governance_registration_payload_signature( +def _create_cvote_registration_payload_signature( keychain: seed.Keychain, - governance_registration_payload: GovernanceRegistrationPayload, + cvote_registration_payload: CVoteRegistrationPayload, path: list[int], ) -> bytes: + from trezor.crypto.curve import ed25519 + node = keychain.derive(path) - encoded_governance_registration = cbor.encode( - {METADATA_KEY_GOVERNANCE_REGISTRATION: governance_registration_payload} + encoded_cvote_registration = cbor.encode( + {_METADATA_KEY_CVOTE_REGISTRATION: cvote_registration_payload} ) - governance_registration_hash = hashlib.blake2b( - data=encoded_governance_registration, - outlen=GOVERNANCE_REGISTRATION_HASH_SIZE, + cvote_registration_hash = hashlib.blake2b( + data=encoded_cvote_registration, + outlen=_CVOTE_REGISTRATION_HASH_SIZE, ).digest() return ed25519.sign_ext( - node.private_key(), node.private_key_ext(), governance_registration_hash + node.private_key(), node.private_key_ext(), cvote_registration_hash ) - - -def _wrap_metadata(metadata: dict) -> tuple[dict, tuple]: - """ - A new structure of metadata is used after Cardano Mary era. The metadata - is wrapped in a tuple and auxiliary_scripts may follow it. Cardano - tooling uses this new format of "wrapped" metadata even if no - auxiliary_scripts are included. So we do the same here. - - https://github.com/input-output-hk/cardano-ledger-specs/blob/f7deb22be14d31b535f56edc3ca542c548244c67/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl#L212 - """ - return metadata, () - - -def _get_hash(auxiliary_data: bytes) -> bytes: - return hashlib.blake2b( - data=auxiliary_data, outlen=AUXILIARY_DATA_HASH_SIZE - ).digest() diff --git a/core/src/apps/cardano/byron_addresses.py b/core/src/apps/cardano/byron_addresses.py index 7b1f818bc0..f956024e00 100644 --- a/core/src/apps/cardano/byron_addresses.py +++ b/core/src/apps/cardano/byron_addresses.py @@ -1,17 +1,17 @@ +from micropython import const from typing import TYPE_CHECKING -from trezor import log, wire -from trezor.crypto import crc, hashlib +from trezor.crypto import crc +from trezor.wire import ProcessError from apps.common import cbor from .helpers import protocol_magics -from .helpers.utils import derive_public_key if TYPE_CHECKING: from . import seed -PROTOCOL_MAGIC_KEY = 2 +_PROTOCOL_MAGIC_KEY = const(2) """ @@ -22,31 +22,28 @@ """ -def _encode_raw(address_data_encoded: bytes) -> bytes: - return cbor.encode( - [cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)] - ) - - def derive(keychain: seed.Keychain, path: list, protocol_magic: int) -> bytes: - address_attributes = get_address_attributes(protocol_magic) - - address_root = _get_address_root(keychain, path, address_attributes) - address_type = 0 - address_data = [address_root, address_attributes, address_type] - address_data_encoded = cbor.encode(address_data) - - return _encode_raw(address_data_encoded) + from .helpers.utils import derive_public_key - -def get_address_attributes(protocol_magic: int) -> dict: + # get_address_attributes # protocol magic is included in Byron addresses only on testnets if protocol_magics.is_mainnet(protocol_magic): address_attributes = {} else: - address_attributes = {PROTOCOL_MAGIC_KEY: cbor.encode(protocol_magic)} + address_attributes = {_PROTOCOL_MAGIC_KEY: cbor.encode(protocol_magic)} + + # _get_address_root + extpubkey = derive_public_key(keychain, path, extended=True) + address_root = _address_hash([0, [0, extpubkey], address_attributes]) + + address_type = 0 + address_data = [address_root, address_attributes, address_type] + address_data_encoded = cbor.encode(address_data) - return address_attributes + # _encode_raw + return cbor.encode( + [cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)] + ) def validate(address: bytes, protocol_magic: int) -> None: @@ -54,27 +51,38 @@ def validate(address: bytes, protocol_magic: int) -> None: _validate_protocol_magic(address_data_encoded, protocol_magic) +def _address_hash(data: list) -> bytes: + from trezor.crypto import hashlib + + cbor_data = cbor.encode(data) + sha_data_hash = hashlib.sha3_256(cbor_data).digest() + res = hashlib.blake2b(data=sha_data_hash, outlen=28).digest() + return res + + def _decode_raw(address: bytes) -> bytes: + from trezor import log + try: address_unpacked = cbor.decode(address) except ValueError as e: if __debug__: log.exception(__name__, e) - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") if not isinstance(address_unpacked, list) or len(address_unpacked) != 2: - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") address_data_encoded = address_unpacked[0] if not isinstance(address_data_encoded, bytes): - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") address_crc = address_unpacked[1] if not isinstance(address_crc, int): - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") if address_crc != crc.crc32(address_data_encoded): - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") return address_data_encoded @@ -87,35 +95,21 @@ def _validate_protocol_magic(address_data_encoded: bytes, protocol_magic: int) - """ address_data = cbor.decode(address_data_encoded) if not isinstance(address_data, list) or len(address_data) < 2: - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") attributes = address_data[1] if protocol_magics.is_mainnet(protocol_magic): - if PROTOCOL_MAGIC_KEY in attributes: - raise wire.ProcessError("Output address network mismatch") + if _PROTOCOL_MAGIC_KEY in attributes: + raise ProcessError("Output address network mismatch") else: # testnet - if len(attributes) == 0 or PROTOCOL_MAGIC_KEY not in attributes: - raise wire.ProcessError("Output address network mismatch") + if len(attributes) == 0 or _PROTOCOL_MAGIC_KEY not in attributes: + raise ProcessError("Output address network mismatch") - protocol_magic_cbor = attributes[PROTOCOL_MAGIC_KEY] + protocol_magic_cbor = attributes[_PROTOCOL_MAGIC_KEY] address_protocol_magic = cbor.decode(protocol_magic_cbor) if not isinstance(address_protocol_magic, int): - raise wire.ProcessError("Invalid address") + raise ProcessError("Invalid address") if address_protocol_magic != protocol_magic: - raise wire.ProcessError("Output address network mismatch") - - -def _address_hash(data: list) -> bytes: - cbor_data = cbor.encode(data) - sha_data_hash = hashlib.sha3_256(cbor_data).digest() - res = hashlib.blake2b(data=sha_data_hash, outlen=28).digest() - return res - - -def _get_address_root( - keychain: seed.Keychain, path: list[int], address_attributes: dict -) -> bytes: - extpubkey = derive_public_key(keychain, path, extended=True) - return _address_hash([0, [0, extpubkey], address_attributes]) + raise ProcessError("Output address network mismatch") diff --git a/core/src/apps/cardano/certificates.py b/core/src/apps/cardano/certificates.py index f404602598..762dea4329 100644 --- a/core/src/apps/cardano/certificates.py +++ b/core/src/apps/cardano/certificates.py @@ -1,32 +1,30 @@ +from micropython import const from typing import TYPE_CHECKING -from trezor import wire -from trezor.enums import CardanoCertificateType, CardanoPoolRelayType - -from apps.common import cbor +from trezor.enums import CardanoCertificateType, CardanoDRepType, CardanoPoolRelayType +from trezor.wire import ProcessError from . import addresses -from .helpers import ADDRESS_KEY_HASH_SIZE, LOVELACE_MAX_SUPPLY -from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT -from .helpers.utils import get_public_key_hash, validate_stake_credential +from .helpers.utils import get_public_key_hash if TYPE_CHECKING: from typing import Any from trezor import messages + from apps.common.cbor import CborSequence from . import seed from .helpers.account_path_check import AccountPathChecker -POOL_HASH_SIZE = 28 -VRF_KEY_HASH_SIZE = 32 -POOL_METADATA_HASH_SIZE = 32 -IPV4_ADDRESS_SIZE = 4 -IPV6_ADDRESS_SIZE = 16 +_POOL_HASH_SIZE = const(28) +_VRF_KEY_HASH_SIZE = const(32) +_POOL_METADATA_HASH_SIZE = const(32) +_IPV4_ADDRESS_SIZE = const(4) +_IPV6_ADDRESS_SIZE = const(16) -MAX_URL_LENGTH = 64 -MAX_PORT_NUMBER = 65535 +_MAX_URL_LENGTH = const(128) +_MAX_PORT_NUMBER = const(65535) def validate( @@ -35,75 +33,130 @@ def validate( network_id: int, account_path_checker: AccountPathChecker, ) -> None: + from .helpers.utils import validate_stake_credential + _validate_structure(certificate) + CCT = CardanoCertificateType # local_cache_global + if certificate.type in ( - CardanoCertificateType.STAKE_DELEGATION, - CardanoCertificateType.STAKE_REGISTRATION, - CardanoCertificateType.STAKE_DEREGISTRATION, + CCT.STAKE_DELEGATION, + CCT.STAKE_REGISTRATION, + CCT.STAKE_DEREGISTRATION, + ): + validate_stake_credential( + certificate.path, + certificate.script_hash, + certificate.key_hash, + ProcessError("Invalid certificate"), + ) + + if certificate.type in ( + CCT.STAKE_REGISTRATION_CONWAY, + CCT.STAKE_DEREGISTRATION_CONWAY, ): + if certificate.deposit is None: + raise ProcessError("Invalid certificate") validate_stake_credential( certificate.path, certificate.script_hash, certificate.key_hash, - wire.ProcessError("Invalid certificate"), + ProcessError("Invalid certificate"), ) - if certificate.type == CardanoCertificateType.STAKE_DELEGATION: - if not certificate.pool or len(certificate.pool) != POOL_HASH_SIZE: - raise wire.ProcessError("Invalid certificate") + if certificate.type == CCT.STAKE_DELEGATION: + if not certificate.pool or len(certificate.pool) != _POOL_HASH_SIZE: + raise ProcessError("Invalid certificate") - if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: + if certificate.type == CCT.STAKE_POOL_REGISTRATION: if certificate.pool_parameters is None: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") _validate_pool_parameters( certificate.pool_parameters, protocol_magic, network_id ) + if certificate.type == CCT.VOTE_DELEGATION: + if certificate.drep is None: + raise ProcessError("Invalid certificate") + validate_drep( + certificate.drep, + ProcessError("Invalid certificate"), + ) + validate_stake_credential( + certificate.path, + certificate.script_hash, + certificate.key_hash, + ProcessError("Invalid certificate"), + ) + account_path_checker.add_certificate(certificate) def _validate_structure(certificate: messages.CardanoTxCertificate) -> None: - pool = certificate.pool - pool_parameters = certificate.pool_parameters + pool = certificate.pool # local_cache_attribute + pool_parameters = certificate.pool_parameters # local_cache_attribute + deposit = certificate.deposit + drep = certificate.drep + CCT = CardanoCertificateType # local_cache_global fields_to_be_empty: dict[CardanoCertificateType, tuple[Any, ...]] = { - CardanoCertificateType.STAKE_REGISTRATION: (pool, pool_parameters), - CardanoCertificateType.STAKE_DELEGATION: (pool_parameters,), - CardanoCertificateType.STAKE_DEREGISTRATION: (pool, pool_parameters), - CardanoCertificateType.STAKE_POOL_REGISTRATION: ( + CCT.STAKE_REGISTRATION: (pool, pool_parameters, deposit, drep), + CCT.STAKE_REGISTRATION_CONWAY: (pool, pool_parameters, drep), + CCT.STAKE_DELEGATION: (pool_parameters, deposit, drep), + CCT.STAKE_DEREGISTRATION: (pool, pool_parameters, deposit, drep), + CCT.STAKE_DEREGISTRATION_CONWAY: (pool, pool_parameters, drep), + CCT.STAKE_POOL_REGISTRATION: ( certificate.path, certificate.script_hash, certificate.key_hash, pool, + deposit, + drep, ), + CCT.VOTE_DELEGATION: (pool, pool_parameters, deposit), } if certificate.type not in fields_to_be_empty or any( fields_to_be_empty[certificate.type] ): - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") def cborize( keychain: seed.Keychain, certificate: messages.CardanoTxCertificate ) -> CborSequence: - if certificate.type in ( + cert_type = certificate.type # local_cache_attribute + + if cert_type in ( CardanoCertificateType.STAKE_REGISTRATION, CardanoCertificateType.STAKE_DEREGISTRATION, ): return ( - certificate.type, + cert_type, + cborize_stake_credential( + keychain, + certificate.path, + certificate.script_hash, + certificate.key_hash, + ), + ) + elif cert_type in ( + CardanoCertificateType.STAKE_REGISTRATION_CONWAY, + CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, + ): + return ( + cert_type, cborize_stake_credential( keychain, certificate.path, certificate.script_hash, certificate.key_hash, ), + certificate.deposit, ) - elif certificate.type == CardanoCertificateType.STAKE_DELEGATION: + elif cert_type == CardanoCertificateType.STAKE_DELEGATION: return ( - certificate.type, + cert_type, cborize_stake_credential( keychain, certificate.path, @@ -112,6 +165,18 @@ def cborize( ), certificate.pool, ) + elif cert_type == CardanoCertificateType.VOTE_DELEGATION: + assert certificate.drep is not None + return ( + cert_type, + cborize_stake_credential( + keychain, + certificate.path, + certificate.script_hash, + certificate.key_hash, + ), + cborize_drep(certificate.drep), + ) else: raise RuntimeError # should be unreachable @@ -135,6 +200,8 @@ def cborize_stake_credential( def cborize_pool_registration_init( certificate: messages.CardanoTxCertificate, ) -> CborSequence: + from apps.common import cbor + assert certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION pool_parameters = certificate.pool_parameters @@ -161,7 +228,7 @@ def cborize_pool_registration_init( def assert_cond(condition: bool) -> None: if not condition: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") def _validate_pool_parameters( @@ -169,8 +236,10 @@ def _validate_pool_parameters( protocol_magic: int, network_id: int, ) -> None: - assert_cond(len(pool_parameters.pool_id) == POOL_HASH_SIZE) - assert_cond(len(pool_parameters.vrf_key_hash) == VRF_KEY_HASH_SIZE) + from .helpers import LOVELACE_MAX_SUPPLY + + assert_cond(len(pool_parameters.pool_id) == _POOL_HASH_SIZE) + assert_cond(len(pool_parameters.vrf_key_hash) == _VRF_KEY_HASH_SIZE) assert_cond(0 <= pool_parameters.pledge <= LOVELACE_MAX_SUPPLY) assert_cond(0 <= pool_parameters.cost <= LOVELACE_MAX_SUPPLY) assert_cond(pool_parameters.margin_numerator >= 0) @@ -181,14 +250,46 @@ def _validate_pool_parameters( addresses.validate_reward_address( pool_parameters.reward_account, protocol_magic, network_id ) + pool_metadata = pool_parameters.metadata # local_cache_attribute + if pool_metadata: + # _validate_pool_metadata + assert_cond(len(pool_metadata.url) <= _MAX_URL_LENGTH) + assert_cond(len(pool_metadata.hash) == _POOL_METADATA_HASH_SIZE) + assert_cond(all((32 <= ord(c) < 127) for c in pool_metadata.url)) + - if pool_parameters.metadata: - _validate_pool_metadata(pool_parameters.metadata) +def validate_drep( + drep: messages.CardanoDRep, + error: ProcessError, +) -> None: + from .helpers import ADDRESS_KEY_HASH_SIZE, SCRIPT_HASH_SIZE + + drep_type = drep.type + script_hash = drep.script_hash + key_hash = drep.key_hash + + if drep_type == CardanoDRepType.KEY_HASH: + if script_hash or not key_hash or len(key_hash) != ADDRESS_KEY_HASH_SIZE: + raise error + elif drep_type == CardanoDRepType.SCRIPT_HASH: + if key_hash or not script_hash or len(script_hash) != SCRIPT_HASH_SIZE: + raise error + elif drep_type in ( + CardanoDRepType.ABSTAIN, + CardanoDRepType.NO_CONFIDENCE, + ): + if script_hash or key_hash: + raise error + else: + raise RuntimeError # should be unreachable def validate_pool_owner( owner: messages.CardanoPoolOwner, account_path_checker: AccountPathChecker ) -> None: + from .helpers import ADDRESS_KEY_HASH_SIZE + from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT + assert_cond( owner.staking_key_hash is not None or owner.staking_key_path is not None ) @@ -201,38 +302,40 @@ def validate_pool_owner( def validate_pool_relay(pool_relay: messages.CardanoPoolRelayParameters) -> None: + port = pool_relay.port # local_cache_attribute + host_name = pool_relay.host_name # local_cache_attribute + if pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_IP: assert_cond( pool_relay.ipv4_address is not None or pool_relay.ipv6_address is not None ) if pool_relay.ipv4_address is not None: - assert_cond(len(pool_relay.ipv4_address) == IPV4_ADDRESS_SIZE) + assert_cond(len(pool_relay.ipv4_address) == _IPV4_ADDRESS_SIZE) if pool_relay.ipv6_address is not None: - assert_cond(len(pool_relay.ipv6_address) == IPV6_ADDRESS_SIZE) - assert_cond( - pool_relay.port is not None and 0 <= pool_relay.port <= MAX_PORT_NUMBER - ) + assert_cond(len(pool_relay.ipv6_address) == _IPV6_ADDRESS_SIZE) + assert_cond(port is not None and 0 <= port <= _MAX_PORT_NUMBER) elif pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_NAME: - assert_cond( - pool_relay.host_name is not None - and len(pool_relay.host_name) <= MAX_URL_LENGTH - ) - assert_cond( - pool_relay.port is not None and 0 <= pool_relay.port <= MAX_PORT_NUMBER - ) + assert_cond(host_name is not None and len(host_name) <= _MAX_URL_LENGTH) + assert_cond(port is not None and 0 <= port <= _MAX_PORT_NUMBER) elif pool_relay.type == CardanoPoolRelayType.MULTIPLE_HOST_NAME: - assert_cond( - pool_relay.host_name is not None - and len(pool_relay.host_name) <= MAX_URL_LENGTH - ) + assert_cond(host_name is not None and len(host_name) <= _MAX_URL_LENGTH) else: raise RuntimeError # should be unreachable -def _validate_pool_metadata(pool_metadata: messages.CardanoPoolMetadataType) -> None: - assert_cond(len(pool_metadata.url) <= MAX_URL_LENGTH) - assert_cond(len(pool_metadata.hash) == POOL_METADATA_HASH_SIZE) - assert_cond(all((32 <= ord(c) < 127) for c in pool_metadata.url)) +def cborize_drep(drep: messages.CardanoDRep) -> tuple[int, bytes] | tuple[int]: + if drep.type == CardanoDRepType.KEY_HASH: + assert drep.key_hash is not None + return 0, drep.key_hash + elif drep.type == CardanoDRepType.SCRIPT_HASH: + assert drep.script_hash is not None + return 1, drep.script_hash + elif drep.type == CardanoDRepType.ABSTAIN: + return (2,) + elif drep.type == CardanoDRepType.NO_CONFIDENCE: + return (3,) + else: + raise RuntimeError # should be unreachable def cborize_pool_owner( @@ -251,7 +354,7 @@ def _cborize_ipv6_address(ipv6_address: bytes | None) -> bytes | None: return None # ipv6 addresses are serialized to CBOR as uint_32[4] little endian - assert len(ipv6_address) == IPV6_ADDRESS_SIZE + assert len(ipv6_address) == _IPV6_ADDRESS_SIZE result = b"" for i in range(0, 4): @@ -263,22 +366,24 @@ def _cborize_ipv6_address(ipv6_address: bytes | None) -> bytes | None: def cborize_pool_relay( pool_relay: messages.CardanoPoolRelayParameters, ) -> CborSequence: - if pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_IP: + relay_type = pool_relay.type # local_cache_attribute + + if relay_type == CardanoPoolRelayType.SINGLE_HOST_IP: return ( - pool_relay.type, + relay_type, pool_relay.port, pool_relay.ipv4_address, _cborize_ipv6_address(pool_relay.ipv6_address), ) - elif pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_NAME: + elif relay_type == CardanoPoolRelayType.SINGLE_HOST_NAME: return ( - pool_relay.type, + relay_type, pool_relay.port, pool_relay.host_name, ) - elif pool_relay.type == CardanoPoolRelayType.MULTIPLE_HOST_NAME: + elif relay_type == CardanoPoolRelayType.MULTIPLE_HOST_NAME: return ( - pool_relay.type, + relay_type, pool_relay.host_name, ) else: diff --git a/core/src/apps/cardano/get_address.py b/core/src/apps/cardano/get_address.py index 44d8bc0e09..da9ffa60d9 100644 --- a/core/src/apps/cardano/get_address.py +++ b/core/src/apps/cardano/get_address.py @@ -1,17 +1,32 @@ -from trezor import log, messages, wire +from typing import TYPE_CHECKING -from . import addresses, seed -from .helpers.credential import Credential, should_show_credentials -from .helpers.utils import validate_network_info -from .layout import show_cardano_address, show_credentials +from trezor import wire +from trezor.messages import CardanoAddress + +from . import seed + +if __debug__: + from trezor import log + +if TYPE_CHECKING: + from trezor.messages import CardanoGetAddress @seed.with_keychain async def get_address( - ctx: wire.Context, msg: messages.CardanoGetAddress, keychain: seed.Keychain -) -> messages.CardanoAddress: + ctx: wire.Context, msg: CardanoGetAddress, keychain: seed.Keychain +) -> CardanoAddress: + + from . import addresses + from .helpers.credential import Credential, should_show_credentials + from .helpers.utils import validate_network_info + from .layout import show_cardano_address, show_credentials + + address_parameters = msg.address_parameters # local_cache_attribute + validate_network_info(msg.network_id, msg.protocol_magic) - addresses.validate_address_parameters(msg.address_parameters) + addresses.validate_address_parameters(address_parameters) + from trezor.lvglui.scrs import lv from . import ICON, PRIMARY_COLOR @@ -19,7 +34,7 @@ async def get_address( try: address = addresses.derive_human_readable( - keychain, msg.address_parameters, msg.protocol_magic, msg.network_id + keychain, address_parameters, msg.protocol_magic, msg.network_id ) except ValueError as e: if __debug__: @@ -27,22 +42,19 @@ async def get_address( raise wire.ProcessError("Deriving address failed") if msg.show_display: - await _display_address(ctx, msg.address_parameters, address, msg.protocol_magic) - - return messages.CardanoAddress(address=address) - - -async def _display_address( - ctx: wire.Context, - address_parameters: messages.CardanoAddressParametersType, - address: str, - protocol_magic: int, -) -> None: - if should_show_credentials(address_parameters): - await show_credentials( + # _display_address + if should_show_credentials(address_parameters): + await show_credentials( + ctx, + Credential.payment_credential(address_parameters), + Credential.stake_credential(address_parameters), + ) + await show_cardano_address( ctx, - Credential.payment_credential(address_parameters), - Credential.stake_credential(address_parameters), + address_parameters, + address, + msg.protocol_magic, + chunkify=bool(msg.chunkify), ) - await show_cardano_address(ctx, address_parameters, address, protocol_magic) + return CardanoAddress(address=address) diff --git a/core/src/apps/cardano/get_native_script_hash.py b/core/src/apps/cardano/get_native_script_hash.py index e5f4130676..403e0005a1 100644 --- a/core/src/apps/cardano/get_native_script_hash.py +++ b/core/src/apps/cardano/get_native_script_hash.py @@ -1,13 +1,22 @@ -from trezor import messages, wire -from trezor.enums import CardanoNativeScriptHashDisplayFormat +from typing import TYPE_CHECKING -from . import layout, native_script, seed +from trezor.messages import CardanoNativeScriptHash + +from . import seed + +if TYPE_CHECKING: + from trezor.messages import CardanoGetNativeScriptHash + from trezor import wire @seed.with_keychain async def get_native_script_hash( - ctx: wire.Context, msg: messages.CardanoGetNativeScriptHash, keychain: seed.Keychain -) -> messages.CardanoNativeScriptHash: + ctx: wire.Context, msg: CardanoGetNativeScriptHash, keychain: seed.Keychain +) -> CardanoNativeScriptHash: + from trezor.enums import CardanoNativeScriptHashDisplayFormat + + from . import layout, native_script + native_script.validate_native_script(msg.script) script_hash = native_script.get_native_script_hash(keychain, msg.script) @@ -16,4 +25,4 @@ async def get_native_script_hash( await layout.show_native_script(ctx, msg.script) await layout.show_script_hash(ctx, script_hash, msg.display_format) - return messages.CardanoNativeScriptHash(script_hash=script_hash) + return CardanoNativeScriptHash(script_hash=script_hash) diff --git a/core/src/apps/cardano/get_public_key.py b/core/src/apps/cardano/get_public_key.py index 7d0e55e4de..17537e8bb0 100644 --- a/core/src/apps/cardano/get_public_key.py +++ b/core/src/apps/cardano/get_public_key.py @@ -1,20 +1,31 @@ +from typing import TYPE_CHECKING from ubinascii import hexlify -from trezor import log, messages, wire -from trezor.ui.layouts import show_pubkey - -from apps.common import paths +from trezor import wire +from trezor.messages import CardanoPublicKey from . import seed -from .helpers.paths import SCHEMA_MINT, SCHEMA_PUBKEY -from .helpers.utils import derive_public_key + +if __debug__: + from trezor import log + +if TYPE_CHECKING: + from trezor.messages import CardanoGetPublicKey @seed.with_keychain async def get_public_key( - ctx: wire.Context, msg: messages.CardanoGetPublicKey, keychain: seed.Keychain -) -> messages.CardanoPublicKey: - address_n = msg.address_n + ctx: wire.Context, msg: CardanoGetPublicKey, keychain: seed.Keychain +) -> CardanoPublicKey: + + from trezor.ui.layouts import show_pubkey + + from apps.common import paths + + from .helpers.paths import SCHEMA_MINT, SCHEMA_PUBKEY + + address_n = msg.address_n # local_cache_attribute + await paths.validate_path( ctx, keychain, @@ -26,6 +37,7 @@ async def get_public_key( from trezor.lvglui.scrs import lv ctx.primary_color, ctx.icon_path = lv.color_hex(PRIMARY_COLOR), ICON + try: key = _get_public_key(keychain, address_n) except ValueError as e: @@ -34,30 +46,31 @@ async def get_public_key( raise wire.ProcessError("Deriving public key failed") if msg.show_display: - await show_pubkey( - ctx, - hexlify(key.node.public_key).decode(), - path=paths.address_n_to_str(msg.address_n), - network="Cardano", - ) + from apps.common.paths import address_n_to_str + + path = address_n_to_str(address_n) + await show_pubkey(ctx, key.xpub, path=path, network="Cardano") return key def _get_public_key( keychain: seed.Keychain, derivation_path: list[int] -) -> messages.CardanoPublicKey: - node = keychain.derive(derivation_path) +) -> CardanoPublicKey: + from trezor.messages import HDNodeType + + from .helpers.utils import derive_public_key - public_key = hexlify(derive_public_key(keychain, derivation_path)).decode() - chain_code = hexlify(node.chain_code()).decode() - xpub_key = public_key + chain_code + node = keychain.derive(derivation_path) + public_key = derive_public_key(keychain, derivation_path) + chain_code = node.chain_code() + xpub_key = hexlify(public_key + chain_code).decode() - node_type = messages.HDNodeType( + node_type = HDNodeType( depth=node.depth(), child_num=node.child_num(), fingerprint=node.fingerprint(), - chain_code=node.chain_code(), - public_key=derive_public_key(keychain, derivation_path), + chain_code=chain_code, + public_key=public_key, ) - return messages.CardanoPublicKey(node=node_type, xpub=xpub_key) + return CardanoPublicKey(node=node_type, xpub=xpub_key) diff --git a/core/src/apps/cardano/helpers/account_path_check.py b/core/src/apps/cardano/helpers/account_path_check.py index e6f62dda00..4b85ad2367 100644 --- a/core/src/apps/cardano/helpers/account_path_check.py +++ b/core/src/apps/cardano/helpers/account_path_check.py @@ -1,19 +1,16 @@ from typing import TYPE_CHECKING -from trezor import wire +from trezor.wire import ProcessError -from ...common.paths import HARDENED from .. import seed -from .paths import ACCOUNT_PATH_INDEX, ACCOUNT_PATH_LENGTH -from .utils import to_account_path if TYPE_CHECKING: from trezor.messages import ( CardanoPoolOwner, CardanoTxCertificate, CardanoTxOutput, - CardanoTxWitnessRequest, CardanoTxWithdrawal, + CardanoTxWitnessRequest, ) @@ -29,7 +26,9 @@ class AccountPathChecker: def __init__(self) -> None: self.account_path: object | list[int] = self.UNDEFINED - def _add(self, path: list[int], error: wire.ProcessError) -> None: + def _add(self, path: list[int], error: ProcessError) -> None: + from .utils import to_account_path + # multi-sig and minting paths are always shown and thus don't need to be checked if seed.is_multisig_path(path) or seed.is_minting_path(path): return @@ -51,10 +50,15 @@ def _is_byron_and_shelley_equivalent(self, account_path: list[int]) -> bool: from the user. This way the user can be sure that the funds are being moved between the user's accounts without being bothered by more screens. """ - assert isinstance(self.account_path, list) + from ...common.paths import HARDENED + from .paths import ACCOUNT_PATH_INDEX, ACCOUNT_PATH_LENGTH + + self_account_path = self.account_path # local_cache_attribute + + assert isinstance(self_account_path, list) is_control_path_byron_or_shelley = seed.is_byron_path( - self.account_path - ) or seed.is_shelley_path(self.account_path) + self_account_path + ) or seed.is_shelley_path(self_account_path) is_new_path_byron_or_shelley = seed.is_byron_path( account_path @@ -63,9 +67,9 @@ def _is_byron_and_shelley_equivalent(self, account_path: list[int]) -> bool: return ( is_control_path_byron_or_shelley and is_new_path_byron_or_shelley - and len(self.account_path) == ACCOUNT_PATH_LENGTH + and len(self_account_path) == ACCOUNT_PATH_LENGTH and len(account_path) == ACCOUNT_PATH_LENGTH - and self.account_path[ACCOUNT_PATH_INDEX] == 0 | HARDENED + and self_account_path[ACCOUNT_PATH_INDEX] == 0 | HARDENED and account_path[ACCOUNT_PATH_INDEX] == 0 | HARDENED ) @@ -76,27 +80,25 @@ def add_output(self, output: CardanoTxOutput) -> None: if not output.address_parameters.address_n: return - self._add( - output.address_parameters.address_n, wire.ProcessError("Invalid output") - ) + self._add(output.address_parameters.address_n, ProcessError("Invalid output")) def add_certificate(self, certificate: CardanoTxCertificate) -> None: if not certificate.path: return - self._add(certificate.path, wire.ProcessError("Invalid certificate")) + self._add(certificate.path, ProcessError("Invalid certificate")) def add_pool_owner(self, pool_owner: CardanoPoolOwner) -> None: if not pool_owner.staking_key_path: return - self._add(pool_owner.staking_key_path, wire.ProcessError("Invalid certificate")) + self._add(pool_owner.staking_key_path, ProcessError("Invalid certificate")) def add_withdrawal(self, withdrawal: CardanoTxWithdrawal) -> None: if not withdrawal.path: return - self._add(withdrawal.path, wire.ProcessError("Invalid withdrawal")) + self._add(withdrawal.path, ProcessError("Invalid withdrawal")) def add_witness_request(self, witness_request: CardanoTxWitnessRequest) -> None: - self._add(witness_request.path, wire.ProcessError("Invalid witness request")) + self._add(witness_request.path, ProcessError("Invalid witness request")) diff --git a/core/src/apps/cardano/helpers/bech32.py b/core/src/apps/cardano/helpers/bech32.py index 82bda2e565..90369d1e91 100644 --- a/core/src/apps/cardano/helpers/bech32.py +++ b/core/src/apps/cardano/helpers/bech32.py @@ -7,7 +7,7 @@ HRP_TESTNET_ADDRESS = "addr_test" HRP_REWARD_ADDRESS = "stake" HRP_TESTNET_REWARD_ADDRESS = "stake_test" -HRP_GOVERNANCE_PUBLIC_KEY = "gov_vk" +HRP_CVOTE_PUBLIC_KEY = "cvote_vk" HRP_SCRIPT_HASH = "script" HRP_KEY_HASH = "addr_vkh" HRP_SHARED_KEY_HASH = "addr_shared_vkh" @@ -15,6 +15,8 @@ HRP_REQUIRED_SIGNER_KEY_HASH = "req_signer_vkh" HRP_OUTPUT_DATUM_HASH = "datum" HRP_SCRIPT_DATA_HASH = "script_data" +HRP_DREP_KEY_HASH = "drep" +HRP_DREP_SCRIPT_HASH = "drep_script" def encode(hrp: str, data: bytes) -> str: @@ -27,19 +29,13 @@ def decode_unsafe(bech: str) -> bytes: return _decode(hrp, bech) -def get_hrp(bech: str) -> str: - return bech.rsplit(HRP_SEPARATOR, 1)[0] - - def _decode(hrp: str, bech: str) -> bytes: decoded_hrp, data, spec = bech32.bech32_decode(bech, 130) if decoded_hrp != hrp: raise ValueError if spec != bech32.Encoding.BECH32: raise ValueError - - if data is not None: - decoded = bech32.convertbits(data, 5, 8, False) - else: + if data is None: raise ValueError + decoded = bech32.convertbits(data, 5, 8, False) return bytes(decoded) diff --git a/core/src/apps/cardano/helpers/credential.py b/core/src/apps/cardano/helpers/credential.py index f59c139776..af6f804dc0 100644 --- a/core/src/apps/cardano/helpers/credential.py +++ b/core/src/apps/cardano/helpers/credential.py @@ -2,10 +2,7 @@ from trezor.enums import CardanoAddressType -from ...common.paths import address_n_to_str -from . import bech32 -from .paths import CHAIN_STAKING_KEY, SCHEMA_PAYMENT, SCHEMA_STAKING -from .utils import to_account_path +from .paths import SCHEMA_PAYMENT if TYPE_CHECKING: from trezor import messages @@ -56,7 +53,9 @@ def __init__( def payment_credential( cls, address_params: messages.CardanoAddressParametersType ) -> "Credential": - address_type = address_params.address_type + address_type = address_params.address_type # local_cache_attribute + CAT = CardanoAddressType # local_cache_global + credential = cls( type_name=CREDENTIAL_TYPE_PAYMENT, address_type=address_type, @@ -67,26 +66,26 @@ def payment_credential( ) if address_type in ( - CardanoAddressType.BASE, - CardanoAddressType.BASE_KEY_SCRIPT, - CardanoAddressType.POINTER, - CardanoAddressType.ENTERPRISE, - CardanoAddressType.BYRON, + CAT.BASE, + CAT.BASE_KEY_SCRIPT, + CAT.POINTER, + CAT.ENTERPRISE, + CAT.BYRON, ): if not SCHEMA_PAYMENT.match(address_params.address_n): credential.is_unusual_path = True elif address_type in ( - CardanoAddressType.BASE_SCRIPT_KEY, - CardanoAddressType.BASE_SCRIPT_SCRIPT, - CardanoAddressType.POINTER_SCRIPT, - CardanoAddressType.ENTERPRISE_SCRIPT, + CAT.BASE_SCRIPT_KEY, + CAT.BASE_SCRIPT_SCRIPT, + CAT.POINTER_SCRIPT, + CAT.ENTERPRISE_SCRIPT, ): credential.is_other_warning = True elif address_type in ( - CardanoAddressType.REWARD, - CardanoAddressType.REWARD_SCRIPT, + CAT.REWARD, + CAT.REWARD_SCRIPT, ): credential.is_reward = True @@ -99,55 +98,58 @@ def payment_credential( def stake_credential( cls, address_params: messages.CardanoAddressParametersType ) -> "Credential": - address_type = address_params.address_type + from .paths import SCHEMA_STAKING + + address_n_staking = address_params.address_n_staking # local_cache_attribute + address_type = address_params.address_type # local_cache_attribute + CAT = CardanoAddressType # local_cache_global + credential = cls( type_name=CREDENTIAL_TYPE_STAKE, address_type=address_type, - path=address_params.address_n_staking, + path=address_n_staking, key_hash=address_params.staking_key_hash, script_hash=address_params.script_staking_hash, pointer=address_params.certificate_pointer, ) - if address_type == CardanoAddressType.BASE: + if address_type == CAT.BASE: if address_params.staking_key_hash: credential.is_other_warning = True else: - if not SCHEMA_STAKING.match(address_params.address_n_staking): + if not SCHEMA_STAKING.match(address_n_staking): credential.is_unusual_path = True if not _do_base_address_credentials_match( address_params.address_n, - address_params.address_n_staking, + address_n_staking, ): credential.is_mismatch = True - elif address_type == CardanoAddressType.BASE_SCRIPT_KEY: - if address_params.address_n_staking and not SCHEMA_STAKING.match( - address_params.address_n_staking - ): + elif address_type == CAT.BASE_SCRIPT_KEY: + if address_n_staking and not SCHEMA_STAKING.match(address_n_staking): credential.is_unusual_path = True elif address_type in ( - CardanoAddressType.POINTER, - CardanoAddressType.POINTER_SCRIPT, + CAT.POINTER, + CAT.POINTER_SCRIPT, ): credential.is_other_warning = True - elif address_type == CardanoAddressType.REWARD: - if not SCHEMA_STAKING.match(address_params.address_n_staking): + elif address_type == CAT.REWARD: + if not SCHEMA_STAKING.match(address_n_staking): credential.is_unusual_path = True elif address_type in ( - CardanoAddressType.BASE_KEY_SCRIPT, - CardanoAddressType.BASE_SCRIPT_SCRIPT, - CardanoAddressType.REWARD_SCRIPT, + CAT.BASE_KEY_SCRIPT, + CAT.BASE_SCRIPT_SCRIPT, + CAT.REWARD_SCRIPT, ): credential.is_other_warning = True elif address_type in ( - CardanoAddressType.ENTERPRISE, - CardanoAddressType.ENTERPRISE_SCRIPT, - CardanoAddressType.BYRON, + CAT.ENTERPRISE, + CAT.ENTERPRISE_SCRIPT, + CAT.BYRON, ): credential.is_no_staking = True @@ -156,17 +158,6 @@ def stake_credential( return credential - def should_warn(self) -> bool: - return any( - ( - self.is_reward, - self.is_no_staking, - self.is_mismatch, - self.is_unusual_path, - self.is_other_warning, - ) - ) - def is_set(self) -> bool: return any((self.path, self.key_hash, self.script_hash, self.pointer)) @@ -183,6 +174,11 @@ def get_title(self) -> str: return "" def format(self) -> list[PropertyType]: + from ...common.paths import address_n_to_str + from . import bech32 + + pointer = self.pointer # local_cache_attribute + if self.path: return [(None, address_n_to_str(self.path))] elif self.key_hash: @@ -194,11 +190,11 @@ def format(self) -> list[PropertyType]: return [(None, bech32.encode(hrp, self.key_hash))] elif self.script_hash: return [(None, bech32.encode(bech32.HRP_SCRIPT_HASH, self.script_hash))] - elif self.pointer: + elif pointer: return [ - (f"Block: {self.pointer.block_index}", None), - (f"Transaction: {self.pointer.tx_index}", None), - (f"Certificate: {self.pointer.certificate_index}", None), + (f"Block: {pointer.block_index}", None), + (f"Transaction: {pointer.tx_index}", None), + (f"Certificate: {pointer.certificate_index}", None), ] else: return [] @@ -221,8 +217,10 @@ def _do_base_address_credentials_match( address_n: list[int], address_n_staking: list[int], ) -> bool: - return address_n_staking == _path_to_staking_path(address_n) - + from .paths import CHAIN_STAKING_KEY + from .utils import to_account_path -def _path_to_staking_path(path: list[int]) -> list[int]: - return to_account_path(path) + [CHAIN_STAKING_KEY, 0] + # Note: This checks that the account matches and the staking path address_index is 0. + # (Even though other values are allowed, we want to display them to the user.) + path_to_staking_path = to_account_path(address_n) + [CHAIN_STAKING_KEY, 0] + return address_n_staking == path_to_staking_path diff --git a/core/src/apps/cardano/helpers/hash_builder_collection.py b/core/src/apps/cardano/helpers/hash_builder_collection.py index c846ed488a..22b6d238e1 100644 --- a/core/src/apps/cardano/helpers/hash_builder_collection.py +++ b/core/src/apps/cardano/helpers/hash_builder_collection.py @@ -4,6 +4,7 @@ if TYPE_CHECKING: from typing import Any, Generic, TypeVar + from trezor import wire from trezor.utils import HashContext @@ -22,7 +23,7 @@ def __init__(self, size: int) -> None: self.size = size self.remaining = size self.hash_fn: HashContext | None = None - self.parent: "HashBuilderCollection" | None = None + self.parent: "HashBuilderCollection | None" = None self.has_unfinished_child = False def start(self, hash_fn: HashContext) -> "HashBuilderCollection": @@ -89,6 +90,19 @@ def _header_bytes(self) -> bytes: return cbor.create_array_header(self.size) +class HashBuilderSet(HashBuilderList, Generic[T]): + def __init__(self, size: int, *, tagged: bool) -> None: + super().__init__(size) + self.tagged = tagged + + def _header_bytes(self) -> bytes: + return ( + cbor.create_tagged_set_header(self.size) + if self.tagged + else cbor.create_array_header(self.size) + ) + + class HashBuilderDict(HashBuilderCollection, Generic[K, V]): key_order_error: wire.ProcessError previous_encoded_key: bytes diff --git a/core/src/apps/cardano/helpers/paths.py b/core/src/apps/cardano/helpers/paths.py index a895c690fe..2725cb1f93 100644 --- a/core/src/apps/cardano/helpers/paths.py +++ b/core/src/apps/cardano/helpers/paths.py @@ -1,6 +1,6 @@ from micropython import const -from apps.common.paths import HARDENED, PathSchema +from apps.common.paths import HARDENED, PathSchema, unharden # noqa: F401 _SLIP44_ID = const(1815) @@ -14,21 +14,14 @@ # minting has a specific schema for key derivation - see CIP-1855 SCHEMA_MINT = PathSchema.parse(f"m/1855'/coin_type'/[0-{HARDENED - 1}]'", _SLIP44_ID) SCHEMA_PAYMENT = PathSchema.parse("m/[44,1852]'/coin_type'/account'/[0,1]/address_index", _SLIP44_ID) -# staking is only allowed on Shelley paths with suffix /2/0 -SCHEMA_STAKING = PathSchema.parse("m/1852'/coin_type'/account'/2/0", _SLIP44_ID) -SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse(f"m/1852'/coin_type'/[0-{HARDENED - 1}]'/2/0", _SLIP44_ID) + +SCHEMA_STAKING = PathSchema.parse("m/1852'/coin_type'/account'/2/address_index", _SLIP44_ID) +SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse(f"m/1852'/coin_type'/[0-{HARDENED - 1}]'/2/address_index", _SLIP44_ID) # fmt: on ACCOUNT_PATH_INDEX = const(2) ACCOUNT_PATH_LENGTH = const(3) CHAIN_STAKING_KEY = const(2) -CHANGE_OUTPUT_PATH_NAME = "Change output path" -CHANGE_OUTPUT_STAKING_PATH_NAME = "Change output staking path" -CERTIFICATE_PATH_NAME = "Certificate path" -POOL_OWNER_STAKING_PATH_NAME = "Pool owner staking path" -WITNESS_PATH_NAME = "Witness path" - - -def unharden(item: int) -> int: - return item ^ (item & HARDENED) +ADDRESS_INDEX_PATH_INDEX = const(4) +RECOMMENDED_ADDRESS_INDEX = const(0) # https://cips.cardano.org/cips/cip11/ diff --git a/core/src/apps/cardano/helpers/protocol_magics.py b/core/src/apps/cardano/helpers/protocol_magics.py index e0a30fc9bc..513c73b986 100644 --- a/core/src/apps/cardano/helpers/protocol_magics.py +++ b/core/src/apps/cardano/helpers/protocol_magics.py @@ -1,8 +1,10 @@ +from micropython import const + # https://book.world.dev.cardano.org/environments.html -MAINNET = 764824073 -TESTNET_PREPROD = 1 -TESTNET_PREVIEW = 2 -TESTNET_LEGACY = 1097911063 +MAINNET = const(764824073) +TESTNET_PREPROD = const(1) +TESTNET_PREVIEW = const(2) +TESTNET_LEGACY = const(1097911063) NAMES = { MAINNET: "Mainnet", diff --git a/core/src/apps/cardano/helpers/utils.py b/core/src/apps/cardano/helpers/utils.py index c2f51d4686..69229ecf3e 100644 --- a/core/src/apps/cardano/helpers/utils.py +++ b/core/src/apps/cardano/helpers/utils.py @@ -1,21 +1,13 @@ from typing import TYPE_CHECKING -from trezor import wire from trezor.crypto import hashlib -from apps.common.seed import remove_ed25519_prefix - -from . import ( - ADDRESS_KEY_HASH_SIZE, - SCRIPT_HASH_SIZE, - bech32, - network_ids, - protocol_magics, -) -from .paths import ACCOUNT_PATH_INDEX, SCHEMA_STAKING_ANY_ACCOUNT, unharden +from . import ADDRESS_KEY_HASH_SIZE, bech32 +from .paths import ACCOUNT_PATH_INDEX if TYPE_CHECKING: from .. import seed + from trezor import wire def variable_length_encode(number: int) -> bytes: @@ -40,6 +32,8 @@ def to_account_path(path: list[int]) -> list[int]: def format_account_number(path: list[int]) -> str: + from .paths import unharden + if len(path) <= ACCOUNT_PATH_INDEX: raise ValueError("Path is too short.") @@ -76,6 +70,8 @@ def get_public_key_hash(keychain: seed.Keychain, path: list[int]) -> bytes: def derive_public_key( keychain: seed.Keychain, path: list[int], extended: bool = False ) -> bytes: + from apps.common.seed import remove_ed25519_prefix + node = keychain.derive(path) public_key = remove_ed25519_prefix(node.public_key()) return public_key if not extended else public_key + node.chain_code() @@ -87,6 +83,9 @@ def validate_stake_credential( key_hash: bytes | None, error: wire.ProcessError, ) -> None: + from . import SCRIPT_HASH_SIZE + from .paths import SCHEMA_STAKING_ANY_ACCOUNT + if sum(bool(k) for k in (path, script_hash, key_hash)) != 1: raise error @@ -104,6 +103,10 @@ def validate_network_info(network_id: int, protocol_magic: int) -> None: belong to the mainnet or that both belong to a testnet. We don't need to check for consistency between various testnets (at least for now). """ + from trezor import wire + + from . import network_ids, protocol_magics + is_mainnet_network_id = network_ids.is_mainnet(network_id) is_mainnet_protocol_magic = protocol_magics.is_mainnet(protocol_magic) diff --git a/core/src/apps/cardano/layout.py b/core/src/apps/cardano/layout.py index 07a762bf06..50d0c61f2c 100644 --- a/core/src/apps/cardano/layout.py +++ b/core/src/apps/cardano/layout.py @@ -1,27 +1,22 @@ -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING from trezor import messages from trezor.enums import ( ButtonRequestType, CardanoAddressType, CardanoCertificateType, + CardanoDRepType, + CardanoNativeScriptHashDisplayFormat, CardanoNativeScriptType, ) from trezor.lvglui.i18n import gettext as _, keys as i18n_keys from trezor.strings import format_amount -from trezor.ui.layouts import ( - confirm_blob, - confirm_metadata, - confirm_output, - confirm_path_warning, - confirm_properties, - confirm_text, - show_address, -) +from trezor.ui import layouts +from trezor.ui.layouts import confirm_metadata, confirm_output, confirm_properties from apps.common.paths import address_n_to_str -from . import addresses, seed +from . import addresses from .helpers import bech32, protocol_magics from .helpers.utils import ( format_account_number, @@ -31,15 +26,13 @@ ) if TYPE_CHECKING: - from trezor import wire + from typing import Literal - from trezor.wire import Context - from trezor.enums import CardanoNativeScriptHashDisplayFormat from trezor.ui.layouts.lvgl import PropertyType from .helpers.credential import Credential from .seed import Keychain - + from trezor import wire ADDRESS_TYPE_NAMES = { CardanoAddressType.BYRON: "Legacy", @@ -66,15 +59,20 @@ CERTIFICATE_TYPE_NAMES = { CardanoCertificateType.STAKE_REGISTRATION: "Stake key registration", + CardanoCertificateType.STAKE_REGISTRATION_CONWAY: "Stake key registration", CardanoCertificateType.STAKE_DEREGISTRATION: "Stake key deregistration", + CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY: "Stake key deregistration", CardanoCertificateType.STAKE_DELEGATION: "Stake delegation", CardanoCertificateType.STAKE_POOL_REGISTRATION: "Stakepool registration", + CardanoCertificateType.VOTE_DELEGATION: "Vote delegation", } TITLE = _(i18n_keys.TITLE__CONFIRM_TRANSACTION) BRT_Other = ButtonRequestType.Other # global_import_cache +CVOTE_REWARD_ELIGIBILITY_WARNING = "Reward eligibility warning" + def format_coin_amount(amount: int, network_id: int) -> str: from .helpers import network_ids @@ -157,11 +155,10 @@ async def show_native_script( async def show_script_hash( - ctx: Context, + ctx: wire.Context, script_hash: bytes, display_format: CardanoNativeScriptHashDisplayFormat, ) -> None: - from trezor.enums import CardanoNativeScriptHashDisplayFormat assert display_format in ( CardanoNativeScriptHashDisplayFormat.BECH32, @@ -177,7 +174,7 @@ async def show_script_hash( br_code=BRT_Other, ) elif display_format == CardanoNativeScriptHashDisplayFormat.POLICY_ID: - await confirm_blob( + await layouts.confirm_blob( ctx, "verify_script", "Verify script", @@ -187,7 +184,7 @@ async def show_script_hash( ) -async def show_tx_init(ctx: Context, title: str) -> bool: +async def show_tx_init(ctx: wire.Context, title: str) -> bool: # should_show_details = await should_show_more( # ctx, # TITLE, @@ -220,11 +217,12 @@ async def confirm_input(ctx: wire.Context, input: messages.CardanoTxInput) -> No async def confirm_sending( - ctx: Context, + ctx: wire.Context, ada_amount: int, to: str, output_type: Literal["address", "change", "collateral-return"], network_id: int, + chunkify: bool = False, ) -> None: if output_type not in ("address", "change", "collateral-return"): raise RuntimeError # should be unreachable @@ -262,7 +260,7 @@ async def confirm_sending_token( ) -async def confirm_datum_hash(ctx: Context, datum_hash: bytes) -> None: +async def confirm_datum_hash(ctx: wire.Context, datum_hash: bytes) -> None: await confirm_properties( ctx, "confirm_datum_hash", @@ -330,41 +328,66 @@ async def show_credentials( stake_credential: Credential, ) -> None: intro_text = "Address" - await _show_credential(ctx, payment_credential, intro_text, is_output=False) - await _show_credential(ctx, stake_credential, intro_text, is_output=False) + await _show_credential(ctx, payment_credential, intro_text, purpose="address") + await _show_credential(ctx, stake_credential, intro_text, purpose="address") async def show_change_output_credentials( - ctx: Context, + ctx: wire.Context, payment_credential: Credential, stake_credential: Credential, ) -> None: intro_text = "The following address is a change address. Its" - await _show_credential(ctx, payment_credential, intro_text, is_output=True) - await _show_credential(ctx, stake_credential, intro_text, is_output=True) + await _show_credential(ctx, payment_credential, intro_text, purpose="output") + await _show_credential(ctx, stake_credential, intro_text, purpose="output") async def show_device_owned_output_credentials( - ctx: Context, + ctx: wire.Context, payment_credential: Credential, stake_credential: Credential, show_both_credentials: bool, ) -> None: intro_text = "The following address is owned by this device. Its" - await _show_credential(ctx, payment_credential, intro_text, is_output=True) + await _show_credential(ctx, payment_credential, intro_text, purpose="output") if show_both_credentials: - await _show_credential(ctx, stake_credential, intro_text, is_output=True) + await _show_credential(ctx, stake_credential, intro_text, purpose="output") + + +async def show_cvote_registration_payment_credentials( + ctx: wire.Context, + payment_credential: Credential, + stake_credential: Credential, + show_both_credentials: bool, + show_payment_warning: bool, +) -> None: + intro_text = "Registration Payment" + await _show_credential( + ctx, payment_credential, intro_text, purpose="cvote_reg_payment_address" + ) + if show_both_credentials or show_payment_warning: + extra_text = CVOTE_REWARD_ELIGIBILITY_WARNING if show_payment_warning else None + await _show_credential( + ctx, + stake_credential, + intro_text, + purpose="cvote_reg_payment_address", + extra_text=extra_text, + ) async def _show_credential( - ctx: Context, + ctx: wire.Context, credential: Credential, intro_text: str, - is_output: bool, + purpose: Literal["address", "output", "cvote_reg_payment_address"], + extra_text: str | None = None, ) -> None: - title = ( - TITLE if is_output else f"{ADDRESS_TYPE_NAMES[credential.address_type]} address" - ) + title = { + "address": f"{ADDRESS_TYPE_NAMES[credential.address_type]} address", + "output": TITLE, + "cvote_reg_payment_address": TITLE, + }[purpose] props: list[PropertyType] = [] append = props.append # local_cache_attribute @@ -374,6 +397,7 @@ async def _show_credential( # show some of the "props". if credential.is_set(): credential_title = credential.get_title() + # TODO: handle translation append( ( f"{intro_text} {credential.type_name} credential is a {credential_title}:", @@ -386,7 +410,8 @@ async def _show_credential( append((None, "Path is unusual.")) if credential.is_mismatch: append((None, "Credential doesn't match payment credential.")) - if credential.is_reward: + if credential.is_reward and purpose != "cvote_reg_payment_address": + # for cvote registrations, this is handled by extra_text at the end append(("Address is a reward address.", None)) if credential.is_no_staking: append( @@ -396,21 +421,25 @@ async def _show_credential( ) ) - await confirm_properties( - ctx, - "confirm_credential", - title, - props, - br_code=BRT_Other, - ) + if extra_text: + append((extra_text, None)) + if len(props) > 0: + await confirm_properties( + ctx, + "confirm_credential", + title, + props, + br_code=BRT_Other, + ) -async def warn_path(ctx: Context, path: list[int], title: str) -> None: - await confirm_path_warning(ctx, address_n_to_str(path), path_type=title) + +async def warn_path(ctx: wire.Context, path: list[int], title: str) -> None: + await layouts.confirm_path_warning(ctx, address_n_to_str(path), path_type=title) async def warn_tx_output_contains_tokens( - ctx: Context, is_collateral_return: bool = False + ctx: wire.Context, is_collateral_return: bool = False ) -> None: content = ( "The collateral return\noutput contains tokens." @@ -426,7 +455,7 @@ async def warn_tx_output_contains_tokens( ) -async def warn_tx_contains_mint(ctx: Context) -> None: +async def warn_tx_contains_mint(ctx: wire.Context) -> None: await confirm_metadata( ctx, "confirm_tokens", @@ -436,7 +465,7 @@ async def warn_tx_contains_mint(ctx: Context) -> None: ) -async def warn_tx_output_no_datum(ctx: Context) -> None: +async def warn_tx_output_no_datum(ctx: wire.Context) -> None: await confirm_metadata( ctx, "confirm_no_datum_hash", @@ -446,7 +475,7 @@ async def warn_tx_output_no_datum(ctx: Context) -> None: ) -async def warn_no_script_data_hash(ctx: Context) -> None: +async def warn_no_script_data_hash(ctx: wire.Context) -> None: await confirm_metadata( ctx, "confirm_no_script_data_hash", @@ -477,9 +506,11 @@ async def warn_unknown_total_collateral(ctx: wire.Context) -> None: async def confirm_witness_request( - ctx: Context, + ctx: wire.Context, witness_path: list[int], ) -> None: + from . import seed + if seed.is_multisig_path(witness_path): path_title = "multi-sig path" elif seed.is_minting_path(witness_path): @@ -487,7 +518,7 @@ async def confirm_witness_request( else: path_title = "path" - await confirm_text( + await layouts.confirm_text( ctx, "confirm_total", TITLE, @@ -539,7 +570,9 @@ async def confirm_tx( async def confirm_certificate( - ctx: wire.Context, certificate: messages.CardanoTxCertificate + ctx: wire.Context, + certificate: messages.CardanoTxCertificate, + network_id: int, ) -> None: # stake pool registration requires custom confirmation logic not covered # in this call @@ -552,7 +585,7 @@ async def confirm_certificate( elif certificate.type == CardanoCertificateType.STAKE_DELEGATION: transaction_type_value = _(i18n_keys.LIST_VALUE__STAKE_DELEGATION) else: - transaction_type_value = _(i18n_keys.LIST_VALUE__STAKEPOOL_REGISTRATION) + transaction_type_value = CERTIFICATE_TYPE_NAMES[certificate.type] props: list[PropertyType] = [ (_(i18n_keys.LIST_KEY__TRANSACTION_TYPE__COLON), transaction_type_value), @@ -569,6 +602,21 @@ async def confirm_certificate( format_stake_pool_id(certificate.pool), ) ) + elif certificate.type in ( + CardanoCertificateType.STAKE_REGISTRATION_CONWAY, + CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, + ): + assert certificate.deposit is not None # validate_certificate + props.append( + ( + _(i18n_keys.LIST_VALUE__DEPOSIT) + ":", + format_coin_amount(certificate.deposit, network_id), + ) + ) + + elif certificate.type == CardanoCertificateType.VOTE_DELEGATION: + assert certificate.drep is not None # validate_certificate + props.append(_format_drep(certificate.drep)) await confirm_properties( ctx, @@ -687,7 +735,7 @@ async def confirm_stake_pool_metadata( async def confirm_stake_pool_registration_final( - ctx: Context, + ctx: wire.Context, protocol_magic: int, ttl: int | None, validity_interval_start: int | None, @@ -726,7 +774,12 @@ async def confirm_withdrawal( ) ) - props.append(("Amount:", format_coin_amount(withdrawal.amount, network_id))) + props.append( + ( + _(i18n_keys.LIST_KEY__AMOUNT__COLON), + format_coin_amount(withdrawal.amount, network_id), + ) + ) await confirm_properties( ctx, @@ -740,12 +793,19 @@ async def confirm_withdrawal( def _format_stake_credential( path: list[int], script_hash: bytes | None, key_hash: bytes | None ) -> tuple[str, str]: - from .helpers.utils import to_account_path + from .helpers.paths import ADDRESS_INDEX_PATH_INDEX, RECOMMENDED_ADDRESS_INDEX if path: + account_number = format_account_number(path) + address_index = path[ADDRESS_INDEX_PATH_INDEX] + if address_index == RECOMMENDED_ADDRESS_INDEX: + return ( + f"for account {account_number}:", + address_n_to_str(path), + ) return ( - _(i18n_keys.LIST_KEY__ACCOUNT__COLON), - address_n_to_str(to_account_path(path)), + f"for account {account_number} and index {address_index}:", + address_n_to_str(path), ) elif key_hash: return ("for key hash:", bech32.encode(bech32.HRP_STAKE_KEY_HASH, key_hash)) @@ -756,13 +816,35 @@ def _format_stake_credential( raise ValueError -async def confirm_governance_registration_delegation( +def _format_drep(drep: messages.CardanoDRep) -> tuple[str, str]: + if drep.type == CardanoDRepType.KEY_HASH: + assert drep.key_hash is not None # validate_drep + return ( + "Delegating to key hash:", + bech32.encode(bech32.HRP_DREP_KEY_HASH, drep.key_hash), + ) + elif drep.type == CardanoDRepType.SCRIPT_HASH: + assert drep.script_hash is not None # validate_drep + return ( + "Delegating to script:", + bech32.encode(bech32.HRP_DREP_SCRIPT_HASH, drep.script_hash), + ) + elif drep.type == CardanoDRepType.ABSTAIN: + return ("Delegating to:", "Always Abstain") + elif drep.type == CardanoDRepType.NO_CONFIDENCE: + return ("Delegating to:", "Always No Confidence") + else: + # should be unreachable unless there's a bug in validation + raise ValueError + + +async def confirm_cvote_registration_delegation( ctx: wire.Context, public_key: str, weight: int, ) -> None: props: list[PropertyType] = [ - ("Governance voting key registration", None), + ("Vote key registration (CIP-36)", None), ("Delegating to:", public_key), ] if weight is not None: @@ -770,31 +852,49 @@ async def confirm_governance_registration_delegation( await confirm_properties( ctx, - "confirm_governance_registration_delegation", + "confirm_cvote_registration_delegation", + title=TITLE, + props=props, + br_code=ButtonRequestType.Other, + ) + + +async def confirm_cvote_registration_payment_address( + ctx: wire.Context, + payment_address: str, + should_show_payment_warning: bool, +) -> None: + props = [ + ("Vote key registration (CIP-36)", None), + ("Rewards go to:", payment_address), + ] + if should_show_payment_warning: + props.append((CVOTE_REWARD_ELIGIBILITY_WARNING, None)) + await confirm_properties( + ctx, + "confirm_cvote_registration_payment_address", title=TITLE, props=props, br_code=ButtonRequestType.Other, ) -async def confirm_governance_registration( +async def confirm_cvote_registration( ctx: wire.Context, - public_key: str | None, + vote_public_key: str | None, staking_path: list[int], - reward_address: str, nonce: int, voting_purpose: int | None, ) -> None: - props: list[PropertyType] = [("Governance voting key registration", None)] - if public_key is not None: - props.append(("Voting public key:", public_key)) + props: list[PropertyType] = [("Vote key registration (CIP-36)", None)] + if vote_public_key is not None: + props.append(("Vote public key:", vote_public_key)) props.extend( [ ( - f"Staking key for account {format_account_number(staking_path)}:", + f"Staking key for account: {format_account_number(staking_path)}:", address_n_to_str(staking_path), ), - ("Rewards go to:", reward_address), ("Nonce:", str(nonce)), ] ) @@ -802,20 +902,22 @@ async def confirm_governance_registration( props.append( ( "Voting purpose:", - "Catalyst" if voting_purpose == 0 else f"{voting_purpose} (other)", + ("Catalyst" if voting_purpose == 0 else f"{voting_purpose} (Other)"), ) ) await confirm_properties( ctx, - "confirm_governance_registration", + "confirm_cvote_registration", title=TITLE, props=props, br_code=ButtonRequestType.Other, ) -async def show_auxiliary_data_hash(ctx: Context, auxiliary_data_hash: bytes) -> None: +async def show_auxiliary_data_hash( + ctx: wire.Context, auxiliary_data_hash: bytes +) -> None: await confirm_properties( ctx, "confirm_auxiliary_data", @@ -842,7 +944,9 @@ async def confirm_token_minting( ), ), ( - "Amount minted:" if token.mint_amount >= 0 else "Amount burned:", + "Amount minted (decimals unknown):" + if token.mint_amount >= 0 + else "Amount burned (decimals unknown):", format_amount(token.mint_amount, 0), ), ), @@ -860,7 +964,7 @@ async def warn_tx_network_unverifiable(ctx: wire.Context) -> None: ) -async def confirm_script_data_hash(ctx: Context, script_data_hash: bytes) -> None: +async def confirm_script_data_hash(ctx: wire.Context, script_data_hash: bytes) -> None: await confirm_properties( ctx, "confirm_script_data_hash", @@ -931,6 +1035,7 @@ async def show_cardano_address( address_parameters: messages.CardanoAddressParametersType, address: str, protocol_magic: int, + chunkify: bool, ) -> None: CAT = CardanoAddressType # local_cache_global @@ -939,8 +1044,7 @@ async def show_cardano_address( network_name = protocol_magics.to_ui_string(protocol_magic) title = f"{ADDRESS_TYPE_NAMES[address_parameters.address_type]} address" - address_extra = None - title_qr = title + path = None if address_parameters.address_type in ( CAT.BYRON, CAT.BASE, @@ -950,17 +1054,14 @@ async def show_cardano_address( CAT.REWARD, ): if address_parameters.address_n: - address_extra = address_n_to_str(address_parameters.address_n) - title_qr = address_n_to_str(address_parameters.address_n) + path = address_n_to_str(address_parameters.address_n) elif address_parameters.address_n_staking: - address_extra = address_n_to_str(address_parameters.address_n_staking) - title_qr = address_n_to_str(address_parameters.address_n_staking) + path = address_n_to_str(address_parameters.address_n_staking) - await show_address( + await layouts.show_address( ctx, address, network=network_name, - address_n=address_extra, - title_qr=title_qr, + address_n=path, addr_type=title, ) diff --git a/core/src/apps/cardano/native_script.py b/core/src/apps/cardano/native_script.py index ad4d39bf2b..815601716b 100644 --- a/core/src/apps/cardano/native_script.py +++ b/core/src/apps/cardano/native_script.py @@ -1,105 +1,111 @@ from typing import TYPE_CHECKING -from trezor import messages, wire -from trezor.crypto import hashlib from trezor.enums import CardanoNativeScriptType - -from apps.common import cbor +from trezor.wire import ProcessError from . import seed -from .helpers import ADDRESS_KEY_HASH_SIZE, SCRIPT_HASH_SIZE -from .helpers.paths import SCHEMA_MINT -from .helpers.utils import get_public_key_hash if TYPE_CHECKING: from typing import Any + from trezor import messages + from apps.common.cbor import CborSequence def validate_native_script(script: messages.CardanoNativeScript | None) -> None: - INVALID_NATIVE_SCRIPT = wire.ProcessError("Invalid native script") + from .helpers import ADDRESS_KEY_HASH_SIZE + from .helpers.paths import SCHEMA_MINT + + INVALID_NATIVE_SCRIPT = ProcessError("Invalid native script") if not script: raise INVALID_NATIVE_SCRIPT _validate_native_script_structure(script) + script_type = script.type # local_cache_attribute + key_path = script.key_path # local_cache_attribute + scripts = script.scripts # local_cache_attribute + CNST = CardanoNativeScriptType # local_cache_global - if script.type == CardanoNativeScriptType.PUB_KEY: - if script.key_hash and script.key_path: + if script_type == CNST.PUB_KEY: + if script.key_hash and key_path: raise INVALID_NATIVE_SCRIPT if script.key_hash: if len(script.key_hash) != ADDRESS_KEY_HASH_SIZE: raise INVALID_NATIVE_SCRIPT - elif script.key_path: - is_minting = SCHEMA_MINT.match(script.key_path) - if not seed.is_multisig_path(script.key_path) and not is_minting: + elif key_path: + is_minting = SCHEMA_MINT.match(key_path) + if not seed.is_multisig_path(key_path) and not is_minting: raise INVALID_NATIVE_SCRIPT else: raise INVALID_NATIVE_SCRIPT - elif script.type == CardanoNativeScriptType.ALL: - for sub_script in script.scripts: + elif script_type == CNST.ALL: + for sub_script in scripts: validate_native_script(sub_script) - elif script.type == CardanoNativeScriptType.ANY: - for sub_script in script.scripts: + elif script_type == CNST.ANY: + for sub_script in scripts: validate_native_script(sub_script) - elif script.type == CardanoNativeScriptType.N_OF_K: + elif script_type == CNST.N_OF_K: if script.required_signatures_count is None: raise INVALID_NATIVE_SCRIPT - if script.required_signatures_count > len(script.scripts): + if script.required_signatures_count > len(scripts): raise INVALID_NATIVE_SCRIPT - for sub_script in script.scripts: + for sub_script in scripts: validate_native_script(sub_script) - elif script.type == CardanoNativeScriptType.INVALID_BEFORE: + elif script_type == CNST.INVALID_BEFORE: if script.invalid_before is None: raise INVALID_NATIVE_SCRIPT - elif script.type == CardanoNativeScriptType.INVALID_HEREAFTER: + elif script_type == CNST.INVALID_HEREAFTER: if script.invalid_hereafter is None: raise INVALID_NATIVE_SCRIPT def _validate_native_script_structure(script: messages.CardanoNativeScript) -> None: - key_hash = script.key_hash - key_path = script.key_path - scripts = script.scripts - required_signatures_count = script.required_signatures_count - invalid_before = script.invalid_before - invalid_hereafter = script.invalid_hereafter + key_hash = script.key_hash # local_cache_attribute + key_path = script.key_path # local_cache_attribute + scripts = script.scripts # local_cache_attribute + required_signatures_count = ( + script.required_signatures_count + ) # local_cache_attribute + invalid_before = script.invalid_before # local_cache_attribute + invalid_hereafter = script.invalid_hereafter # local_cache_attribute + CNST = CardanoNativeScriptType # local_cache_global fields_to_be_empty: dict[CardanoNativeScriptType, tuple[Any, ...]] = { - CardanoNativeScriptType.PUB_KEY: ( + CNST.PUB_KEY: ( scripts, required_signatures_count, invalid_before, invalid_hereafter, ), - CardanoNativeScriptType.ALL: ( + CNST.ALL: ( key_hash, key_path, required_signatures_count, invalid_before, invalid_hereafter, ), - CardanoNativeScriptType.ANY: ( + CNST.ANY: ( key_hash, key_path, required_signatures_count, invalid_before, invalid_hereafter, ), - CardanoNativeScriptType.N_OF_K: ( + CNST.N_OF_K: ( key_hash, key_path, invalid_before, invalid_hereafter, ), - CardanoNativeScriptType.INVALID_BEFORE: ( + CNST.INVALID_BEFORE: ( key_hash, key_path, required_signatures_count, invalid_hereafter, ), - CardanoNativeScriptType.INVALID_HEREAFTER: ( + CNST.INVALID_HEREAFTER: ( key_hash, key_path, required_signatures_count, @@ -108,12 +114,18 @@ def _validate_native_script_structure(script: messages.CardanoNativeScript) -> N } if script.type not in fields_to_be_empty or any(fields_to_be_empty[script.type]): - raise wire.ProcessError("Invalid native script") + raise ProcessError("Invalid native script") def get_native_script_hash( keychain: seed.Keychain, script: messages.CardanoNativeScript ) -> bytes: + from trezor.crypto import hashlib + + from apps.common import cbor + + from .helpers import SCRIPT_HASH_SIZE + script_cbor = cbor.encode(cborize_native_script(keychain, script)) prefixed_script_cbor = b"\00" + script_cbor return hashlib.blake2b(data=prefixed_script_cbor, outlen=SCRIPT_HASH_SIZE).digest() @@ -122,29 +134,34 @@ def get_native_script_hash( def cborize_native_script( keychain: seed.Keychain, script: messages.CardanoNativeScript ) -> CborSequence: + from .helpers.utils import get_public_key_hash + + script_type = script.type # local_cache_attribute + CNST = CardanoNativeScriptType # local_cache_global + script_content: CborSequence - if script.type == CardanoNativeScriptType.PUB_KEY: + if script_type == CNST.PUB_KEY: if script.key_hash: script_content = (script.key_hash,) elif script.key_path: script_content = (get_public_key_hash(keychain, script.key_path),) else: - raise wire.ProcessError("Invalid native script") - elif script.type == CardanoNativeScriptType.ALL: + raise ProcessError("Invalid native script") + elif script_type == CNST.ALL: script_content = ( tuple( cborize_native_script(keychain, sub_script) for sub_script in script.scripts ), ) - elif script.type == CardanoNativeScriptType.ANY: + elif script_type == CNST.ANY: script_content = ( tuple( cborize_native_script(keychain, sub_script) for sub_script in script.scripts ), ) - elif script.type == CardanoNativeScriptType.N_OF_K: + elif script_type == CNST.N_OF_K: script_content = ( script.required_signatures_count, tuple( @@ -152,11 +169,11 @@ def cborize_native_script( for sub_script in script.scripts ), ) - elif script.type == CardanoNativeScriptType.INVALID_BEFORE: + elif script_type == CNST.INVALID_BEFORE: script_content = (script.invalid_before,) - elif script.type == CardanoNativeScriptType.INVALID_HEREAFTER: + elif script_type == CNST.INVALID_HEREAFTER: script_content = (script.invalid_hereafter,) else: raise RuntimeError # should be unreachable - return (script.type,) + script_content + return (script_type,) + script_content diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index c492d44fad..59df85e2f1 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -8,16 +8,16 @@ from apps.common import mnemonic from apps.common.seed import derive_and_store_roots, get_seed -from .helpers import paths +from .helpers.paths import BYRON_ROOT, MINTING_ROOT, MULTISIG_ROOT, SHELLEY_ROOT if TYPE_CHECKING: - from typing import Callable, Awaitable, TypeVar - - from apps.common.paths import Bip32Path - from apps.common.keychain import MsgOut, Handler + from typing import Awaitable, Callable, TypeVar from trezor import messages + from apps.common.keychain import Handler, MsgOut + from apps.common.paths import Bip32Path + CardanoMessages = ( messages.CardanoGetAddress | messages.CardanoGetPublicKey @@ -40,10 +40,10 @@ def __init__(self, root: bip32.HDNode) -> None: if utils.USE_THD89: self.master_root = root.clone() else: - self.byron_root = self._derive_path(root, paths.BYRON_ROOT) - self.shelley_root = self._derive_path(root, paths.SHELLEY_ROOT) - self.multisig_root = self._derive_path(root, paths.MULTISIG_ROOT) - self.minting_root = self._derive_path(root, paths.MINTING_ROOT) + self.byron_root = self._derive_path(root, BYRON_ROOT) + self.shelley_root = self._derive_path(root, SHELLEY_ROOT) + self.multisig_root = self._derive_path(root, MULTISIG_ROOT) + self.minting_root = self._derive_path(root, MINTING_ROOT) root.__del__() @staticmethod @@ -84,16 +84,16 @@ def derive(self, node_path: Bip32Path) -> bip32.HDNode: else: path_root = self._get_path_root(node_path) - # this is true now, so for simplicity we don't branch on path type - assert ( - len(paths.BYRON_ROOT) == len(paths.SHELLEY_ROOT) - and len(paths.MULTISIG_ROOT) == len(paths.SHELLEY_ROOT) - and len(paths.MINTING_ROOT) == len(paths.SHELLEY_ROOT) - ) - suffix = node_path[len(paths.SHELLEY_ROOT) :] + # this is true now, so for simplicity we don't branch on path type + assert ( + len(BYRON_ROOT) == len(SHELLEY_ROOT) + and len(MULTISIG_ROOT) == len(SHELLEY_ROOT) + and len(MINTING_ROOT) == len(SHELLEY_ROOT) + ) + suffix = node_path[len(SHELLEY_ROOT) :] - # derive child node from the root - return self._derive_path(path_root, suffix) + # derive child node from the root + return self._derive_path(path_root, suffix) # XXX the root node remains in session cache so we should not delete it # def __del__(self) -> None: @@ -101,19 +101,19 @@ def derive(self, node_path: Bip32Path) -> bip32.HDNode: def is_byron_path(path: Bip32Path) -> bool: - return path[: len(paths.BYRON_ROOT)] == paths.BYRON_ROOT + return path[: len(BYRON_ROOT)] == BYRON_ROOT def is_shelley_path(path: Bip32Path) -> bool: - return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT + return path[: len(SHELLEY_ROOT)] == SHELLEY_ROOT def is_multisig_path(path: Bip32Path) -> bool: - return path[: len(paths.MULTISIG_ROOT)] == paths.MULTISIG_ROOT + return path[: len(MULTISIG_ROOT)] == MULTISIG_ROOT def is_minting_path(path: Bip32Path) -> bool: - return path[: len(paths.MINTING_ROOT)] == paths.MINTING_ROOT + return path[: len(MINTING_ROOT)] == MINTING_ROOT def derive_and_store_secrets(passphrase: str) -> None: @@ -203,7 +203,7 @@ async def _get_keychain_bip39( return Keychain(root) -async def get_keychain( +async def _get_keychain( ctx: wire.Context, derivation_type: CardanoDerivationType ) -> Keychain: if mnemonic.is_bip39(): @@ -216,7 +216,7 @@ async def get_keychain( def with_keychain(func: HandlerWithKeychain[MsgIn, MsgOut]) -> Handler[MsgIn, MsgOut]: async def wrapper(ctx: wire.Context, msg: MsgIn) -> MsgOut: - keychain = await get_keychain(ctx, msg.derivation_type) + keychain = await _get_keychain(ctx, msg.derivation_type) return await func(ctx, msg, keychain) return wrapper diff --git a/core/src/apps/cardano/sign_tx/__init__.py b/core/src/apps/cardano/sign_tx/__init__.py index 5581526868..4f40a15051 100644 --- a/core/src/apps/cardano/sign_tx/__init__.py +++ b/core/src/apps/cardano/sign_tx/__init__.py @@ -1,35 +1,46 @@ -from typing import Type +from typing import TYPE_CHECKING -from trezor import log, messages, wire -from trezor.enums import CardanoTxSigningMode +from trezor import wire from .. import seed -from .signer import Signer + +if __debug__: + from trezor import log +if TYPE_CHECKING: + from typing import Type + + from trezor.messages import CardanoSignTxFinished, CardanoSignTxInit @seed.with_keychain async def sign_tx( - ctx: wire.Context, msg: messages.CardanoSignTxInit, keychain: seed.Keychain -) -> messages.CardanoSignTxFinished: - signer_type: Type[Signer] + ctx: wire.Context, msg: CardanoSignTxInit, keychain: seed.Keychain +) -> CardanoSignTxFinished: + from trezor.enums import CardanoTxSigningMode + from trezor.messages import CardanoSignTxFinished + + from .signer import Signer + + signing_mode = msg.signing_mode # local_cache_attribute from trezor.lvglui.scrs import lv from .. import ICON, PRIMARY_COLOR ctx.primary_color, ctx.icon_path = lv.color_hex(PRIMARY_COLOR), ICON - if msg.signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: + signer_type: Type[Signer] + if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: from .ordinary_signer import OrdinarySigner signer_type = OrdinarySigner - elif msg.signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: + elif signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: from .pool_owner_signer import PoolOwnerSigner signer_type = PoolOwnerSigner - elif msg.signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: + elif signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: from .multisig_signer import MultisigSigner signer_type = MultisigSigner - elif msg.signing_mode == CardanoTxSigningMode.PLUTUS_TRANSACTION: + elif signing_mode == CardanoTxSigningMode.PLUTUS_TRANSACTION: from .plutus_signer import PlutusSigner signer_type = PlutusSigner @@ -45,4 +56,4 @@ async def sign_tx( log.exception(__name__, e) raise wire.ProcessError("Signing failed") - return messages.CardanoSignTxFinished() + return CardanoSignTxFinished() diff --git a/core/src/apps/cardano/sign_tx/multisig_signer.py b/core/src/apps/cardano/sign_tx/multisig_signer.py index 2cd56f63ff..b57104e317 100644 --- a/core/src/apps/cardano/sign_tx/multisig_signer.py +++ b/core/src/apps/cardano/sign_tx/multisig_signer.py @@ -1,10 +1,12 @@ -from trezor import messages, wire -from trezor.enums import CardanoCertificateType +from typing import TYPE_CHECKING + +from trezor.wire import ProcessError -from .. import layout, seed -from ..helpers.paths import SCHEMA_MINT from .signer import Signer +if TYPE_CHECKING: + from trezor import messages + class MultisigSigner(Signer): """ @@ -14,23 +16,30 @@ class MultisigSigner(Signer): SIGNING_MODE_TITLE = "Confirming a multisig transaction" def _validate_tx_init(self) -> None: + msg = self.msg # local_cache_attribute + _assert_tx_init_cond = self._assert_tx_init_cond # local_cache_attribute + super()._validate_tx_init() - self._assert_tx_init_cond(self.msg.collateral_inputs_count == 0) - self._assert_tx_init_cond(not self.msg.has_collateral_return) - self._assert_tx_init_cond(self.msg.total_collateral is None) - self._assert_tx_init_cond(self.msg.reference_inputs_count == 0) + _assert_tx_init_cond(msg.collateral_inputs_count == 0) + _assert_tx_init_cond(not msg.has_collateral_return) + _assert_tx_init_cond(msg.total_collateral is None) + _assert_tx_init_cond(msg.reference_inputs_count == 0) async def _confirm_tx(self, tx_hash: bytes) -> None: + from .. import layout + + msg = self.msg # local_cache_attribute + # super() omitted intentionally is_network_id_verifiable = self._is_network_id_verifiable() await layout.confirm_tx( self.ctx, - self.msg.fee, - self.msg.network_id, - self.msg.protocol_magic, - self.msg.ttl, - self.msg.validity_interval_start, - self.msg.total_collateral, + msg.fee, + msg.network_id, + msg.protocol_magic, + msg.ttl, + msg.validity_interval_start, + msg.total_collateral, is_network_id_verifiable, tx_hash=None, ) @@ -38,23 +47,28 @@ async def _confirm_tx(self, tx_hash: bytes) -> None: def _validate_output(self, output: messages.CardanoTxOutput) -> None: super()._validate_output(output) if output.address_parameters is not None: - raise wire.ProcessError("Invalid output") + raise ProcessError("Invalid output") def _validate_certificate(self, certificate: messages.CardanoTxCertificate) -> None: + from trezor.enums import CardanoCertificateType + super()._validate_certificate(certificate) if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") if certificate.path or certificate.key_hash: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") def _validate_withdrawal(self, withdrawal: messages.CardanoTxWithdrawal) -> None: super()._validate_withdrawal(withdrawal) if withdrawal.path or withdrawal.key_hash: - raise wire.ProcessError("Invalid withdrawal") + raise ProcessError("Invalid withdrawal") def _validate_witness_request( self, witness_request: messages.CardanoTxWitnessRequest ) -> None: + from .. import seed + from ..helpers.paths import SCHEMA_MINT + super()._validate_witness_request(witness_request) is_minting = SCHEMA_MINT.match(witness_request.path) tx_has_token_minting = self.msg.minting_asset_groups_count > 0 @@ -63,4 +77,4 @@ def _validate_witness_request( seed.is_multisig_path(witness_request.path) or (is_minting and tx_has_token_minting) ): - raise wire.ProcessError("Invalid witness request") + raise ProcessError("Invalid witness request") diff --git a/core/src/apps/cardano/sign_tx/ordinary_signer.py b/core/src/apps/cardano/sign_tx/ordinary_signer.py index 6ecbd22462..d7a3cc17d0 100644 --- a/core/src/apps/cardano/sign_tx/ordinary_signer.py +++ b/core/src/apps/cardano/sign_tx/ordinary_signer.py @@ -1,15 +1,14 @@ -from trezor import messages, wire -from trezor.enums import CardanoCertificateType - -from .. import layout, seed -from ..helpers.paths import ( - SCHEMA_MINT, - SCHEMA_PAYMENT, - SCHEMA_STAKING, - WITNESS_PATH_NAME, -) +from typing import TYPE_CHECKING + +from trezor.wire import ProcessError + +from .. import layout +from ..helpers.paths import SCHEMA_MINT from .signer import Signer +if TYPE_CHECKING: + from trezor import messages + class OrdinarySigner(Signer): """ @@ -20,42 +19,51 @@ class OrdinarySigner(Signer): SIGNING_MODE_TITLE = "Confirming a transaction" def _validate_tx_init(self) -> None: + msg = self.msg # local_cache_attribute + _assert_tx_init_cond = self._assert_tx_init_cond # local_cache_attribute + super()._validate_tx_init() - self._assert_tx_init_cond(self.msg.collateral_inputs_count == 0) - self._assert_tx_init_cond(not self.msg.has_collateral_return) - self._assert_tx_init_cond(self.msg.total_collateral is None) - self._assert_tx_init_cond(self.msg.reference_inputs_count == 0) + _assert_tx_init_cond(msg.collateral_inputs_count == 0) + _assert_tx_init_cond(not msg.has_collateral_return) + _assert_tx_init_cond(msg.total_collateral is None) + _assert_tx_init_cond(msg.reference_inputs_count == 0) async def _confirm_tx(self, tx_hash: bytes) -> None: + msg = self.msg # local_cache_attribute + # super() omitted intentionally is_network_id_verifiable = self._is_network_id_verifiable() await layout.confirm_tx( self.ctx, - self.msg.fee, - self.msg.network_id, - self.msg.protocol_magic, - self.msg.ttl, - self.msg.validity_interval_start, - self.msg.total_collateral, + msg.fee, + msg.network_id, + msg.protocol_magic, + msg.ttl, + msg.validity_interval_start, + msg.total_collateral, is_network_id_verifiable, tx_hash=None, ) def _validate_certificate(self, certificate: messages.CardanoTxCertificate) -> None: + from trezor.enums import CardanoCertificateType + super()._validate_certificate(certificate) if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") if certificate.script_hash or certificate.key_hash: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") def _validate_withdrawal(self, withdrawal: messages.CardanoTxWithdrawal) -> None: super()._validate_withdrawal(withdrawal) if withdrawal.script_hash or withdrawal.key_hash: - raise wire.ProcessError("Invalid withdrawal") + raise ProcessError("Invalid withdrawal") def _validate_witness_request( self, witness_request: messages.CardanoTxWitnessRequest ) -> None: + from .. import seed + super()._validate_witness_request(witness_request) is_minting = SCHEMA_MINT.match(witness_request.path) tx_has_token_minting = self.msg.minting_asset_groups_count > 0 @@ -65,9 +73,11 @@ def _validate_witness_request( or seed.is_shelley_path(witness_request.path) or (is_minting and tx_has_token_minting) ): - raise wire.ProcessError("Invalid witness request") + raise ProcessError("Invalid witness request") async def _show_witness_request(self, witness_path: list[int]) -> None: + from ..helpers.paths import SCHEMA_PAYMENT, SCHEMA_STAKING + # super() omitted intentionally # We only allow payment, staking or minting paths. # If the path is an unusual payment or staking path, we either fail or show the @@ -80,7 +90,7 @@ async def _show_witness_request(self, witness_path: list[int]) -> None: if is_minting: await layout.confirm_witness_request(self.ctx, witness_path) elif not is_payment and not is_staking: - await self._fail_or_warn_path(witness_path, WITNESS_PATH_NAME) + await self._fail_or_warn_path(witness_path, "Witness path") else: await self._show_if_showing_details( layout.confirm_witness_request(self.ctx, witness_path) diff --git a/core/src/apps/cardano/sign_tx/plutus_signer.py b/core/src/apps/cardano/sign_tx/plutus_signer.py index 0c03ea6cf1..486ffb5ae3 100644 --- a/core/src/apps/cardano/sign_tx/plutus_signer.py +++ b/core/src/apps/cardano/sign_tx/plutus_signer.py @@ -1,11 +1,13 @@ -from trezor import messages, wire -from trezor.enums import CardanoCertificateType +from typing import TYPE_CHECKING -from .. import layout, seed -from ..helpers.credential import Credential, should_show_credentials -from ..helpers.paths import SCHEMA_MINT +from trezor import wire + +from .. import layout from .signer import Signer +if TYPE_CHECKING: + from trezor import messages + class PlutusSigner(Signer): """ @@ -28,6 +30,8 @@ async def _show_tx_init(self) -> None: await layout.warn_unknown_total_collateral(self.ctx) async def _confirm_tx(self, tx_hash: bytes) -> None: + msg = self.msg # local_cache_attribute + # super() omitted intentionally # We display tx hash so that experienced users can compare it to the tx hash # computed by a trusted device (in case the tx contains many items which are @@ -35,22 +39,16 @@ async def _confirm_tx(self, tx_hash: bytes) -> None: is_network_id_verifiable = self._is_network_id_verifiable() await layout.confirm_tx( self.ctx, - self.msg.fee, - self.msg.network_id, - self.msg.protocol_magic, - self.msg.ttl, - self.msg.validity_interval_start, - self.msg.total_collateral, + msg.fee, + msg.network_id, + msg.protocol_magic, + msg.ttl, + msg.validity_interval_start, + msg.total_collateral, is_network_id_verifiable, tx_hash, ) - def _should_show_tx_hash(self) -> bool: - # super() omitted intentionally - # Plutus txs tend to contain a lot of opaque data, some users might - # want to verify only the tx hash. - return True - async def _show_input(self, input: messages.CardanoTxInput) -> None: # super() omitted intentionally # The inputs are not interchangeable (because of datums), so we must show them. @@ -59,6 +57,8 @@ async def _show_input(self, input: messages.CardanoTxInput) -> None: async def _show_output_credentials( self, address_parameters: messages.CardanoAddressParametersType ) -> None: + from ..helpers.credential import Credential, should_show_credentials + # In ordinary txs, change outputs with matching payment and staking paths can be # hidden, but we need to show them in Plutus txs because of the script # evaluation. We at least hide the staking path if it matches the payment path. @@ -86,6 +86,8 @@ def _is_change_output(self, output: messages.CardanoTxOutput) -> bool: return False def _validate_certificate(self, certificate: messages.CardanoTxCertificate) -> None: + from trezor.enums import CardanoCertificateType + super()._validate_certificate(certificate) if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: raise wire.ProcessError("Invalid certificate") @@ -93,6 +95,9 @@ def _validate_certificate(self, certificate: messages.CardanoTxCertificate) -> N def _validate_witness_request( self, witness_request: messages.CardanoTxWitnessRequest ) -> None: + from .. import seed + from ..helpers.paths import SCHEMA_MINT + super()._validate_witness_request(witness_request) is_minting = SCHEMA_MINT.match(witness_request.path) diff --git a/core/src/apps/cardano/sign_tx/pool_owner_signer.py b/core/src/apps/cardano/sign_tx/pool_owner_signer.py index 8008a226e8..628d8f3d15 100644 --- a/core/src/apps/cardano/sign_tx/pool_owner_signer.py +++ b/core/src/apps/cardano/sign_tx/pool_owner_signer.py @@ -1,10 +1,12 @@ -from trezor import messages, wire -from trezor.enums import CardanoCertificateType +from typing import TYPE_CHECKING + +from trezor.wire import ProcessError -from .. import layout -from ..helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT from .signer import Signer +if TYPE_CHECKING: + from trezor import messages + class PoolOwnerSigner(Signer): """ @@ -22,18 +24,25 @@ class PoolOwnerSigner(Signer): SIGNING_MODE_TITLE = "Confirming pool registration as owner" def _validate_tx_init(self) -> None: + msg = self.msg # local_cache_attribute + super()._validate_tx_init() - self._assert_tx_init_cond(self.msg.certificates_count == 1) - self._assert_tx_init_cond(self.msg.withdrawals_count == 0) - self._assert_tx_init_cond(self.msg.minting_asset_groups_count == 0) - self._assert_tx_init_cond(self.msg.script_data_hash is None) - self._assert_tx_init_cond(self.msg.collateral_inputs_count == 0) - self._assert_tx_init_cond(self.msg.required_signers_count == 0) - self._assert_tx_init_cond(not self.msg.has_collateral_return) - self._assert_tx_init_cond(self.msg.total_collateral is None) - self._assert_tx_init_cond(self.msg.reference_inputs_count == 0) + for condition in ( + msg.certificates_count == 1, + msg.withdrawals_count == 0, + msg.minting_asset_groups_count == 0, + msg.script_data_hash is None, + msg.collateral_inputs_count == 0, + msg.required_signers_count == 0, + not msg.has_collateral_return, + msg.total_collateral is None, + msg.reference_inputs_count == 0, + ): + self._assert_tx_init_cond(condition) async def _confirm_tx(self, tx_hash: bytes) -> None: + from .. import layout + # super() omitted intentionally await layout.confirm_stake_pool_registration_final( self.ctx, @@ -50,7 +59,7 @@ def _validate_output(self, output: messages.CardanoTxOutput) -> None: or output.inline_datum_size > 0 or output.reference_script_size > 0 ): - raise wire.ProcessError("Invalid output") + raise ProcessError("Invalid output") def _should_show_output(self, output: messages.CardanoTxOutput) -> bool: # super() omitted intentionally @@ -58,17 +67,24 @@ def _should_show_output(self, output: messages.CardanoTxOutput) -> bool: return False def _validate_certificate(self, certificate: messages.CardanoTxCertificate) -> None: + from trezor.enums import CardanoCertificateType + super()._validate_certificate(certificate) if certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION: - raise wire.ProcessError("Invalid certificate") + raise ProcessError("Invalid certificate") def _validate_witness_request( self, witness_request: messages.CardanoTxWitnessRequest ) -> None: + from ..helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT + super()._validate_witness_request(witness_request) - if not SCHEMA_STAKING_ANY_ACCOUNT.match(witness_request.path): - raise wire.ProcessError( - "Stakepool registration transaction can only contain staking witnesses" + if not ( + SCHEMA_STAKING_ANY_ACCOUNT.match(witness_request.path) + and witness_request.path == self.pool_owner_path + ): + raise ProcessError( + "Stakepool registration transaction can only contain the pool owner witness request" ) def _is_network_id_verifiable(self) -> bool: diff --git a/core/src/apps/cardano/sign_tx/signer.py b/core/src/apps/cardano/sign_tx/signer.py index abf9e09884..8363528703 100644 --- a/core/src/apps/cardano/sign_tx/signer.py +++ b/core/src/apps/cardano/sign_tx/signer.py @@ -1,87 +1,72 @@ from micropython import const from typing import TYPE_CHECKING -from trezor import messages, wire -from trezor.crypto import hashlib -from trezor.crypto.curve import ed25519 +from trezor import messages from trezor.enums import ( - CardanoAddressType, CardanoCertificateType, CardanoTxOutputSerializationFormat, CardanoTxWitnessType, ) +from trezor.messages import CardanoTxItemAck, CardanoTxOutput from trezor.ui.layouts.lvgl import confirm_final +from trezor.wire import DataError, ProcessError -from apps.common import cbor, safety_checks +from apps.common import safety_checks -from .. import addresses, auxiliary_data, certificates, layout, seed -from ..helpers import ( - ADDRESS_KEY_HASH_SIZE, - INPUT_PREV_HASH_SIZE, - LOVELACE_MAX_SUPPLY, - OUTPUT_DATUM_HASH_SIZE, - SCRIPT_DATA_HASH_SIZE, -) -from ..helpers.account_path_check import AccountPathChecker -from ..helpers.credential import Credential, should_show_credentials +from .. import addresses, certificates, layout, seed +from ..helpers import INPUT_PREV_HASH_SIZE, LOVELACE_MAX_SUPPLY +from ..helpers.credential import Credential from ..helpers.hash_builder_collection import ( HashBuilderDict, - HashBuilderEmbeddedCBOR, HashBuilderList, + HashBuilderSet, ) -from ..helpers.paths import ( - CERTIFICATE_PATH_NAME, - CHANGE_OUTPUT_PATH_NAME, - CHANGE_OUTPUT_STAKING_PATH_NAME, - POOL_OWNER_STAKING_PATH_NAME, - SCHEMA_STAKING, -) -from ..helpers.utils import ( - derive_public_key, - get_public_key_hash, - validate_network_info, - validate_stake_credential, -) +from ..helpers.paths import SCHEMA_STAKING +from ..helpers.utils import derive_public_key if TYPE_CHECKING: from typing import Any, Awaitable, ClassVar + from trezor import wire + from trezor.enums import CardanoAddressType + + from apps.common import cbor from apps.common.paths import PathSchema - CardanoTxResponseType = ( - messages.CardanoTxItemAck | messages.CardanoTxWitnessResponse - ) + from ..helpers.hash_builder_collection import HashBuilderEmbeddedCBOR -MINTING_POLICY_ID_LENGTH = 28 -MAX_ASSET_NAME_LENGTH = 32 + CardanoTxResponseType = CardanoTxItemAck | messages.CardanoTxWitnessResponse -TX_BODY_KEY_INPUTS = const(0) -TX_BODY_KEY_OUTPUTS = const(1) -TX_BODY_KEY_FEE = const(2) -TX_BODY_KEY_TTL = const(3) -TX_BODY_KEY_CERTIFICATES = const(4) -TX_BODY_KEY_WITHDRAWALS = const(5) -TX_BODY_KEY_AUXILIARY_DATA = const(7) -TX_BODY_KEY_VALIDITY_INTERVAL_START = const(8) -TX_BODY_KEY_MINT = const(9) -TX_BODY_KEY_SCRIPT_DATA_HASH = const(11) -TX_BODY_KEY_COLLATERAL_INPUTS = const(13) -TX_BODY_KEY_REQUIRED_SIGNERS = const(14) -TX_BODY_KEY_NETWORK_ID = const(15) -TX_BODY_KEY_COLLATERAL_RETURN = const(16) -TX_BODY_KEY_TOTAL_COLLATERAL = const(17) -TX_BODY_KEY_REFERENCE_INPUTS = const(18) +_MINTING_POLICY_ID_LENGTH = const(28) +_MAX_ASSET_NAME_LENGTH = const(32) -BABBAGE_OUTPUT_KEY_ADDRESS = const(0) -BABBAGE_OUTPUT_KEY_AMOUNT = const(1) -BABBAGE_OUTPUT_KEY_DATUM_OPTION = const(2) -BABBAGE_OUTPUT_KEY_REFERENCE_SCRIPT = const(3) +_TX_BODY_KEY_INPUTS = const(0) +_TX_BODY_KEY_OUTPUTS = const(1) +_TX_BODY_KEY_FEE = const(2) +_TX_BODY_KEY_TTL = const(3) +_TX_BODY_KEY_CERTIFICATES = const(4) +_TX_BODY_KEY_WITHDRAWALS = const(5) +_TX_BODY_KEY_AUXILIARY_DATA = const(7) +_TX_BODY_KEY_VALIDITY_INTERVAL_START = const(8) +_TX_BODY_KEY_MINT = const(9) +_TX_BODY_KEY_SCRIPT_DATA_HASH = const(11) +_TX_BODY_KEY_COLLATERAL_INPUTS = const(13) +_TX_BODY_KEY_REQUIRED_SIGNERS = const(14) +_TX_BODY_KEY_NETWORK_ID = const(15) +_TX_BODY_KEY_COLLATERAL_RETURN = const(16) +_TX_BODY_KEY_TOTAL_COLLATERAL = const(17) +_TX_BODY_KEY_REFERENCE_INPUTS = const(18) -DATUM_OPTION_KEY_HASH = const(0) -DATUM_OPTION_KEY_INLINE = const(1) +_BABBAGE_OUTPUT_KEY_ADDRESS = const(0) +_BABBAGE_OUTPUT_KEY_AMOUNT = const(1) +_BABBAGE_OUTPUT_KEY_DATUM_OPTION = const(2) +_BABBAGE_OUTPUT_KEY_REFERENCE_SCRIPT = const(3) -POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT = 10 +_DATUM_OPTION_KEY_HASH = const(0) +_DATUM_OPTION_KEY_INLINE = const(1) -MAX_CHUNK_SIZE = 1024 +_POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT = const(10) + +_MAX_CHUNK_SIZE = const(1024) class Signer: @@ -101,12 +86,17 @@ def __init__( msg: messages.CardanoSignTxInit, keychain: seed.Keychain, ) -> None: + from ..helpers.account_path_check import AccountPathChecker + self.ctx = ctx self.msg = msg self.keychain = keychain self.account_path_checker = AccountPathChecker() + # There should be at most one pool owner given as a path. + self.pool_owner_path = None + # Inputs, outputs and fee are mandatory, count the number of optional fields present. tx_dict_items_count = 3 + sum( ( @@ -126,12 +116,14 @@ def __init__( ) ) self.tx_dict: HashBuilderDict[int, Any] = HashBuilderDict( - tx_dict_items_count, wire.ProcessError("Invalid tx signing request") + tx_dict_items_count, ProcessError("Invalid tx signing request") ) self.should_show_details = False async def sign(self) -> None: + from trezor.crypto import hashlib + hash_fn = hashlib.blake2b(outlen=32) self.tx_dict.start(hash_fn) with self.tx_dict: @@ -141,7 +133,6 @@ async def sign(self) -> None: await self._confirm_tx(tx_hash) response_after_witness_requests = await self._process_witness_requests(tx_hash) - await confirm_final(self.ctx, "Cardano") await self.ctx.call(response_after_witness_requests, messages.CardanoTxHostAck) await self.ctx.call( messages.CardanoTxBodyHash(tx_hash=tx_hash), messages.CardanoTxHostAck @@ -152,96 +143,100 @@ async def sign(self) -> None: async def _processs_tx_init(self) -> None: self._validate_tx_init() await self._show_tx_init() + msg = self.msg # local_cache_attribute + add = self.tx_dict.add # local_cache_attribute + HBL = HashBuilderList # local_cache_global + HBS = HashBuilderSet # local_cache_global - inputs_list: HashBuilderList[tuple[bytes, int]] = HashBuilderList( - self.msg.inputs_count + inputs_set: HashBuilderSet[tuple[bytes, int]] = HBS( + msg.inputs_count, tagged=self.msg.tag_cbor_sets ) - with self.tx_dict.add(TX_BODY_KEY_INPUTS, inputs_list): - await self._process_inputs(inputs_list) + with add(_TX_BODY_KEY_INPUTS, inputs_set): + await self._process_inputs(inputs_set) - outputs_list: HashBuilderList = HashBuilderList(self.msg.outputs_count) - with self.tx_dict.add(TX_BODY_KEY_OUTPUTS, outputs_list): + outputs_list: HashBuilderList = HBL(msg.outputs_count) + with add(_TX_BODY_KEY_OUTPUTS, outputs_list): await self._process_outputs(outputs_list) - self.tx_dict.add(TX_BODY_KEY_FEE, self.msg.fee) + add(_TX_BODY_KEY_FEE, msg.fee) - if self.msg.ttl is not None: - self.tx_dict.add(TX_BODY_KEY_TTL, self.msg.ttl) + if msg.ttl is not None: + add(_TX_BODY_KEY_TTL, msg.ttl) - if self.msg.certificates_count > 0: - certificates_list: HashBuilderList = HashBuilderList( - self.msg.certificates_count + if msg.certificates_count > 0: + certificates_set: HashBuilderSet = HBS( + msg.certificates_count, tagged=self.msg.tag_cbor_sets ) - with self.tx_dict.add(TX_BODY_KEY_CERTIFICATES, certificates_list): - await self._process_certificates(certificates_list) + with add(_TX_BODY_KEY_CERTIFICATES, certificates_set): + await self._process_certificates(certificates_set) - if self.msg.withdrawals_count > 0: + if msg.withdrawals_count > 0: withdrawals_dict: HashBuilderDict[bytes, int] = HashBuilderDict( - self.msg.withdrawals_count, wire.ProcessError("Invalid withdrawal") + msg.withdrawals_count, ProcessError("Invalid withdrawal") ) - with self.tx_dict.add(TX_BODY_KEY_WITHDRAWALS, withdrawals_dict): + with add(_TX_BODY_KEY_WITHDRAWALS, withdrawals_dict): await self._process_withdrawals(withdrawals_dict) - if self.msg.has_auxiliary_data: + if msg.has_auxiliary_data: await self._process_auxiliary_data() - if self.msg.validity_interval_start is not None: - self.tx_dict.add( - TX_BODY_KEY_VALIDITY_INTERVAL_START, self.msg.validity_interval_start - ) + if msg.validity_interval_start is not None: + add(_TX_BODY_KEY_VALIDITY_INTERVAL_START, msg.validity_interval_start) - if self.msg.minting_asset_groups_count > 0: + if msg.minting_asset_groups_count > 0: minting_dict: HashBuilderDict[bytes, HashBuilderDict] = HashBuilderDict( - self.msg.minting_asset_groups_count, - wire.ProcessError("Invalid mint token bundle"), + msg.minting_asset_groups_count, + ProcessError("Invalid mint token bundle"), ) - with self.tx_dict.add(TX_BODY_KEY_MINT, minting_dict): + with add(_TX_BODY_KEY_MINT, minting_dict): await self._process_minting(minting_dict) - if self.msg.script_data_hash is not None: + if msg.script_data_hash is not None: await self._process_script_data_hash() - if self.msg.collateral_inputs_count > 0: - collateral_inputs_list: HashBuilderList[ - tuple[bytes, int] - ] = HashBuilderList(self.msg.collateral_inputs_count) - with self.tx_dict.add( - TX_BODY_KEY_COLLATERAL_INPUTS, collateral_inputs_list - ): - await self._process_collateral_inputs(collateral_inputs_list) + if msg.collateral_inputs_count > 0: + collateral_inputs_set: HashBuilderSet[tuple[bytes, int]] = HBS( + msg.collateral_inputs_count, tagged=self.msg.tag_cbor_sets + ) + with add(_TX_BODY_KEY_COLLATERAL_INPUTS, collateral_inputs_set): + await self._process_collateral_inputs(collateral_inputs_set) - if self.msg.required_signers_count > 0: - required_signers_list: HashBuilderList[bytes] = HashBuilderList( - self.msg.required_signers_count + if msg.required_signers_count > 0: + required_signers_set: HashBuilderSet[bytes] = HBS( + msg.required_signers_count, tagged=self.msg.tag_cbor_sets ) - with self.tx_dict.add(TX_BODY_KEY_REQUIRED_SIGNERS, required_signers_list): - await self._process_required_signers(required_signers_list) + with add(_TX_BODY_KEY_REQUIRED_SIGNERS, required_signers_set): + await self._process_required_signers(required_signers_set) - if self.msg.include_network_id: - self.tx_dict.add(TX_BODY_KEY_NETWORK_ID, self.msg.network_id) + if msg.include_network_id: + add(_TX_BODY_KEY_NETWORK_ID, msg.network_id) - if self.msg.has_collateral_return: + if msg.has_collateral_return: await self._process_collateral_return() - if self.msg.total_collateral is not None: - self.tx_dict.add(TX_BODY_KEY_TOTAL_COLLATERAL, self.msg.total_collateral) + if msg.total_collateral is not None: + add(_TX_BODY_KEY_TOTAL_COLLATERAL, msg.total_collateral) - if self.msg.reference_inputs_count > 0: - reference_inputs_list: HashBuilderList[tuple[bytes, int]] = HashBuilderList( - self.msg.reference_inputs_count + if msg.reference_inputs_count > 0: + reference_inputs_set: HashBuilderSet[tuple[bytes, int]] = HBS( + msg.reference_inputs_count, tagged=self.msg.tag_cbor_sets ) - with self.tx_dict.add(TX_BODY_KEY_REFERENCE_INPUTS, reference_inputs_list): - await self._process_reference_inputs(reference_inputs_list) + with add(_TX_BODY_KEY_REFERENCE_INPUTS, reference_inputs_set): + await self._process_reference_inputs(reference_inputs_set) def _validate_tx_init(self) -> None: - if self.msg.fee > LOVELACE_MAX_SUPPLY: - raise wire.ProcessError("Fee is out of range!") + from ..helpers.utils import validate_network_info + + msg = self.msg # local_cache_attribute + + if msg.fee > LOVELACE_MAX_SUPPLY: + raise ProcessError("Fee is out of range!") if ( - self.msg.total_collateral is not None - and self.msg.total_collateral > LOVELACE_MAX_SUPPLY + msg.total_collateral is not None + and msg.total_collateral > LOVELACE_MAX_SUPPLY ): - raise wire.ProcessError("Total collateral is out of range!") - validate_network_info(self.msg.network_id, self.msg.protocol_magic) + raise ProcessError("Total collateral is out of range!") + validate_network_info(msg.network_id, msg.protocol_magic) async def _show_tx_init(self) -> None: self.should_show_details = await layout.show_tx_init( @@ -255,10 +250,6 @@ async def _confirm_tx(self, tx_hash: bytes) -> None: # Final signing confirmation is handled separately in each signing mode. raise NotImplementedError - def _should_show_tx_hash(self) -> bool: - # By default we display tx hash only if showing details - return self.should_show_details - # inputs async def _process_inputs( @@ -266,7 +257,7 @@ async def _process_inputs( ) -> None: for _ in range(self.msg.inputs_count): input: messages.CardanoTxInput = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxInput + CardanoTxItemAck(), messages.CardanoTxInput ) self._validate_input(input) await self._show_input(input) @@ -274,7 +265,7 @@ async def _process_inputs( def _validate_input(self, input: messages.CardanoTxInput) -> None: if len(input.prev_hash) != INPUT_PREV_HASH_SIZE: - raise wire.ProcessError("Invalid input") + raise ProcessError("Invalid input") async def _show_input(self, input: messages.CardanoTxInput) -> None: # We never show the inputs, except for Plutus txs. @@ -285,18 +276,18 @@ async def _show_input(self, input: messages.CardanoTxInput) -> None: async def _process_outputs(self, outputs_list: HashBuilderList) -> None: total_amount = 0 for _ in range(self.msg.outputs_count): - output: messages.CardanoTxOutput = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxOutput + output: CardanoTxOutput = await self.ctx.call( + CardanoTxItemAck(), CardanoTxOutput ) await self._process_output(outputs_list, output) total_amount += output.amount if total_amount > LOVELACE_MAX_SUPPLY: - raise wire.ProcessError("Total transaction amount is out of range!") + raise ProcessError("Total transaction amount is out of range!") async def _process_output( - self, outputs_list: HashBuilderList, output: messages.CardanoTxOutput + self, outputs_list: HashBuilderList, output: CardanoTxOutput ) -> None: self._validate_output(output) should_show = self._should_show_output(output) @@ -316,51 +307,64 @@ async def _process_output( await self._process_legacy_output(output_list, output, should_show) elif output.format == CardanoTxOutputSerializationFormat.MAP_BABBAGE: output_dict: HashBuilderDict[int, Any] = HashBuilderDict( - output_items_count, wire.ProcessError("Invalid output") + output_items_count, ProcessError("Invalid output") ) with outputs_list.append(output_dict): await self._process_babbage_output(output_dict, output, should_show) else: raise RuntimeError # should be unreachable - def _validate_output(self, output: messages.CardanoTxOutput) -> None: - if output.address_parameters is not None and output.address is not None: - raise wire.ProcessError("Invalid output") + def _validate_output(self, output: CardanoTxOutput) -> None: + from ..helpers import OUTPUT_DATUM_HASH_SIZE - if output.address_parameters is not None: - addresses.validate_output_address_parameters(output.address_parameters) - self._fail_if_strict_and_unusual(output.address_parameters) + address_parameters = output.address_parameters # local_cache_attribute + + if address_parameters is not None and output.address is not None: + raise ProcessError("Invalid output") + + if address_parameters is not None: + addresses.validate_output_address_parameters(address_parameters) + self._fail_if_strict_and_unusual(address_parameters) elif output.address is not None: addresses.validate_output_address( output.address, self.msg.protocol_magic, self.msg.network_id ) else: - raise wire.ProcessError("Invalid output") + raise ProcessError("Invalid output") # datum hash if output.datum_hash is not None: if len(output.datum_hash) != OUTPUT_DATUM_HASH_SIZE: - raise wire.ProcessError("Invalid output datum hash") + raise ProcessError("Invalid output datum hash") # inline datum if output.inline_datum_size > 0: if output.format != CardanoTxOutputSerializationFormat.MAP_BABBAGE: - raise wire.ProcessError("Invalid output") + raise ProcessError("Invalid output") # datum hash and inline datum are mutually exclusive if output.datum_hash is not None and output.inline_datum_size > 0: - raise wire.ProcessError("Invalid output") + raise ProcessError("Invalid output") # reference script if output.reference_script_size > 0: if output.format != CardanoTxOutputSerializationFormat.MAP_BABBAGE: - raise wire.ProcessError("Invalid output") + raise ProcessError("Invalid output") self.account_path_checker.add_output(output) - async def _show_output_init(self, output: messages.CardanoTxOutput) -> None: + async def _show_output_init(self, output: CardanoTxOutput) -> None: address_type = self._get_output_address_type(output) - should_show_credentials = False + if ( + output.datum_hash is None + and output.inline_datum_size == 0 + and address_type in addresses.ADDRESS_TYPES_PAYMENT_SCRIPT + ): + await layout.warn_tx_output_no_datum(self.ctx) + + if output.asset_groups_count > 0: + await layout.warn_tx_output_contains_tokens(self.ctx) + if output.address_parameters is not None: address = addresses.derive_human_readable( self.keychain, @@ -368,7 +372,7 @@ async def _show_output_init(self, output: messages.CardanoTxOutput) -> None: self.msg.protocol_magic, self.msg.network_id, ) - should_show_credentials = True + await self._show_output_credentials(output.address_parameters) else: assert output.address is not None # _validate_output address = output.address @@ -379,18 +383,8 @@ async def _show_output_init(self, output: messages.CardanoTxOutput) -> None: address, "change" if self._is_change_output(output) else "address", self.msg.network_id, + chunkify=bool(self.msg.chunkify), ) - if output.asset_groups_count > 0: - await layout.warn_tx_output_contains_tokens(self.ctx) - if should_show_credentials: - assert output.address_parameters is not None - await self._show_output_credentials(output.address_parameters) - if ( - output.datum_hash is None - and output.inline_datum_size == 0 - and address_type in addresses.ADDRESS_TYPES_PAYMENT_SCRIPT - ): - await layout.warn_tx_output_no_datum(self.ctx) async def _show_output_credentials( self, address_parameters: messages.CardanoAddressParametersType @@ -401,7 +395,7 @@ async def _show_output_credentials( Credential.stake_credential(address_parameters), ) - def _should_show_output(self, output: messages.CardanoTxOutput) -> bool: + def _should_show_output(self, output: CardanoTxOutput) -> bool: """ Determines whether the output should be shown. Extracted from _show_output because of readability. @@ -427,12 +421,14 @@ def _should_show_output(self, output: messages.CardanoTxOutput) -> bool: return True - def _is_change_output(self, output: messages.CardanoTxOutput) -> bool: + def _is_change_output(self, output: CardanoTxOutput) -> bool: """Used only to determine what message to show to the user when confirming sending.""" return output.address_parameters is not None - def _is_simple_change_output(self, output: messages.CardanoTxOutput) -> bool: + def _is_simple_change_output(self, output: CardanoTxOutput) -> bool: """Used to determine whether an output is a change output with ordinary credentials.""" + from ..helpers.credential import should_show_credentials + return output.address_parameters is not None and not should_show_credentials( output.address_parameters ) @@ -440,7 +436,7 @@ def _is_simple_change_output(self, output: messages.CardanoTxOutput) -> bool: async def _process_legacy_output( self, output_list: HashBuilderList, - output: messages.CardanoTxOutput, + output: CardanoTxOutput, should_show: bool, ) -> None: address = self._get_output_address(output) @@ -465,23 +461,27 @@ async def _process_legacy_output( async def _process_babbage_output( self, output_dict: HashBuilderDict[int, Any], - output: messages.CardanoTxOutput, + output: CardanoTxOutput, should_show: bool, ) -> None: """ This output format corresponds to the post-Alonzo format in CDDL. Note that it is to be used also for outputs with no Plutus elements. """ + from ..helpers.hash_builder_collection import HashBuilderEmbeddedCBOR + + add = output_dict.add # local_cache_attribute + address = self._get_output_address(output) - output_dict.add(BABBAGE_OUTPUT_KEY_ADDRESS, address) + add(_BABBAGE_OUTPUT_KEY_ADDRESS, address) if output.asset_groups_count == 0: # Only amount is added to the dict. - output_dict.add(BABBAGE_OUTPUT_KEY_AMOUNT, output.amount) + add(_BABBAGE_OUTPUT_KEY_AMOUNT, output.amount) else: # [amount, asset_groups] is added to the dict. output_value_list: HashBuilderList = HashBuilderList(2) - with output_dict.add(BABBAGE_OUTPUT_KEY_AMOUNT, output_value_list): + with add(_BABBAGE_OUTPUT_KEY_AMOUNT, output_value_list): await self._process_output_value(output_value_list, output, should_show) if output.datum_hash is not None: @@ -489,14 +489,14 @@ async def _process_babbage_output( await self._show_if_showing_details( layout.confirm_datum_hash(self.ctx, output.datum_hash) ) - output_dict.add( - BABBAGE_OUTPUT_KEY_DATUM_OPTION, - (DATUM_OPTION_KEY_HASH, output.datum_hash), + add( + _BABBAGE_OUTPUT_KEY_DATUM_OPTION, + (_DATUM_OPTION_KEY_HASH, output.datum_hash), ) elif output.inline_datum_size > 0: inline_datum_list: HashBuilderList = HashBuilderList(2) - with output_dict.add(BABBAGE_OUTPUT_KEY_DATUM_OPTION, inline_datum_list): - inline_datum_list.append(DATUM_OPTION_KEY_INLINE) + with add(_BABBAGE_OUTPUT_KEY_DATUM_OPTION, inline_datum_list): + inline_datum_list.append(_DATUM_OPTION_KEY_INLINE) inline_datum_cbor: HashBuilderEmbeddedCBOR = HashBuilderEmbeddedCBOR( output.inline_datum_size ) @@ -509,9 +509,7 @@ async def _process_babbage_output( reference_script_cbor: HashBuilderEmbeddedCBOR = HashBuilderEmbeddedCBOR( output.reference_script_size ) - with output_dict.add( - BABBAGE_OUTPUT_KEY_REFERENCE_SCRIPT, reference_script_cbor - ): + with add(_BABBAGE_OUTPUT_KEY_REFERENCE_SCRIPT, reference_script_cbor): await self._process_reference_script( reference_script_cbor, output.reference_script_size, should_show ) @@ -519,7 +517,7 @@ async def _process_babbage_output( async def _process_output_value( self, output_value_list: HashBuilderList, - output: messages.CardanoTxOutput, + output: CardanoTxOutput, should_show_tokens: bool, ) -> None: """Should be used only when the output contains tokens.""" @@ -531,7 +529,7 @@ async def _process_output_value( bytes, HashBuilderDict[bytes, int] ] = HashBuilderDict( output.asset_groups_count, - wire.ProcessError("Invalid token bundle in output"), + ProcessError("Invalid token bundle in output"), ) with output_value_list.append(asset_groups_dict): await self._process_asset_groups( @@ -550,13 +548,13 @@ async def _process_asset_groups( ) -> None: for _ in range(asset_groups_count): asset_group: messages.CardanoAssetGroup = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoAssetGroup + CardanoTxItemAck(), messages.CardanoAssetGroup ) self._validate_asset_group(asset_group) tokens: HashBuilderDict[bytes, int] = HashBuilderDict( asset_group.tokens_count, - wire.ProcessError("Invalid token bundle in output"), + ProcessError("Invalid token bundle in output"), ) with asset_groups_dict.add(asset_group.policy_id, tokens): await self._process_tokens( @@ -570,12 +568,12 @@ def _validate_asset_group( self, asset_group: messages.CardanoAssetGroup, is_mint: bool = False ) -> None: INVALID_TOKEN_BUNDLE = ( - wire.ProcessError("Invalid mint token bundle") + ProcessError("Invalid mint token bundle") if is_mint - else wire.ProcessError("Invalid token bundle in output") + else ProcessError("Invalid token bundle in output") ) - if len(asset_group.policy_id) != MINTING_POLICY_ID_LENGTH: + if len(asset_group.policy_id) != _MINTING_POLICY_ID_LENGTH: raise INVALID_TOKEN_BUNDLE if asset_group.tokens_count == 0: raise INVALID_TOKEN_BUNDLE @@ -591,7 +589,7 @@ async def _process_tokens( ) -> None: for _ in range(tokens_count): token: messages.CardanoToken = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoToken + CardanoTxItemAck(), messages.CardanoToken ) self._validate_token(token) if should_show_tokens: @@ -604,9 +602,9 @@ def _validate_token( self, token: messages.CardanoToken, is_mint: bool = False ) -> None: INVALID_TOKEN_BUNDLE = ( - wire.ProcessError("Invalid mint token bundle") + ProcessError("Invalid mint token bundle") if is_mint - else wire.ProcessError("Invalid token bundle in output") + else ProcessError("Invalid token bundle in output") ) if is_mint: @@ -616,7 +614,7 @@ def _validate_token( if token.amount is None or token.mint_amount is not None: raise INVALID_TOKEN_BUNDLE - if len(token.asset_name_bytes) > MAX_ASSET_NAME_LENGTH: + if len(token.asset_name_bytes) > _MAX_ASSET_NAME_LENGTH: raise INVALID_TOKEN_BUNDLE # inline datum @@ -632,13 +630,13 @@ async def _process_inline_datum( chunks_count = self._get_chunks_count(inline_datum_size) for chunk_number in range(chunks_count): chunk: messages.CardanoTxInlineDatumChunk = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxInlineDatumChunk + CardanoTxItemAck(), messages.CardanoTxInlineDatumChunk ) self._validate_chunk( chunk.data, chunk_number, chunks_count, - wire.ProcessError("Invalid inline datum chunk"), + ProcessError("Invalid inline datum chunk"), ) if chunk_number == 0 and should_show: await self._show_if_showing_details( @@ -659,13 +657,13 @@ async def _process_reference_script( chunks_count = self._get_chunks_count(reference_script_size) for chunk_number in range(chunks_count): chunk: messages.CardanoTxReferenceScriptChunk = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxReferenceScriptChunk + CardanoTxItemAck(), messages.CardanoTxReferenceScriptChunk ) self._validate_chunk( chunk.data, chunk_number, chunks_count, - wire.ProcessError("Invalid reference script chunk"), + ProcessError("Invalid reference script chunk"), ) if chunk_number == 0 and should_show: await self._show_if_showing_details( @@ -677,10 +675,10 @@ async def _process_reference_script( # certificates - async def _process_certificates(self, certificates_list: HashBuilderList) -> None: + async def _process_certificates(self, certificates_set: HashBuilderSet) -> None: for _ in range(self.msg.certificates_count): certificate: messages.CardanoTxCertificate = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxCertificate + CardanoTxItemAck(), messages.CardanoTxCertificate ) self._validate_certificate(certificate) await self._show_certificate(certificate) @@ -690,20 +688,20 @@ async def _process_certificates(self, certificates_list: HashBuilderList) -> Non assert pool_parameters is not None # _validate_certificate pool_items_list: HashBuilderList = HashBuilderList( - POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT + _POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT ) - with certificates_list.append(pool_items_list): + with certificates_set.append(pool_items_list): for item in certificates.cborize_pool_registration_init( certificate ): pool_items_list.append(item) - pool_owners_list: HashBuilderList[bytes] = HashBuilderList( - pool_parameters.owners_count + pool_owners_set: HashBuilderSet[bytes] = HashBuilderSet( + pool_parameters.owners_count, tagged=self.msg.tag_cbor_sets ) - with pool_items_list.append(pool_owners_list): + with pool_items_list.append(pool_owners_set): await self._process_pool_owners( - pool_owners_list, pool_parameters.owners_count + pool_owners_set, pool_parameters.owners_count ) relays_list: HashBuilderList[cbor.CborSequence] = HashBuilderList( @@ -718,7 +716,7 @@ async def _process_certificates(self, certificates_list: HashBuilderList) -> Non certificates.cborize_pool_metadata(pool_parameters.metadata) ) else: - certificates_list.append( + certificates_set.append( certificates.cborize(self.keychain, certificate) ) @@ -735,7 +733,7 @@ async def _show_certificate( ) -> None: if certificate.path: await self._fail_or_warn_if_invalid_path( - SCHEMA_STAKING, certificate.path, CERTIFICATE_PATH_NAME + SCHEMA_STAKING, certificate.path, "Certificate path" ) if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: @@ -747,33 +745,34 @@ async def _show_certificate( self.ctx, certificate.pool_parameters.metadata ) else: - await layout.confirm_certificate(self.ctx, certificate) + await layout.confirm_certificate(self.ctx, certificate, self.msg.network_id) # pool owners async def _process_pool_owners( - self, pool_owners_list: HashBuilderList[bytes], owners_count: int + self, pool_owners_set: HashBuilderSet[bytes], owners_count: int ) -> None: owners_as_path_count = 0 for _ in range(owners_count): owner: messages.CardanoPoolOwner = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoPoolOwner + CardanoTxItemAck(), messages.CardanoPoolOwner ) certificates.validate_pool_owner(owner, self.account_path_checker) await self._show_pool_owner(owner) - pool_owners_list.append( + pool_owners_set.append( certificates.cborize_pool_owner(self.keychain, owner) ) if owner.staking_key_path: owners_as_path_count += 1 + self.pool_owner_path = owner.staking_key_path certificates.assert_cond(owners_as_path_count == 1) async def _show_pool_owner(self, owner: messages.CardanoPoolOwner) -> None: if owner.staking_key_path: await self._fail_or_warn_if_invalid_path( - SCHEMA_STAKING, owner.staking_key_path, POOL_OWNER_STAKING_PATH_NAME + SCHEMA_STAKING, owner.staking_key_path, "Pool owner staking path" ) await layout.confirm_stake_pool_owner( @@ -789,7 +788,7 @@ async def _process_pool_relays( ) -> None: for _ in range(relays_count): relay: messages.CardanoPoolRelayParameters = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoPoolRelayParameters + CardanoTxItemAck(), messages.CardanoPoolRelayParameters ) certificates.validate_pool_relay(relay) relays_list.append(certificates.cborize_pool_relay(relay)) @@ -801,7 +800,7 @@ async def _process_withdrawals( ) -> None: for _ in range(self.msg.withdrawals_count): withdrawal: messages.CardanoTxWithdrawal = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxWithdrawal + CardanoTxItemAck(), messages.CardanoTxWithdrawal ) self._validate_withdrawal(withdrawal) address_bytes = self._derive_withdrawal_address_bytes(withdrawal) @@ -813,42 +812,48 @@ async def _process_withdrawals( withdrawals_dict.add(address_bytes, withdrawal.amount) def _validate_withdrawal(self, withdrawal: messages.CardanoTxWithdrawal) -> None: + from ..helpers.utils import validate_stake_credential + validate_stake_credential( withdrawal.path, withdrawal.script_hash, withdrawal.key_hash, - wire.ProcessError("Invalid withdrawal"), + ProcessError("Invalid withdrawal"), ) if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY: - raise wire.ProcessError("Invalid withdrawal") + raise ProcessError("Invalid withdrawal") self.account_path_checker.add_withdrawal(withdrawal) # auxiliary data async def _process_auxiliary_data(self) -> None: + from .. import auxiliary_data + + msg = self.msg # local_cache_attribute + data: messages.CardanoTxAuxiliaryData = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxAuxiliaryData + CardanoTxItemAck(), messages.CardanoTxAuxiliaryData ) - auxiliary_data.validate(data) + auxiliary_data.validate(data, msg.protocol_magic, msg.network_id) ( auxiliary_data_hash, auxiliary_data_supplement, ) = auxiliary_data.get_hash_and_supplement( - self.keychain, data, self.msg.protocol_magic, self.msg.network_id + self.keychain, data, msg.protocol_magic, msg.network_id ) await auxiliary_data.show( self.ctx, self.keychain, auxiliary_data_hash, - data.governance_registration_parameters, - self.msg.protocol_magic, - self.msg.network_id, + data.cvote_registration_parameters, + msg.protocol_magic, + msg.network_id, self.should_show_details, ) - self.tx_dict.add(TX_BODY_KEY_AUXILIARY_DATA, auxiliary_data_hash) + self.tx_dict.add(_TX_BODY_KEY_AUXILIARY_DATA, auxiliary_data_hash) await self.ctx.call(auxiliary_data_supplement, messages.CardanoTxHostAck) @@ -858,19 +863,19 @@ async def _process_minting( self, minting_dict: HashBuilderDict[bytes, HashBuilderDict] ) -> None: token_minting: messages.CardanoTxMint = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxMint + CardanoTxItemAck(), messages.CardanoTxMint ) await layout.warn_tx_contains_mint(self.ctx) for _ in range(token_minting.asset_groups_count): asset_group: messages.CardanoAssetGroup = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoAssetGroup + CardanoTxItemAck(), messages.CardanoAssetGroup ) self._validate_asset_group(asset_group, is_mint=True) tokens: HashBuilderDict[bytes, int] = HashBuilderDict( - asset_group.tokens_count, wire.ProcessError("Invalid mint token bundle") + asset_group.tokens_count, ProcessError("Invalid mint token bundle") ) with minting_dict.add(asset_group.policy_id, tokens): await self._process_minting_tokens( @@ -889,7 +894,7 @@ async def _process_minting_tokens( ) -> None: for _ in range(tokens_count): token: messages.CardanoToken = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoToken + CardanoTxItemAck(), messages.CardanoToken ) self._validate_token(token, is_mint=True) await layout.confirm_token_minting(self.ctx, policy_id, token) @@ -905,12 +910,14 @@ async def _process_script_data_hash(self) -> None: await self._show_if_showing_details( layout.confirm_script_data_hash(self.ctx, self.msg.script_data_hash) ) - self.tx_dict.add(TX_BODY_KEY_SCRIPT_DATA_HASH, self.msg.script_data_hash) + self.tx_dict.add(_TX_BODY_KEY_SCRIPT_DATA_HASH, self.msg.script_data_hash) def _validate_script_data_hash(self) -> None: + from ..helpers import SCRIPT_DATA_HASH_SIZE + assert self.msg.script_data_hash is not None if len(self.msg.script_data_hash) != SCRIPT_DATA_HASH_SIZE: - raise wire.ProcessError("Invalid script data hash") + raise ProcessError("Invalid script data hash") # collateral inputs @@ -919,7 +926,7 @@ async def _process_collateral_inputs( ) -> None: for _ in range(self.msg.collateral_inputs_count): collateral_input: messages.CardanoTxCollateralInput = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxCollateralInput + CardanoTxItemAck(), messages.CardanoTxCollateralInput ) self._validate_collateral_input(collateral_input) await self._show_collateral_input(collateral_input) @@ -931,7 +938,7 @@ def _validate_collateral_input( self, collateral_input: messages.CardanoTxCollateralInput ) -> None: if len(collateral_input.prev_hash) != INPUT_PREV_HASH_SIZE: - raise wire.ProcessError("Invalid collateral input") + raise ProcessError("Invalid collateral input") async def _show_collateral_input( self, collateral_input: messages.CardanoTxCollateralInput @@ -944,11 +951,13 @@ async def _show_collateral_input( # required signers async def _process_required_signers( - self, required_signers_list: HashBuilderList[bytes] + self, required_signers_set: HashBuilderSet[bytes] ) -> None: + from ..helpers.utils import get_public_key_hash + for _ in range(self.msg.required_signers_count): required_signer: messages.CardanoTxRequiredSigner = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxRequiredSigner + CardanoTxItemAck(), messages.CardanoTxRequiredSigner ) self._validate_required_signer(required_signer) await self._show_if_showing_details( @@ -958,24 +967,28 @@ async def _process_required_signers( key_hash = required_signer.key_hash or get_public_key_hash( self.keychain, required_signer.key_path ) - required_signers_list.append(key_hash) + required_signers_set.append(key_hash) def _validate_required_signer( self, required_signer: messages.CardanoTxRequiredSigner ) -> None: - INVALID_REQUIRED_SIGNER = wire.ProcessError("Invalid required signer") + from ..helpers import ADDRESS_KEY_HASH_SIZE - if required_signer.key_hash and required_signer.key_path: + key_path = required_signer.key_path # local_cache_attribute + + INVALID_REQUIRED_SIGNER = ProcessError("Invalid required signer") + + if required_signer.key_hash and key_path: raise INVALID_REQUIRED_SIGNER if required_signer.key_hash: if len(required_signer.key_hash) != ADDRESS_KEY_HASH_SIZE: raise INVALID_REQUIRED_SIGNER - elif required_signer.key_path: + elif key_path: if not ( - seed.is_shelley_path(required_signer.key_path) - or seed.is_multisig_path(required_signer.key_path) - or seed.is_minting_path(required_signer.key_path) + seed.is_shelley_path(key_path) + or seed.is_multisig_path(key_path) + or seed.is_minting_path(key_path) ): raise INVALID_REQUIRED_SIGNER else: @@ -984,8 +997,8 @@ def _validate_required_signer( # collateral return async def _process_collateral_return(self) -> None: - output: messages.CardanoTxOutput = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxOutput + output: CardanoTxOutput = await self.ctx.call( + CardanoTxItemAck(), CardanoTxOutput ) self._validate_collateral_return(output) should_show_init = self._should_show_collateral_return_init(output) @@ -997,38 +1010,36 @@ async def _process_collateral_return(self) -> None: output_items_count = 2 if output.format == CardanoTxOutputSerializationFormat.ARRAY_LEGACY: output_list: HashBuilderList = HashBuilderList(output_items_count) - with self.tx_dict.add(TX_BODY_KEY_COLLATERAL_RETURN, output_list): + with self.tx_dict.add(_TX_BODY_KEY_COLLATERAL_RETURN, output_list): await self._process_legacy_output( output_list, output, should_show_tokens ) elif output.format == CardanoTxOutputSerializationFormat.MAP_BABBAGE: output_dict: HashBuilderDict[int, Any] = HashBuilderDict( - output_items_count, wire.ProcessError("Invalid collateral return") + output_items_count, ProcessError("Invalid collateral return") ) - with self.tx_dict.add(TX_BODY_KEY_COLLATERAL_RETURN, output_dict): + with self.tx_dict.add(_TX_BODY_KEY_COLLATERAL_RETURN, output_dict): await self._process_babbage_output( output_dict, output, should_show_tokens ) else: raise RuntimeError # should be unreachable - def _validate_collateral_return(self, output: messages.CardanoTxOutput) -> None: + def _validate_collateral_return(self, output: CardanoTxOutput) -> None: self._validate_output(output) address_type = self._get_output_address_type(output) if address_type not in addresses.ADDRESS_TYPES_PAYMENT_KEY: - raise wire.ProcessError("Invalid collateral return") + raise ProcessError("Invalid collateral return") if ( output.datum_hash is not None or output.inline_datum_size > 0 or output.reference_script_size > 0 ): - raise wire.ProcessError("Invalid collateral return") + raise ProcessError("Invalid collateral return") - async def _show_collateral_return_init( - self, output: messages.CardanoTxOutput - ) -> None: + async def _show_collateral_return_init(self, output: CardanoTxOutput) -> None: # We don't display missing datum warning since datums are forbidden. if output.asset_groups_count > 0: @@ -1056,11 +1067,10 @@ async def _show_collateral_return_init( address, "collateral-return", self.msg.network_id, + chunkify=bool(self.msg.chunkify), ) - def _should_show_collateral_return_init( - self, output: messages.CardanoTxOutput - ) -> bool: + def _should_show_collateral_return_init(self, output: CardanoTxOutput) -> bool: if self.msg.total_collateral is None: return True @@ -1069,9 +1079,7 @@ def _should_show_collateral_return_init( return True - def _should_show_collateral_return_tokens( - self, output: messages.CardanoTxOutput - ) -> bool: + def _should_show_collateral_return_tokens(self, output: CardanoTxOutput) -> bool: if self._is_simple_change_output(output): return False @@ -1084,7 +1092,7 @@ async def _process_reference_inputs( ) -> None: for _ in range(self.msg.reference_inputs_count): reference_input: messages.CardanoTxReferenceInput = await self.ctx.call( - messages.CardanoTxItemAck(), messages.CardanoTxReferenceInput + CardanoTxItemAck(), messages.CardanoTxReferenceInput ) self._validate_reference_input(reference_input) await self._show_if_showing_details( @@ -1098,13 +1106,13 @@ def _validate_reference_input( self, reference_input: messages.CardanoTxReferenceInput ) -> None: if len(reference_input.prev_hash) != INPUT_PREV_HASH_SIZE: - raise wire.ProcessError("Invalid reference input") + raise ProcessError("Invalid reference input") # witness requests async def _process_witness_requests(self, tx_hash: bytes) -> CardanoTxResponseType: - response: CardanoTxResponseType = messages.CardanoTxItemAck() - + response: CardanoTxResponseType = CardanoTxItemAck() + confirmed = False for _ in range(self.msg.witness_requests_count): witness_request = await self.ctx.call( response, messages.CardanoTxWitnessRequest @@ -1112,6 +1120,9 @@ async def _process_witness_requests(self, tx_hash: bytes) -> CardanoTxResponseTy self._validate_witness_request(witness_request) path = witness_request.path await self._show_witness_request(path) + if not confirmed: + await confirm_final(self.ctx, "Cardano") + confirmed = True if seed.is_byron_path(path): response = self._get_byron_witness(path, tx_hash) else: @@ -1134,7 +1145,7 @@ async def _show_witness_request( def _assert_tx_init_cond(self, condition: bool) -> None: if not condition: - raise wire.ProcessError("Invalid tx signing request") + raise ProcessError("Invalid tx signing request") def _is_network_id_verifiable(self) -> bool: """ @@ -1152,7 +1163,7 @@ def _is_network_id_verifiable(self) -> bool: or self.msg.withdrawals_count != 0 ) - def _get_output_address(self, output: messages.CardanoTxOutput) -> bytes: + def _get_output_address(self, output: CardanoTxOutput) -> bytes: if output.address_parameters: return addresses.derive_bytes( self.keychain, @@ -1164,9 +1175,7 @@ def _get_output_address(self, output: messages.CardanoTxOutput) -> bytes: assert output.address is not None # _validate_output return addresses.get_bytes_unsafe(output.address) - def _get_output_address_type( - self, output: messages.CardanoTxOutput - ) -> CardanoAddressType: + def _get_output_address_type(self, output: CardanoTxOutput) -> CardanoAddressType: if output.address_parameters: return output.address_parameters.address_type assert output.address is not None # _validate_output @@ -1175,6 +1184,8 @@ def _get_output_address_type( def _derive_withdrawal_address_bytes( self, withdrawal: messages.CardanoTxWithdrawal ) -> bytes: + from trezor.enums import CardanoAddressType + reward_address_type = ( CardanoAddressType.REWARD if withdrawal.path or withdrawal.key_hash @@ -1194,18 +1205,18 @@ def _derive_withdrawal_address_bytes( def _get_chunks_count(self, data_size: int) -> int: assert data_size > 0 - return (data_size - 1) // MAX_CHUNK_SIZE + 1 + return (data_size - 1) // _MAX_CHUNK_SIZE + 1 def _validate_chunk( self, chunk_data: bytes, chunk_number: int, chunks_count: int, - error: wire.ProcessError, + error: ProcessError, ) -> None: - if chunk_number < chunks_count - 1 and len(chunk_data) != MAX_CHUNK_SIZE: + if chunk_number < chunks_count - 1 and len(chunk_data) != _MAX_CHUNK_SIZE: raise error - if chunk_number == chunks_count - 1 and len(chunk_data) > MAX_CHUNK_SIZE: + if chunk_number == chunks_count - 1 and len(chunk_data) > _MAX_CHUNK_SIZE: raise error def _get_byron_witness( @@ -1229,6 +1240,8 @@ def _get_shelley_witness( ) def _sign_tx_hash(self, tx_body_hash: bytes, path: list[int]) -> bytes: + from trezor.crypto.curve import ed25519 + node = self.keychain.derive(path) return ed25519.sign_ext( node.private_key(), node.private_key_ext(), tx_body_hash @@ -1242,7 +1255,7 @@ async def _fail_or_warn_if_invalid_path( async def _fail_or_warn_path(self, path: list[int], path_name: str) -> None: if safety_checks.is_strict(): - raise wire.DataError(f"Invalid {path_name.lower()}") + raise DataError(f"Invalid {path_name.lower()}") else: await layout.warn_path(self.ctx, path, path_name) @@ -1253,10 +1266,10 @@ def _fail_if_strict_and_unusual( return if Credential.payment_credential(address_parameters).is_unusual_path: - raise wire.DataError(f"Invalid {CHANGE_OUTPUT_PATH_NAME.lower()}") + raise DataError("Invalid change output path") if Credential.stake_credential(address_parameters).is_unusual_path: - raise wire.DataError(f"Invalid {CHANGE_OUTPUT_STAKING_PATH_NAME.lower()}") + raise DataError("Invalid change output staking path") async def _show_if_showing_details(self, layout_fn: Awaitable) -> None: if self.should_show_details: diff --git a/core/src/apps/common/cbor.py b/core/src/apps/common/cbor.py index dd90a4ba12..5fe4b7edc5 100644 --- a/core/src/apps/common/cbor.py +++ b/core/src/apps/common/cbor.py @@ -2,13 +2,13 @@ Minimalistic CBOR implementation, supports only what we need in cardano. """ -import ustruct as struct from micropython import const from typing import TYPE_CHECKING -from trezor import log, utils +from trezor.utils import BufferReader -from . import readers +if __debug__: + from trezor import log if TYPE_CHECKING: from typing import Any, Generic, Iterator, TypeVar @@ -44,20 +44,26 @@ _CBOR_TRUE = const(0x15) _CBOR_NULL = const(0x16) _CBOR_BREAK = const(0x1F) -_CBOR_RAW_TAG = const(0x18) + + +# See https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml +_CBOR_RAW_TAG = const(0x18) # Tag 24 +_CBOR_SET_TAG = const(0x102) # Tag 258 def _header(typ: int, l: int) -> bytes: + from ustruct import pack + if l < 24: - return struct.pack(">B", typ + l) + return pack(">B", typ + l) elif l < 2**8: - return struct.pack(">BB", typ + 24, l) + return pack(">BB", typ + 24, l) elif l < 2**16: - return struct.pack(">BH", typ + 25, l) + return pack(">BH", typ + 25, l) elif l < 2**32: - return struct.pack(">BI", typ + 26, l) + return pack(">BI", typ + 26, l) elif l < 2**64: - return struct.pack(">BQ", typ + 27, l) + return pack(">BQ", typ + 27, l) else: raise NotImplementedError # Length not supported @@ -117,7 +123,9 @@ def _cbor_encode(value: Value) -> Iterator[bytes]: raise NotImplementedError -def _read_length(r: utils.BufferReader, aux: int) -> int: +def _read_length(r: BufferReader, aux: int) -> int: + from . import readers + if aux < _CBOR_UINT8_FOLLOWS: return aux elif aux == _CBOR_UINT8_FOLLOWS: @@ -132,7 +140,7 @@ def _read_length(r: utils.BufferReader, aux: int) -> int: raise NotImplementedError # Length not supported -def _cbor_decode(r: utils.BufferReader) -> Value: +def _cbor_decode(r: BufferReader) -> Value: fb = r.get() fb_type = fb & _CBOR_TYPE_MASK fb_aux = fb & _CBOR_INFO_BITS @@ -271,40 +279,9 @@ def encode_streamed(value: Value) -> Iterator[bytes]: return _cbor_encode(value) -def encode_chunked(value: Value, max_chunk_size: int) -> Iterator[bytes]: - """ - Returns the encoded value as an iterable of chunks of a given size, - removing the need to reserve a continuous chunk of memory for the - full serialized representation of the value. - """ - if max_chunk_size <= 0: - raise ValueError - - chunks = encode_streamed(value) - - chunk_buffer = utils.empty_bytearray(max_chunk_size) - try: - current_chunk_view = utils.BufferReader(next(chunks)) - while True: - num_bytes_to_write = min( - current_chunk_view.remaining_count(), - max_chunk_size - len(chunk_buffer), - ) - chunk_buffer.extend(current_chunk_view.read(num_bytes_to_write)) - - if len(chunk_buffer) >= max_chunk_size: - yield chunk_buffer - chunk_buffer[:] = bytes() - - if current_chunk_view.remaining_count() == 0: - current_chunk_view = utils.BufferReader(next(chunks)) - except StopIteration: - if len(chunk_buffer) > 0: - yield chunk_buffer - - def decode(cbor: bytes, offset: int = 0) -> Value: - r = utils.BufferReader(cbor) + + r = BufferReader(cbor) r.seek(offset) res = _cbor_decode(r) if r.remaining_count(): @@ -328,6 +305,10 @@ def create_embedded_cbor_bytes_header(size: int) -> bytes: return _header(_CBOR_TAG, _CBOR_RAW_TAG) + _header(_CBOR_BYTE_STRING, size) +def create_tagged_set_header(size: int) -> bytes: + return _header(_CBOR_TAG, _CBOR_SET_TAG) + _header(_CBOR_ARRAY, size) + + def precedes(prev: bytes, curr: bytes) -> bool: """ Returns True if `prev` is smaller than `curr` with regards to diff --git a/core/src/apps/common/keychain.py b/core/src/apps/common/keychain.py index 464532acb0..e7467755b2 100644 --- a/core/src/apps/common/keychain.py +++ b/core/src/apps/common/keychain.py @@ -150,17 +150,17 @@ def root_fingerprint(self) -> int: n = self._derive_with_cache( prefix_len=0, path=[0 | paths.HARDENED], - new_root=lambda: bip32.from_seed(self.seed, self.curve), + new_root=lambda: bip32.from_seed(self.seed, self.curve), # type: ignore[Argument of type "() -> HDNode" cannot be assigned to parameter "new_root" of type "() -> NodeType@_derive_with_cache" in function "_derive_with_cache"] ) - self._root_fingerprint = n.fingerprint() + self._root_fingerprint = n.fingerprint() # type: ignore[Cannot access member "fingerprint" for type "NodeProtocol[Unknown]*"] return self._root_fingerprint def derive(self, path: paths.Bip32Path, force_strict: bool = True) -> bip32.HDNode: self.verify_path(path, force_strict) - return self._derive_with_cache( + return self._derive_with_cache( # type: ignore[Expression of type "NodeType@_derive_with_cache" cannot be assigned to return type "HDNode"] prefix_len=3, path=path, - new_root=lambda: bip32.from_seed(self.seed, self.curve), + new_root=lambda: bip32.from_seed(self.seed, self.curve), # type: ignore[Argument of type "() -> HDNode" cannot be assigned to parameter "new_root" of type "() -> NodeType@_derive_with_cache" in function "_derive_with_cache"] ) def derive_slip21(self, path: paths.Slip21Path) -> Slip21Node: diff --git a/core/src/apps/common/paths.py b/core/src/apps/common/paths.py index bb7f9c3ba0..77c880e757 100644 --- a/core/src/apps/common/paths.py +++ b/core/src/apps/common/paths.py @@ -5,19 +5,11 @@ SLIP25_PURPOSE = const(10025 | HARDENED) if TYPE_CHECKING: - from typing import ( - Any, - Callable, - Collection, - Container, - Iterable, - Sequence, - TypeVar, - ) - from typing_extensions import Protocol + from typing import Any, Callable, Collection, Container, Iterable, Sequence, TypeVar from trezor import wire + from typing_extensions import Protocol - Bip32Path = Sequence[int] + Bip32Path = list[int] Slip21Path = Sequence[bytes] PathType = TypeVar("PathType", Bip32Path, Slip21Path, contravariant=True) @@ -197,23 +189,24 @@ def parse(cls, pattern: str, slip44_id: int | Iterable[int]) -> "PathSchema": # optionally replace a keyword component = cls.REPLACEMENTS.get(component, component) + append = schema.append # local_cache_attribute if "-" in component: # parse as a range a, b = [parse(s) for s in component.split("-", 1)] - schema.append(Interval(a, b)) + append(Interval(a, b)) elif "," in component: # parse as a list of values - schema.append(set(parse(s) for s in component.split(","))) + append(set(parse(s) for s in component.split(","))) elif component == "coin_type": # substitute SLIP-44 ids - schema.append(set(parse(s) for s in slip44_id)) + append(set(parse(s) for s in slip44_id)) else: # plain constant - schema.append((parse(component),)) + append((parse(component),)) return cls(schema, trailing_components, compact=True) @@ -258,18 +251,19 @@ def restrict(self, path: Bip32Path) -> bool: path. If the restriction results in a never-matching schema, then False is returned. """ + schema = self.schema # local_cache_attribute for i, value in enumerate(path): - if i < len(self.schema): + if i < len(schema): # Ensure that the path is a prefix of the schema. - if value not in self.schema[i]: + if value not in schema[i]: self.set_never_matching() return False # Restrict the schema component if there are multiple choices. - component = self.schema[i] + component = schema[i] if not isinstance(component, tuple) or len(component) != 1: - self.schema[i] = (value,) + schema[i] = (value,) else: # The path is longer than the schema. We need to restrict the # trailing components. @@ -278,7 +272,7 @@ def restrict(self, path: Bip32Path) -> bool: self.set_never_matching() return False - self.schema.append((value,)) + schema.append((value,)) return True @@ -286,15 +280,13 @@ def restrict(self, path: Bip32Path) -> bool: def __repr__(self) -> str: components = ["m"] - - def unharden(item: int) -> int: - return item ^ (item & HARDENED) + append = components.append # local_cache_attribute for component in self.schema: if isinstance(component, Interval): a, b = component.min, component.max prime = "'" if a & HARDENED else "" - components.append(f"[{unharden(a)}-{unharden(b)}]{prime}") + append(f"[{unharden(a)}-{unharden(b)}]{prime}") else: # typechecker thinks component is a Contanier but we're using it # as a Collection. @@ -307,15 +299,15 @@ def unharden(item: int) -> int: component_str = "[" + component_str + "]" if next(iter(collection)) & HARDENED: component_str += "'" - components.append(component_str) + append(component_str) if self.trailing_components: for key, val in self.WILDCARD_RANGES.items(): if self.trailing_components is val: - components.append(key) + append(key) break else: - components.append("???") + append("???") return "" @@ -326,12 +318,6 @@ def match(path: Bip32Path) -> bool: return True -class NeverMatchingSchema: - @staticmethod - def match(path: Bip32Path) -> bool: - return False - - # BIP-44 for basic (legacy) Bitcoin accounts, and widely used for other currencies: # https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki PATTERN_BIP44 = "m/44'/coin_type'/account'/change/*" @@ -376,7 +362,7 @@ def path_is_hardened(address_n: Bip32Path) -> bool: def address_n_to_str(address_n: Iterable[int]) -> str: - def path_item(i: int) -> str: + def _path_item(i: int) -> str: if i & HARDENED: return str(i ^ HARDENED) + "'" else: @@ -385,11 +371,11 @@ def path_item(i: int) -> str: if not address_n: return "m" - return "m/" + "/".join(path_item(i) for i in address_n) + return "m/" + "/".join(_path_item(i) for i in address_n) def parse_path(path: str) -> list[int]: - def parse_path_item(item: str) -> int: + def _parse_path_item(item: str) -> int: if item.endswith("'") or item.endswith("h") or item.endswith("H"): return HARDENED | int(item[:-1]) else: @@ -399,4 +385,51 @@ def parse_path_item(item: str) -> int: return [] if path.startswith("m/"): path = path[2:] - return [parse_path_item(item) for item in path.split("/")] + return [_parse_path_item(item) for item in path.split("/")] + + +def unharden(item: int) -> int: + return item ^ (item & HARDENED) + + +def get_account_name( + coin: str, address_n: Bip32Path, pattern: str | Sequence[str], slip44_id: int +) -> str | None: + account_num = _get_account_num(address_n, pattern, slip44_id) + if account_num is None: + return None + return f"{coin} #{account_num}" + + +def _get_account_num( + address_n: Bip32Path, pattern: str | Sequence[str], slip44_id: int +) -> int | None: + if isinstance(pattern, str): + pattern = [pattern] + + # Trying all possible patterns - at least one should match + for patt in pattern: + try: + return _get_account_num_single(address_n, patt, slip44_id) + except ValueError: + pass + + # This function should not raise + return None + + +def _get_account_num_single(address_n: Bip32Path, pattern: str, slip44_id: int) -> int: + # Validating address_n is compatible with pattern + if not PathSchema.parse(pattern, slip44_id).match(address_n): + raise ValueError + + account_pos = pattern.find("/account") + if account_pos >= 0: + i = pattern.count("/", 0, account_pos) + num = address_n[i] + if is_hardened(num): + return unharden(num) + 1 + else: + return num + 1 + else: + raise ValueError diff --git a/core/src/apps/tron/sign_message.py b/core/src/apps/tron/sign_message.py index 98568f4c6e..9e3253a69d 100644 --- a/core/src/apps/tron/sign_message.py +++ b/core/src/apps/tron/sign_message.py @@ -25,8 +25,8 @@ async def sign_message( ctx: Context, msg: TronSignMessage, keychain: Keychain ) -> TronMessageSignature: validate_message(msg.message) - address_n = msg.address_n or () - await paths.validate_path(ctx, keychain, msg.address_n) + address_n = msg.address_n + await paths.validate_path(ctx, keychain, address_n) node = keychain.derive(address_n) if utils.USE_THD89: diff --git a/core/src/apps/tron/sign_tx.py b/core/src/apps/tron/sign_tx.py index 34109a55c6..5a31da582a 100644 --- a/core/src/apps/tron/sign_tx.py +++ b/core/src/apps/tron/sign_tx.py @@ -20,8 +20,8 @@ async def sign_tx( """Parse and sign TRX transaction""" validate(msg) - address_n = msg.address_n or () - await paths.validate_path(ctx, keychain, msg.address_n) + address_n = msg.address_n + await paths.validate_path(ctx, keychain, address_n) node = keychain.derive(address_n) seckey = node.private_key() diff --git a/core/src/trezor/enums/CardanoGovernanceRegistrationFormat.py b/core/src/trezor/enums/CardanoCVoteRegistrationFormat.py similarity index 100% rename from core/src/trezor/enums/CardanoGovernanceRegistrationFormat.py rename to core/src/trezor/enums/CardanoCVoteRegistrationFormat.py diff --git a/core/src/trezor/enums/CardanoCertificateType.py b/core/src/trezor/enums/CardanoCertificateType.py index 4b7d241202..bc4abf6f20 100644 --- a/core/src/trezor/enums/CardanoCertificateType.py +++ b/core/src/trezor/enums/CardanoCertificateType.py @@ -6,3 +6,6 @@ STAKE_DEREGISTRATION = 1 STAKE_DELEGATION = 2 STAKE_POOL_REGISTRATION = 3 +STAKE_REGISTRATION_CONWAY = 7 +STAKE_DEREGISTRATION_CONWAY = 8 +VOTE_DELEGATION = 9 diff --git a/core/src/trezor/enums/CardanoDRepType.py b/core/src/trezor/enums/CardanoDRepType.py new file mode 100644 index 0000000000..bae3fbe218 --- /dev/null +++ b/core/src/trezor/enums/CardanoDRepType.py @@ -0,0 +1,8 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +KEY_HASH = 0 +SCRIPT_HASH = 1 +ABSTAIN = 2 +NO_CONFIDENCE = 3 diff --git a/core/src/trezor/enums/CardanoTxAuxiliaryDataSupplementType.py b/core/src/trezor/enums/CardanoTxAuxiliaryDataSupplementType.py index 3033edca2c..29ff1f41a4 100644 --- a/core/src/trezor/enums/CardanoTxAuxiliaryDataSupplementType.py +++ b/core/src/trezor/enums/CardanoTxAuxiliaryDataSupplementType.py @@ -3,4 +3,4 @@ # isort:skip_file NONE = 0 -GOVERNANCE_REGISTRATION_SIGNATURE = 1 +CVOTE_REGISTRATION_SIGNATURE = 1 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index dcb9631f88..ed4eb2e51d 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -557,6 +557,15 @@ class CardanoCertificateType(IntEnum): STAKE_DEREGISTRATION = 1 STAKE_DELEGATION = 2 STAKE_POOL_REGISTRATION = 3 + STAKE_REGISTRATION_CONWAY = 7 + STAKE_DEREGISTRATION_CONWAY = 8 + VOTE_DELEGATION = 9 + + class CardanoDRepType(IntEnum): + KEY_HASH = 0 + SCRIPT_HASH = 1 + ABSTAIN = 2 + NO_CONFIDENCE = 3 class CardanoPoolRelayType(IntEnum): SINGLE_HOST_IP = 0 @@ -565,9 +574,9 @@ class CardanoPoolRelayType(IntEnum): class CardanoTxAuxiliaryDataSupplementType(IntEnum): NONE = 0 - GOVERNANCE_REGISTRATION_SIGNATURE = 1 + CVOTE_REGISTRATION_SIGNATURE = 1 - class CardanoGovernanceRegistrationFormat(IntEnum): + class CardanoCVoteRegistrationFormat(IntEnum): CIP15 = 0 CIP36 = 1 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 1166da8df9..0588c11561 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -24,9 +24,10 @@ def __getattr__(name: str) -> Any: from trezor.enums import ButtonRequestType # noqa: F401 from trezor.enums import Capability # noqa: F401 from trezor.enums import CardanoAddressType # noqa: F401 + from trezor.enums import CardanoCVoteRegistrationFormat # noqa: F401 from trezor.enums import CardanoCertificateType # noqa: F401 + from trezor.enums import CardanoDRepType # noqa: F401 from trezor.enums import CardanoDerivationType # noqa: F401 - from trezor.enums import CardanoGovernanceRegistrationFormat # noqa: F401 from trezor.enums import CardanoNativeScriptHashDisplayFormat # noqa: F401 from trezor.enums import CardanoNativeScriptType # noqa: F401 from trezor.enums import CardanoPoolRelayType # noqa: F401 @@ -1804,6 +1805,7 @@ class CardanoGetAddress(protobuf.MessageType): network_id: "int" address_parameters: "CardanoAddressParametersType" derivation_type: "CardanoDerivationType" + chunkify: "bool | None" def __init__( self, @@ -1813,6 +1815,7 @@ def __init__( address_parameters: "CardanoAddressParametersType", derivation_type: "CardanoDerivationType", show_display: "bool | None" = None, + chunkify: "bool | None" = None, ) -> None: pass @@ -1890,6 +1893,8 @@ class CardanoSignTxInit(protobuf.MessageType): has_collateral_return: "bool" total_collateral: "int | None" reference_inputs_count: "int" + chunkify: "bool | None" + tag_cbor_sets: "bool" def __init__( self, @@ -1915,6 +1920,8 @@ def __init__( has_collateral_return: "bool | None" = None, total_collateral: "int | None" = None, reference_inputs_count: "int | None" = None, + chunkify: "bool | None" = None, + tag_cbor_sets: "bool | None" = None, ) -> None: pass @@ -2114,6 +2121,24 @@ def __init__( def is_type_of(cls, msg: Any) -> TypeGuard["CardanoPoolParametersType"]: return isinstance(msg, cls) + class CardanoDRep(protobuf.MessageType): + type: "CardanoDRepType" + key_hash: "bytes | None" + script_hash: "bytes | None" + + def __init__( + self, + *, + type: "CardanoDRepType", + key_hash: "bytes | None" = None, + script_hash: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["CardanoDRep"]: + return isinstance(msg, cls) + class CardanoTxCertificate(protobuf.MessageType): type: "CardanoCertificateType" path: "list[int]" @@ -2121,6 +2146,8 @@ class CardanoTxCertificate(protobuf.MessageType): pool_parameters: "CardanoPoolParametersType | None" script_hash: "bytes | None" key_hash: "bytes | None" + deposit: "int | None" + drep: "CardanoDRep | None" def __init__( self, @@ -2131,6 +2158,8 @@ def __init__( pool_parameters: "CardanoPoolParametersType | None" = None, script_hash: "bytes | None" = None, key_hash: "bytes | None" = None, + deposit: "int | None" = None, + drep: "CardanoDRep | None" = None, ) -> None: pass @@ -2158,56 +2187,58 @@ def __init__( def is_type_of(cls, msg: Any) -> TypeGuard["CardanoTxWithdrawal"]: return isinstance(msg, cls) - class CardanoGovernanceRegistrationDelegation(protobuf.MessageType): - voting_public_key: "bytes" + class CardanoCVoteRegistrationDelegation(protobuf.MessageType): + vote_public_key: "bytes" weight: "int" def __init__( self, *, - voting_public_key: "bytes", + vote_public_key: "bytes", weight: "int", ) -> None: pass @classmethod - def is_type_of(cls, msg: Any) -> TypeGuard["CardanoGovernanceRegistrationDelegation"]: + def is_type_of(cls, msg: Any) -> TypeGuard["CardanoCVoteRegistrationDelegation"]: return isinstance(msg, cls) - class CardanoGovernanceRegistrationParametersType(protobuf.MessageType): - voting_public_key: "bytes | None" + class CardanoCVoteRegistrationParametersType(protobuf.MessageType): + vote_public_key: "bytes | None" staking_path: "list[int]" - reward_address_parameters: "CardanoAddressParametersType" + payment_address_parameters: "CardanoAddressParametersType | None" nonce: "int" - format: "CardanoGovernanceRegistrationFormat" - delegations: "list[CardanoGovernanceRegistrationDelegation]" + format: "CardanoCVoteRegistrationFormat" + delegations: "list[CardanoCVoteRegistrationDelegation]" voting_purpose: "int | None" + payment_address: "str | None" def __init__( self, *, - reward_address_parameters: "CardanoAddressParametersType", nonce: "int", staking_path: "list[int] | None" = None, - delegations: "list[CardanoGovernanceRegistrationDelegation] | None" = None, - voting_public_key: "bytes | None" = None, - format: "CardanoGovernanceRegistrationFormat | None" = None, + delegations: "list[CardanoCVoteRegistrationDelegation] | None" = None, + vote_public_key: "bytes | None" = None, + payment_address_parameters: "CardanoAddressParametersType | None" = None, + format: "CardanoCVoteRegistrationFormat | None" = None, voting_purpose: "int | None" = None, + payment_address: "str | None" = None, ) -> None: pass @classmethod - def is_type_of(cls, msg: Any) -> TypeGuard["CardanoGovernanceRegistrationParametersType"]: + def is_type_of(cls, msg: Any) -> TypeGuard["CardanoCVoteRegistrationParametersType"]: return isinstance(msg, cls) class CardanoTxAuxiliaryData(protobuf.MessageType): - governance_registration_parameters: "CardanoGovernanceRegistrationParametersType | None" + cvote_registration_parameters: "CardanoCVoteRegistrationParametersType | None" hash: "bytes | None" def __init__( self, *, - governance_registration_parameters: "CardanoGovernanceRegistrationParametersType | None" = None, + cvote_registration_parameters: "CardanoCVoteRegistrationParametersType | None" = None, hash: "bytes | None" = None, ) -> None: pass @@ -2287,14 +2318,14 @@ def is_type_of(cls, msg: Any) -> TypeGuard["CardanoTxItemAck"]: class CardanoTxAuxiliaryDataSupplement(protobuf.MessageType): type: "CardanoTxAuxiliaryDataSupplementType" auxiliary_data_hash: "bytes | None" - governance_signature: "bytes | None" + cvote_registration_signature: "bytes | None" def __init__( self, *, type: "CardanoTxAuxiliaryDataSupplementType", auxiliary_data_hash: "bytes | None" = None, - governance_signature: "bytes | None" = None, + cvote_registration_signature: "bytes | None" = None, ) -> None: pass diff --git a/python/src/trezorlib/cardano.py b/python/src/trezorlib/cardano.py index 481665c171..f9597c90f7 100644 --- a/python/src/trezorlib/cardano.py +++ b/python/src/trezorlib/cardano.py @@ -61,12 +61,11 @@ "owners", ) REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens") -REQUIRED_FIELDS_GOVERNANCE_REGISTRATION = ( +REQUIRED_FIELDS_CVOTE_REGISTRATION = ( "staking_path", "nonce", - "reward_address_parameters", ) -REQUIRED_FIELDS_GOVERNANCE_DELEGATION = ("voting_public_key", "weight") +REQUIRED_FIELDS_CVOTE_DELEGATION = ("vote_public_key", "weight") INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields" @@ -421,6 +420,27 @@ def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays: ), None, ) + elif certificate_type in ( + messages.CardanoCertificateType.STAKE_REGISTRATION_CONWAY, + messages.CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, + ): + if "deposit" not in certificate: + raise CERTIFICATE_MISSING_FIELDS_ERROR + + path, script_hash, key_hash = _parse_credential( + certificate, CERTIFICATE_MISSING_FIELDS_ERROR + ) + + return ( + messages.CardanoTxCertificate( + type=certificate_type, + path=path, + script_hash=script_hash, + key_hash=key_hash, + deposit=int(certificate["deposit"]), + ), + None, + ) elif certificate_type == messages.CardanoCertificateType.STAKE_POOL_REGISTRATION: pool_parameters = certificate["pool_parameters"] @@ -466,6 +486,30 @@ def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays: ), (owners, relays), ) + if certificate_type == messages.CardanoCertificateType.VOTE_DELEGATION: + if "drep" not in certificate: + raise CERTIFICATE_MISSING_FIELDS_ERROR + + path, script_hash, key_hash = _parse_credential( + certificate, CERTIFICATE_MISSING_FIELDS_ERROR + ) + + return ( + messages.CardanoTxCertificate( + type=certificate_type, + path=path, + script_hash=script_hash, + key_hash=key_hash, + drep=messages.CardanoDRep( + type=messages.CardanoDRepType(certificate["drep"]["type"]), + key_hash=parse_optional_bytes(certificate["drep"].get("key_hash")), + script_hash=parse_optional_bytes( + certificate["drep"].get("script_hash") + ), + ), + ), + None, + ) else: raise ValueError("Unknown certificate type") @@ -563,55 +607,55 @@ def parse_auxiliary_data( # include all provided fields so we can test validation in FW hash = parse_optional_bytes(auxiliary_data.get("hash")) - governance_registration_parameters = None - if "governance_registration_parameters" in auxiliary_data: - governance_registration = auxiliary_data["governance_registration_parameters"] - if not all( - k in governance_registration - for k in REQUIRED_FIELDS_GOVERNANCE_REGISTRATION - ): + cvote_registration_parameters = None + if "cvote_registration_parameters" in auxiliary_data: + cvote_registration = auxiliary_data["cvote_registration_parameters"] + if not all(k in cvote_registration for k in REQUIRED_FIELDS_CVOTE_REGISTRATION): raise AUXILIARY_DATA_MISSING_FIELDS_ERROR - serialization_format = governance_registration.get("format") + serialization_format = cvote_registration.get("format") delegations = [] - for delegation in governance_registration.get("delegations", []): - if not all(k in delegation for k in REQUIRED_FIELDS_GOVERNANCE_DELEGATION): + for delegation in cvote_registration.get("delegations", []): + if not all(k in delegation for k in REQUIRED_FIELDS_CVOTE_DELEGATION): raise AUXILIARY_DATA_MISSING_FIELDS_ERROR delegations.append( - messages.CardanoGovernanceRegistrationDelegation( - voting_public_key=bytes.fromhex(delegation["voting_public_key"]), + messages.CardanoCVoteRegistrationDelegation( + vote_public_key=bytes.fromhex(delegation["vote_public_key"]), weight=int(delegation["weight"]), ) ) voting_purpose = None - if serialization_format == messages.CardanoGovernanceRegistrationFormat.CIP36: - voting_purpose = governance_registration.get("voting_purpose") + if serialization_format == messages.CardanoCVoteRegistrationFormat.CIP36: + voting_purpose = cvote_registration.get("voting_purpose") - governance_registration_parameters = ( - messages.CardanoGovernanceRegistrationParametersType( - voting_public_key=parse_optional_bytes( - governance_registration.get("voting_public_key") - ), - staking_path=tools.parse_path(governance_registration["staking_path"]), - nonce=governance_registration["nonce"], - reward_address_parameters=_parse_address_parameters( - governance_registration["reward_address_parameters"], + cvote_registration_parameters = messages.CardanoCVoteRegistrationParametersType( + vote_public_key=parse_optional_bytes( + cvote_registration.get("vote_public_key") + ), + staking_path=tools.parse_path(cvote_registration["staking_path"]), + nonce=cvote_registration["nonce"], + payment_address=cvote_registration.get("payment_address"), + payment_address_parameters=( + _parse_address_parameters( + cvote_registration["payment_address_parameters"], str(AUXILIARY_DATA_MISSING_FIELDS_ERROR), - ), - format=serialization_format, - delegations=delegations, - voting_purpose=voting_purpose, - ) + ) + if "payment_address_parameters" in cvote_registration + else None + ), + format=serialization_format, + delegations=delegations, + voting_purpose=voting_purpose, ) - if hash is None and governance_registration_parameters is None: + if hash is None and cvote_registration_parameters is None: raise AUXILIARY_DATA_MISSING_FIELDS_ERROR return messages.CardanoTxAuxiliaryData( hash=hash, - governance_registration_parameters=governance_registration_parameters, + cvote_registration_parameters=cvote_registration_parameters, ) @@ -687,6 +731,9 @@ def _get_witness_requests( in ( messages.CardanoCertificateType.STAKE_DEREGISTRATION, messages.CardanoCertificateType.STAKE_DELEGATION, + messages.CardanoCertificateType.STAKE_REGISTRATION_CONWAY, + messages.CardanoCertificateType.STAKE_DEREGISTRATION_CONWAY, + messages.CardanoCertificateType.VOTE_DELEGATION, ) and certificate.path ): @@ -784,6 +831,7 @@ def get_address( network_id: int = NETWORK_IDS["mainnet"], show_display: bool = False, derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, + chunkify: bool = False, ) -> "MessageType": return client.call( messages.CardanoGetAddress( @@ -792,6 +840,7 @@ def get_address( network_id=network_id, show_display=show_display, derivation_type=derivation_type, + chunkify=chunkify, ) ) @@ -801,10 +850,13 @@ def get_public_key( client: "TrezorClient", address_n: List[int], derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, + show_display: bool = False, ) -> "MessageType": return client.call( messages.CardanoGetPublicKey( - address_n=address_n, derivation_type=derivation_type + address_n=address_n, + derivation_type=derivation_type, + show_display=show_display, ) ) @@ -848,6 +900,8 @@ def sign_tx( additional_witness_requests: Sequence[Path] = (), derivation_type: messages.CardanoDerivationType = messages.CardanoDerivationType.ICARUS, include_network_id: bool = False, + chunkify: bool = False, + tag_cbor_sets: bool = False, ) -> Dict[str, Any]: UNEXPECTED_RESPONSE_ERROR = exceptions.TrezorException("Unexpected response") @@ -884,6 +938,8 @@ def sign_tx( witness_requests_count=len(witness_requests), derivation_type=derivation_type, include_network_id=include_network_id, + chunkify=chunkify, + tag_cbor_sets=tag_cbor_sets, ) ) if not isinstance(response, messages.CardanoTxItemAck): @@ -911,9 +967,9 @@ def sign_tx( auxiliary_data_supplement.type != messages.CardanoTxAuxiliaryDataSupplementType.NONE ): - sign_tx_response[ - "auxiliary_data_supplement" - ] = auxiliary_data_supplement.__dict__ + sign_tx_response["auxiliary_data_supplement"] = ( + auxiliary_data_supplement.__dict__ + ) response = client.call(messages.CardanoTxHostAck()) if not isinstance(response, messages.CardanoTxItemAck): diff --git a/python/src/trezorlib/cli/cardano.py b/python/src/trezorlib/cli/cardano.py index 6b6e0b1702..561238bb62 100644 --- a/python/src/trezorlib/cli/cardano.py +++ b/python/src/trezorlib/cli/cardano.py @@ -25,7 +25,7 @@ if TYPE_CHECKING: from ..client import TrezorClient -PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0" +PATH_HELP = "BIP-32 path to key, e.g. m/44h/1815h/0h/0/0" TESTNET_CHOICES = { "preprod": "testnet_preprod", @@ -60,6 +60,8 @@ def cli() -> None: default=messages.CardanoDerivationType.ICARUS, ) @click.option("-i", "--include-network-id", is_flag=True) +@click.option("-C", "chunkify", is_flag=True) +@click.option("-T", "--tag-cbor-sets", is_flag=True) @with_client def sign_tx( client: "TrezorClient", @@ -70,6 +72,8 @@ def sign_tx( testnet: str, derivation_type: messages.CardanoDerivationType, include_network_id: bool, + chunkify: bool, + tag_cbor_sets: bool, ) -> cardano.SignTxResponse: """Sign Cardano transaction.""" transaction = json.load(file) @@ -143,6 +147,8 @@ def sign_tx( additional_witness_requests, derivation_type=derivation_type, include_network_id=include_network_id, + chunkify=chunkify, + tag_cbor_sets=tag_cbor_sets, ) sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex() @@ -151,9 +157,11 @@ def sign_tx( "type": witness["type"], "pub_key": witness["pub_key"].hex(), "signature": witness["signature"].hex(), - "chain_code": witness["chain_code"].hex() - if witness["chain_code"] is not None - else None, + "chain_code": ( + witness["chain_code"].hex() + if witness["chain_code"] is not None + else None + ), } for witness in sign_tx_response["witnesses"] ] @@ -162,11 +170,13 @@ def sign_tx( auxiliary_data_supplement["auxiliary_data_hash"] = auxiliary_data_supplement[ "auxiliary_data_hash" ].hex() - governance_signature = auxiliary_data_supplement.get("governance_signature") - if governance_signature: - auxiliary_data_supplement[ - "governance_signature" - ] = governance_signature.hex() + cvote_registration_signature = auxiliary_data_supplement.get( + "cvote_registration_signature" + ) + if cvote_registration_signature: + auxiliary_data_supplement["cvote_registration_signature"] = ( + cvote_registration_signature.hex() + ) sign_tx_response["auxiliary_data_supplement"] = auxiliary_data_supplement return sign_tx_response @@ -198,6 +208,7 @@ def sign_tx( type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) +@click.option("-C", "--chunkify", is_flag=True) @with_client def get_address( client: "TrezorClient", @@ -215,6 +226,7 @@ def get_address( show_display: bool, testnet: str, derivation_type: messages.CardanoDerivationType, + chunkify: bool, ) -> str: """ Get Cardano address. @@ -258,6 +270,7 @@ def get_address( network_id, show_display, derivation_type=derivation_type, + chunkify=chunkify, ) @@ -269,16 +282,20 @@ def get_address( type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) +@click.option("-d", "--show-display", is_flag=True) @with_client def get_public_key( client: "TrezorClient", address: str, derivation_type: messages.CardanoDerivationType, + show_display: bool, ) -> messages.CardanoPublicKey: """Get Cardano public key.""" address_n = tools.parse_path(address) client.init_device(derive_cardano=True) - return cardano.get_public_key(client, address_n, derivation_type=derivation_type) + return cardano.get_public_key( + client, address_n, derivation_type=derivation_type, show_display=show_display + ) @cli.command() diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 98dae852fb..d5ecddb569 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -585,6 +585,16 @@ class CardanoCertificateType(IntEnum): STAKE_DEREGISTRATION = 1 STAKE_DELEGATION = 2 STAKE_POOL_REGISTRATION = 3 + STAKE_REGISTRATION_CONWAY = 7 + STAKE_DEREGISTRATION_CONWAY = 8 + VOTE_DELEGATION = 9 + + +class CardanoDRepType(IntEnum): + KEY_HASH = 0 + SCRIPT_HASH = 1 + ABSTAIN = 2 + NO_CONFIDENCE = 3 class CardanoPoolRelayType(IntEnum): @@ -595,10 +605,10 @@ class CardanoPoolRelayType(IntEnum): class CardanoTxAuxiliaryDataSupplementType(IntEnum): NONE = 0 - GOVERNANCE_REGISTRATION_SIGNATURE = 1 + CVOTE_REGISTRATION_SIGNATURE = 1 -class CardanoGovernanceRegistrationFormat(IntEnum): +class CardanoCVoteRegistrationFormat(IntEnum): CIP15 = 0 CIP36 = 1 @@ -3063,6 +3073,7 @@ class CardanoGetAddress(protobuf.MessageType): 4: protobuf.Field("network_id", "uint32", repeated=False, required=True), 5: protobuf.Field("address_parameters", "CardanoAddressParametersType", repeated=False, required=True), 6: protobuf.Field("derivation_type", "CardanoDerivationType", repeated=False, required=True), + 7: protobuf.Field("chunkify", "bool", repeated=False, required=False), } def __init__( @@ -3073,12 +3084,14 @@ def __init__( address_parameters: "CardanoAddressParametersType", derivation_type: "CardanoDerivationType", show_display: Optional["bool"] = False, + chunkify: Optional["bool"] = None, ) -> None: self.protocol_magic = protocol_magic self.network_id = network_id self.address_parameters = address_parameters self.derivation_type = derivation_type self.show_display = show_display + self.chunkify = chunkify class CardanoAddress(protobuf.MessageType): @@ -3156,6 +3169,8 @@ class CardanoSignTxInit(protobuf.MessageType): 19: protobuf.Field("has_collateral_return", "bool", repeated=False, required=False), 20: protobuf.Field("total_collateral", "uint64", repeated=False, required=False), 21: protobuf.Field("reference_inputs_count", "uint32", repeated=False, required=False), + 22: protobuf.Field("chunkify", "bool", repeated=False, required=False), + 23: protobuf.Field("tag_cbor_sets", "bool", repeated=False, required=False), } def __init__( @@ -3182,6 +3197,8 @@ def __init__( has_collateral_return: Optional["bool"] = False, total_collateral: Optional["int"] = None, reference_inputs_count: Optional["int"] = 0, + chunkify: Optional["bool"] = None, + tag_cbor_sets: Optional["bool"] = False, ) -> None: self.signing_mode = signing_mode self.protocol_magic = protocol_magic @@ -3204,6 +3221,8 @@ def __init__( self.has_collateral_return = has_collateral_return self.total_collateral = total_collateral self.reference_inputs_count = reference_inputs_count + self.chunkify = chunkify + self.tag_cbor_sets = tag_cbor_sets class CardanoTxInput(protobuf.MessageType): @@ -3424,6 +3443,26 @@ def __init__( self.metadata = metadata +class CardanoDRep(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("type", "CardanoDRepType", repeated=False, required=True), + 2: protobuf.Field("key_hash", "bytes", repeated=False, required=False), + 3: protobuf.Field("script_hash", "bytes", repeated=False, required=False), + } + + def __init__( + self, + *, + type: "CardanoDRepType", + key_hash: Optional["bytes"] = None, + script_hash: Optional["bytes"] = None, + ) -> None: + self.type = type + self.key_hash = key_hash + self.script_hash = script_hash + + class CardanoTxCertificate(protobuf.MessageType): MESSAGE_WIRE_TYPE = 325 FIELDS = { @@ -3433,6 +3472,8 @@ class CardanoTxCertificate(protobuf.MessageType): 4: protobuf.Field("pool_parameters", "CardanoPoolParametersType", repeated=False, required=False), 5: protobuf.Field("script_hash", "bytes", repeated=False, required=False), 6: protobuf.Field("key_hash", "bytes", repeated=False, required=False), + 7: protobuf.Field("deposit", "uint64", repeated=False, required=False), + 8: protobuf.Field("drep", "CardanoDRep", repeated=False, required=False), } def __init__( @@ -3444,6 +3485,8 @@ def __init__( pool_parameters: Optional["CardanoPoolParametersType"] = None, script_hash: Optional["bytes"] = None, key_hash: Optional["bytes"] = None, + deposit: Optional["int"] = None, + drep: Optional["CardanoDRep"] = None, ) -> None: self.path: Sequence["int"] = path if path is not None else [] self.type = type @@ -3451,6 +3494,8 @@ def __init__( self.pool_parameters = pool_parameters self.script_hash = script_hash self.key_hash = key_hash + self.deposit = deposit + self.drep = drep class CardanoTxWithdrawal(protobuf.MessageType): @@ -3476,69 +3521,72 @@ def __init__( self.key_hash = key_hash -class CardanoGovernanceRegistrationDelegation(protobuf.MessageType): +class CardanoCVoteRegistrationDelegation(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { - 1: protobuf.Field("voting_public_key", "bytes", repeated=False, required=True), + 1: protobuf.Field("vote_public_key", "bytes", repeated=False, required=True), 2: protobuf.Field("weight", "uint32", repeated=False, required=True), } def __init__( self, *, - voting_public_key: "bytes", + vote_public_key: "bytes", weight: "int", ) -> None: - self.voting_public_key = voting_public_key + self.vote_public_key = vote_public_key self.weight = weight -class CardanoGovernanceRegistrationParametersType(protobuf.MessageType): +class CardanoCVoteRegistrationParametersType(protobuf.MessageType): MESSAGE_WIRE_TYPE = None FIELDS = { - 1: protobuf.Field("voting_public_key", "bytes", repeated=False, required=False), + 1: protobuf.Field("vote_public_key", "bytes", repeated=False, required=False), 2: protobuf.Field("staking_path", "uint32", repeated=True, required=False), - 3: protobuf.Field("reward_address_parameters", "CardanoAddressParametersType", repeated=False, required=True), + 3: protobuf.Field("payment_address_parameters", "CardanoAddressParametersType", repeated=False, required=False), 4: protobuf.Field("nonce", "uint64", repeated=False, required=True), - 5: protobuf.Field("format", "CardanoGovernanceRegistrationFormat", repeated=False, required=False), - 6: protobuf.Field("delegations", "CardanoGovernanceRegistrationDelegation", repeated=True, required=False), + 5: protobuf.Field("format", "CardanoCVoteRegistrationFormat", repeated=False, required=False), + 6: protobuf.Field("delegations", "CardanoCVoteRegistrationDelegation", repeated=True, required=False), 7: protobuf.Field("voting_purpose", "uint64", repeated=False, required=False), + 8: protobuf.Field("payment_address", "string", repeated=False, required=False), } def __init__( self, *, - reward_address_parameters: "CardanoAddressParametersType", nonce: "int", staking_path: Optional[Sequence["int"]] = None, - delegations: Optional[Sequence["CardanoGovernanceRegistrationDelegation"]] = None, - voting_public_key: Optional["bytes"] = None, - format: Optional["CardanoGovernanceRegistrationFormat"] = CardanoGovernanceRegistrationFormat.CIP15, + delegations: Optional[Sequence["CardanoCVoteRegistrationDelegation"]] = None, + vote_public_key: Optional["bytes"] = None, + payment_address_parameters: Optional["CardanoAddressParametersType"] = None, + format: Optional["CardanoCVoteRegistrationFormat"] = CardanoCVoteRegistrationFormat.CIP15, voting_purpose: Optional["int"] = None, + payment_address: Optional["str"] = None, ) -> None: self.staking_path: Sequence["int"] = staking_path if staking_path is not None else [] - self.delegations: Sequence["CardanoGovernanceRegistrationDelegation"] = delegations if delegations is not None else [] - self.reward_address_parameters = reward_address_parameters + self.delegations: Sequence["CardanoCVoteRegistrationDelegation"] = delegations if delegations is not None else [] self.nonce = nonce - self.voting_public_key = voting_public_key + self.vote_public_key = vote_public_key + self.payment_address_parameters = payment_address_parameters self.format = format self.voting_purpose = voting_purpose + self.payment_address = payment_address class CardanoTxAuxiliaryData(protobuf.MessageType): MESSAGE_WIRE_TYPE = 327 FIELDS = { - 1: protobuf.Field("governance_registration_parameters", "CardanoGovernanceRegistrationParametersType", repeated=False, required=False), + 1: protobuf.Field("cvote_registration_parameters", "CardanoCVoteRegistrationParametersType", repeated=False, required=False), 2: protobuf.Field("hash", "bytes", repeated=False, required=False), } def __init__( self, *, - governance_registration_parameters: Optional["CardanoGovernanceRegistrationParametersType"] = None, + cvote_registration_parameters: Optional["CardanoCVoteRegistrationParametersType"] = None, hash: Optional["bytes"] = None, ) -> None: - self.governance_registration_parameters = governance_registration_parameters + self.cvote_registration_parameters = cvote_registration_parameters self.hash = hash @@ -3616,7 +3664,7 @@ class CardanoTxAuxiliaryDataSupplement(protobuf.MessageType): FIELDS = { 1: protobuf.Field("type", "CardanoTxAuxiliaryDataSupplementType", repeated=False, required=True), 2: protobuf.Field("auxiliary_data_hash", "bytes", repeated=False, required=False), - 3: protobuf.Field("governance_signature", "bytes", repeated=False, required=False), + 3: protobuf.Field("cvote_registration_signature", "bytes", repeated=False, required=False), } def __init__( @@ -3624,11 +3672,11 @@ def __init__( *, type: "CardanoTxAuxiliaryDataSupplementType", auxiliary_data_hash: Optional["bytes"] = None, - governance_signature: Optional["bytes"] = None, + cvote_registration_signature: Optional["bytes"] = None, ) -> None: self.type = type self.auxiliary_data_hash = auxiliary_data_hash - self.governance_signature = governance_signature + self.cvote_registration_signature = cvote_registration_signature class CardanoTxWitnessRequest(protobuf.MessageType):