From a94cfa1a13fef60fa1ac683a3132166580629cf4 Mon Sep 17 00:00:00 2001 From: David Misiak Date: Tue, 6 Dec 2022 20:05:27 +0100 Subject: [PATCH] feat(cardano): allow external reward addresses in governance registrations --- common/protob/messages-cardano.proto | 5 +- core/src/apps/cardano/addresses.py | 17 +++++++ core/src/apps/cardano/auxiliary_data.py | 64 ++++++++++++++++++------- core/src/apps/cardano/sign_tx/signer.py | 2 +- core/src/trezor/messages.py | 6 ++- python/src/trezorlib/cardano.py | 6 ++- python/src/trezorlib/messages.py | 9 ++-- 7 files changed, 81 insertions(+), 28 deletions(-) diff --git a/common/protob/messages-cardano.proto b/common/protob/messages-cardano.proto index 05c37d5d78..1263ca305d 100644 --- a/common/protob/messages-cardano.proto +++ b/common/protob/messages-cardano.proto @@ -369,13 +369,14 @@ message CardanoGovernanceRegistrationDelegation { * @embed */ message CardanoGovernanceRegistrationParametersType { - optional bytes voting_public_key = 1; + optional bytes voting_public_key = 1; // mutually exclusive with delegations repeated uint32 staking_path = 2; - required CardanoAddressParametersType reward_address_parameters = 3; + optional CardanoAddressParametersType reward_address_parameters = 3; // mutually exclusive with reward_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 uint64 voting_purpose = 7; + optional string reward_address = 8; // mutually exclusive with reward_address_parameters } /** diff --git a/core/src/apps/cardano/addresses.py b/core/src/apps/cardano/addresses.py index 55e0e2b5f4..089509c695 100644 --- a/core/src/apps/cardano/addresses.py +++ b/core/src/apps/cardano/addresses.py @@ -231,6 +231,16 @@ def validate_output_address_parameters( assert_params_cond(parameters.address_type in ADDRESS_TYPES_PAYMENT_KEY) +def validate_governance_reward_address_parameters( + parameters: messages.CardanoAddressParametersType, +) -> None: + validate_address_parameters(parameters) + + # Despite the name, the address doesn't have to be a REWARD address. + # see also validate_governance_reward_address + assert_params_cond(parameters.address_type in ADDRESS_TYPES_SHELLEY) + + def assert_cond(condition: bool) -> None: if not condition: raise ProcessError("Invalid address") @@ -287,6 +297,13 @@ def validate_reward_address(address: str, protocol_magic: int, network_id: int) ) +def validate_governance_reward_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) diff --git a/core/src/apps/cardano/auxiliary_data.py b/core/src/apps/cardano/auxiliary_data.py index ea63eae7ae..928cc978b7 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 CardanoAddressType, CardanoGovernanceRegistrationFormat +from trezor.enums import CardanoGovernanceRegistrationFormat from apps.common import cbor @@ -38,7 +38,11 @@ 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 @@ -47,13 +51,17 @@ def validate(auxiliary_data: messages.CardanoTxAuxiliaryData) -> None: if auxiliary_data.governance_registration_parameters: fields_provided += 1 _validate_governance_registration_parameters( - auxiliary_data.governance_registration_parameters + auxiliary_data.governance_registration_parameters, + protocol_magic, + network_id, ) assert_cond(fields_provided == 1) def _validate_governance_registration_parameters( parameters: messages.CardanoGovernanceRegistrationParametersType, + protocol_magic: int, + network_id: int, ) -> None: voting_key_fields_provided = 0 if parameters.voting_public_key is not None: @@ -67,9 +75,18 @@ def _validate_governance_registration_parameters( 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) + reward_address_fields_provided = 0 + if parameters.reward_address is not None: + reward_address_fields_provided += 1 + addresses.validate_governance_reward_address( + parameters.reward_address, protocol_magic, network_id + ) + if parameters.reward_address_parameters: + reward_address_fields_provided += 1 + addresses.validate_governance_reward_address_parameters( + parameters.reward_address_parameters + ) + assert_cond(reward_address_fields_provided == 1) if parameters.voting_purpose is not None: assert_cond(parameters.format == CardanoGovernanceRegistrationFormat.CIP36) @@ -144,12 +161,18 @@ async def _show_governance_registration( bech32.HRP_GOVERNANCE_PUBLIC_KEY, parameters.voting_public_key ) - reward_address = addresses.derive_human_readable( - keychain, - parameters.reward_address_parameters, - protocol_magic, - network_id, - ) + 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 @@ -241,12 +264,17 @@ def _get_signed_governance_registration_payload( 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.reward_address: + reward_address = addresses.get_bytes_unsafe(parameters.reward_address) + else: + address_parameters = parameters.reward_address_parameters + assert address_parameters # _validate_governance_registration_parameters + reward_address = addresses.derive_bytes( + keychain, + address_parameters, + protocol_magic, + network_id, + ) voting_purpose = _get_voting_purpose_to_serialize(parameters) diff --git a/core/src/apps/cardano/sign_tx/signer.py b/core/src/apps/cardano/sign_tx/signer.py index a739899865..ceb544cc00 100644 --- a/core/src/apps/cardano/sign_tx/signer.py +++ b/core/src/apps/cardano/sign_tx/signer.py @@ -824,7 +824,7 @@ class Signer: data: messages.CardanoTxAuxiliaryData = await self.ctx.call( CardanoTxItemAck(), messages.CardanoTxAuxiliaryData ) - auxiliary_data.validate(data) + auxiliary_data.validate(data, msg.protocol_magic, msg.network_id) ( auxiliary_data_hash, diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 773448a93f..c12c5e90cd 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -1657,22 +1657,24 @@ if TYPE_CHECKING: class CardanoGovernanceRegistrationParametersType(protobuf.MessageType): voting_public_key: "bytes | None" staking_path: "list[int]" - reward_address_parameters: "CardanoAddressParametersType" + reward_address_parameters: "CardanoAddressParametersType | None" nonce: "int" format: "CardanoGovernanceRegistrationFormat" delegations: "list[CardanoGovernanceRegistrationDelegation]" voting_purpose: "int | None" + reward_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, + reward_address_parameters: "CardanoAddressParametersType | None" = None, format: "CardanoGovernanceRegistrationFormat | None" = None, voting_purpose: "int | None" = None, + reward_address: "str | None" = None, ) -> None: pass diff --git a/python/src/trezorlib/cardano.py b/python/src/trezorlib/cardano.py index 70983e13d0..d82c1f1a98 100644 --- a/python/src/trezorlib/cardano.py +++ b/python/src/trezorlib/cardano.py @@ -64,7 +64,6 @@ REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens") REQUIRED_FIELDS_GOVERNANCE_REGISTRATION = ( "staking_path", "nonce", - "reward_address_parameters", ) REQUIRED_FIELDS_GOVERNANCE_DELEGATION = ("voting_public_key", "weight") @@ -596,10 +595,13 @@ def parse_auxiliary_data( ), staking_path=tools.parse_path(governance_registration["staking_path"]), nonce=governance_registration["nonce"], + reward_address=governance_registration.get("reward_address"), reward_address_parameters=_parse_address_parameters( governance_registration["reward_address_parameters"], str(AUXILIARY_DATA_MISSING_FIELDS_ERROR), - ), + ) + if "reward_address_parameters" in governance_registration + else None, format=serialization_format, delegations=delegations, voting_purpose=voting_purpose, diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 2178610400..69f3762a32 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -2658,31 +2658,34 @@ class CardanoGovernanceRegistrationParametersType(protobuf.MessageType): FIELDS = { 1: protobuf.Field("voting_public_key", "bytes", repeated=False, required=False, default=None), 2: protobuf.Field("staking_path", "uint32", repeated=True, required=False, default=None), - 3: protobuf.Field("reward_address_parameters", "CardanoAddressParametersType", repeated=False, required=True), + 3: protobuf.Field("reward_address_parameters", "CardanoAddressParametersType", repeated=False, required=False, default=None), 4: protobuf.Field("nonce", "uint64", repeated=False, required=True), 5: protobuf.Field("format", "CardanoGovernanceRegistrationFormat", repeated=False, required=False, default=CardanoGovernanceRegistrationFormat.CIP15), 6: protobuf.Field("delegations", "CardanoGovernanceRegistrationDelegation", repeated=True, required=False, default=None), 7: protobuf.Field("voting_purpose", "uint64", repeated=False, required=False, default=None), + 8: protobuf.Field("reward_address", "string", repeated=False, required=False, default=None), } 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, + reward_address_parameters: Optional["CardanoAddressParametersType"] = None, format: Optional["CardanoGovernanceRegistrationFormat"] = CardanoGovernanceRegistrationFormat.CIP15, voting_purpose: Optional["int"] = None, + reward_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.nonce = nonce self.voting_public_key = voting_public_key + self.reward_address_parameters = reward_address_parameters self.format = format self.voting_purpose = voting_purpose + self.reward_address = reward_address class CardanoTxAuxiliaryData(protobuf.MessageType):