From ac58c1c25b91b9728638b59858f11a4d97de0a88 Mon Sep 17 00:00:00 2001 From: David Misiak Date: Sat, 10 Dec 2022 18:25:44 +0100 Subject: [PATCH] feat(cardano): show governance registration reward address credentials --- core/src/apps/cardano/addresses.py | 2 + core/src/apps/cardano/auxiliary_data.py | 47 ++++++++---- core/src/apps/cardano/layout.py | 96 +++++++++++++++++++------ 3 files changed, 108 insertions(+), 37 deletions(-) diff --git a/core/src/apps/cardano/addresses.py b/core/src/apps/cardano/addresses.py index 089509c695..70a673bf63 100644 --- a/core/src/apps/cardano/addresses.py +++ b/core/src/apps/cardano/addresses.py @@ -44,6 +44,8 @@ ADDRESS_TYPES_PAYMENT_SCRIPT = ( CardanoAddressType.ENTERPRISE_SCRIPT, ) +ADDRESS_TYPES_PAYMENT = ADDRESS_TYPES_PAYMENT_KEY + ADDRESS_TYPES_PAYMENT_SCRIPT + _MIN_ADDRESS_BYTES_LENGTH = const(29) _MAX_ADDRESS_BYTES_LENGTH = const(65) diff --git a/core/src/apps/cardano/auxiliary_data.py b/core/src/apps/cardano/auxiliary_data.py index 928cc978b7..34016e0c50 100644 --- a/core/src/apps/cardano/auxiliary_data.py +++ b/core/src/apps/cardano/auxiliary_data.py @@ -2,7 +2,7 @@ from micropython import const from typing import TYPE_CHECKING from trezor.crypto import hashlib -from trezor.enums import CardanoGovernanceRegistrationFormat +from trezor.enums import CardanoAddressType, CardanoGovernanceRegistrationFormat from apps.common import cbor @@ -137,6 +137,14 @@ async def show( await layout.show_auxiliary_data_hash(ctx, auxiliary_data_hash) +def _should_show_payment_warning(address_type: CardanoAddressType) -> bool: + # For non-payment governance reward addresses, we show a warning that the address is not + # actually 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_governance_registration( ctx: Context, keychain: seed.Keychain, @@ -146,6 +154,7 @@ async def _show_governance_registration( 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( @@ -155,25 +164,34 @@ async def _show_governance_registration( ctx, encoded_public_key, delegation.weight ) + if parameters.reward_address: + show_payment_warning = _should_show_payment_warning( + addresses.get_type(addresses.get_bytes_unsafe(parameters.reward_address)) + ) + await layout.confirm_governance_registration_reward_address( + ctx, parameters.reward_address, show_payment_warning + ) + else: + address_parameters = parameters.reward_address_parameters + assert address_parameters # _validate_governance_registration_parameters + show_both_credentials = should_show_credentials(address_parameters) + show_payment_warning = _should_show_payment_warning( + address_parameters.address_type + ) + await layout.show_governance_registration_reward_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: encoded_public_key = bech32.encode( bech32.HRP_GOVERNANCE_PUBLIC_KEY, parameters.voting_public_key ) - if parameters.reward_address: - reward_address = parameters.reward_address - else: - assert ( - parameters.reward_address_parameters - ) # _validate_governance_registration_parameters - 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 ) @@ -182,7 +200,6 @@ async def _show_governance_registration( ctx, encoded_public_key, parameters.staking_path, - reward_address, parameters.nonce, voting_purpose, ) diff --git a/core/src/apps/cardano/layout.py b/core/src/apps/cardano/layout.py index 5ad4a8541c..dd5df1af5b 100644 --- a/core/src/apps/cardano/layout.py +++ b/core/src/apps/cardano/layout.py @@ -68,6 +68,10 @@ CERTIFICATE_TYPE_NAMES = { BRT_Other = ButtonRequestType.Other # global_import_cache +GOVERNANCE_REWARD_ELIGIBILITY_WARNING = ( + "Warning: The address is not a payment address, it is not eligible for rewards." +) + def format_coin_amount(amount: int, network_id: int) -> str: from .helpers import network_ids @@ -327,8 +331,8 @@ 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( @@ -337,8 +341,8 @@ async def show_change_output_credentials( 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( @@ -348,22 +352,47 @@ async def show_device_owned_output_credentials( 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_governance_registration_reward_credentials( + ctx: Context, + payment_credential: Credential, + stake_credential: Credential, + show_both_credentials: bool, + show_payment_warning: bool, +) -> None: + intro_text = "The governance registration reward address is owned by this device. Its" + await _show_credential( + ctx, payment_credential, intro_text, purpose="gov_reg_reward_address" + ) + if show_both_credentials or show_payment_warning: + extra_text = ( + GOVERNANCE_REWARD_ELIGIBILITY_WARNING if show_payment_warning else None + ) + await _show_credential( + ctx, + stake_credential, + intro_text, + purpose="gov_reg_reward_address", + extra_text=extra_text, + ) async def _show_credential( ctx: Context, credential: Credential, intro_text: str, - is_output: bool, + purpose: Literal["address", "output", "gov_reg_reward_address"], + extra_text: str | None = None, ) -> None: - title = ( - "Confirm transaction" - if is_output - else f"{ADDRESS_TYPE_NAMES[credential.address_type]} address" - ) + title = { + "address": f"{ADDRESS_TYPE_NAMES[credential.address_type]} address", + "output": "Confirm transaction", + "gov_reg_reward_address": "Confirm transaction", + }[purpose] props: list[PropertyType] = [] append = props.append # local_cache_attribute @@ -385,7 +414,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 != "gov_reg_reward_address": + # for governance registrations, this is handled by extra_text at the end append(("Address is a reward address.", None)) if credential.is_no_staking: append( @@ -395,13 +425,17 @@ 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: @@ -762,11 +796,30 @@ async def confirm_governance_registration_delegation( ) +async def confirm_governance_registration_reward_address( + ctx: Context, + reward_address: str, + should_show_payment_warning: bool, +) -> None: + props = [ + ("Governance voting key registration", None), + ("Rewards go to:", reward_address), + ] + if should_show_payment_warning: + props.append((GOVERNANCE_REWARD_ELIGIBILITY_WARNING, None)) + await confirm_properties( + ctx, + "confirm_governance_registration_reward_address", + title="Confirm transaction", + props=props, + br_code=ButtonRequestType.Other, + ) + + async def confirm_governance_registration( ctx: Context, public_key: str | None, staking_path: list[int], - reward_address: str, nonce: int, voting_purpose: int | None, ) -> None: @@ -779,7 +832,6 @@ async def confirm_governance_registration( f"Staking key for account {format_account_number(staking_path)}:", address_n_to_str(staking_path), ), - ("Rewards go to:", reward_address), ("Nonce:", str(nonce)), ] )