refactor(cardano): introduce Signer and its subclasses

pull/2374/head
David Misiak 2 years ago committed by matejcik
parent b1bee00a3a
commit 2724d29968

@ -523,6 +523,16 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.seed
apps.cardano.sign_tx
import apps.cardano.sign_tx
apps.cardano.sign_tx.multisig_signer
import apps.cardano.sign_tx.multisig_signer
apps.cardano.sign_tx.ordinary_signer
import apps.cardano.sign_tx.ordinary_signer
apps.cardano.sign_tx.plutus_signer
import apps.cardano.sign_tx.plutus_signer
apps.cardano.sign_tx.pool_owner_signer
import apps.cardano.sign_tx.pool_owner_signer
apps.cardano.sign_tx.signer
import apps.cardano.sign_tx.signer
apps.common.mnemonic
import apps.common.mnemonic
apps.eos

@ -1,10 +1,6 @@
from typing import TYPE_CHECKING
from trezor.enums import (
CardanoCertificateType,
CardanoPoolRelayType,
CardanoTxSigningMode,
)
from trezor.enums import CardanoCertificateType, CardanoPoolRelayType
from apps.common import cbor
@ -41,22 +37,10 @@ MAX_PORT_NUMBER = 65535
def validate_certificate(
certificate: CardanoTxCertificate,
signing_mode: CardanoTxSigningMode,
protocol_magic: int,
network_id: int,
account_path_checker: AccountPathChecker,
) -> None:
if (
signing_mode != CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER
and certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION
):
raise INVALID_CERTIFICATE
elif (
signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER
and certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION
):
raise INVALID_CERTIFICATE
_validate_certificate_structure(certificate)
if certificate.type in (
@ -68,7 +52,6 @@ def validate_certificate(
certificate.path,
certificate.script_hash,
certificate.key_hash,
signing_mode,
INVALID_CERTIFICATE,
)

@ -6,8 +6,8 @@ from trezor.messages import CardanoAddress
from . import seed
from .address import derive_human_readable_address, validate_address_parameters
from .helpers.credential import Credential, should_show_address_credentials
from .helpers.utils import validate_network_info
from .layout import show_address_credentials, show_cardano_address
from .sign_tx import validate_network_info
if TYPE_CHECKING:
from trezor.messages import (

@ -1,8 +1,9 @@
from typing import TYPE_CHECKING
from trezor import wire
from trezor.crypto import hashlib
from trezor.enums import CardanoTxSigningMode
from apps.cardano.helpers import network_ids, protocol_magics
from apps.cardano.helpers.paths import (
ACCOUNT_PATH_INDEX,
SCHEMA_STAKING_ANY_ACCOUNT,
@ -13,7 +14,6 @@ from apps.common.seed import remove_ed25519_prefix
from . import ADDRESS_KEY_HASH_SIZE, SCRIPT_HASH_SIZE, bech32
if TYPE_CHECKING:
from trezor import wire
from .. import seed
@ -84,32 +84,27 @@ def validate_stake_credential(
path: list[int],
script_hash: bytes | None,
key_hash: bytes | None,
signing_mode: CardanoTxSigningMode,
error: wire.ProcessError,
) -> None:
if sum(bool(k) for k in (path, script_hash, key_hash)) != 1:
raise error
if path:
if signing_mode not in (
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxSigningMode.PLUTUS_TRANSACTION,
):
raise error
if not SCHEMA_STAKING_ANY_ACCOUNT.match(path):
raise error
elif script_hash:
if signing_mode not in (
CardanoTxSigningMode.MULTISIG_TRANSACTION,
CardanoTxSigningMode.PLUTUS_TRANSACTION,
):
raise error
if len(script_hash) != SCRIPT_HASH_SIZE:
raise error
elif key_hash:
if signing_mode != CardanoTxSigningMode.PLUTUS_TRANSACTION:
raise error
if len(key_hash) != ADDRESS_KEY_HASH_SIZE:
raise error
else:
if path and not SCHEMA_STAKING_ANY_ACCOUNT.match(path):
raise error
if script_hash and len(script_hash) != SCRIPT_HASH_SIZE:
raise error
if key_hash and len(key_hash) != ADDRESS_KEY_HASH_SIZE:
raise error
def validate_network_info(network_id: int, protocol_magic: int) -> None:
"""
We are only concerned about checking that both network_id and protocol_magic
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).
"""
is_mainnet_network_id = network_ids.is_mainnet(network_id)
is_mainnet_protocol_magic = protocol_magics.is_mainnet(protocol_magic)
if is_mainnet_network_id != is_mainnet_protocol_magic:
raise wire.ProcessError("Invalid network id/protocol magic combination!")

@ -7,7 +7,6 @@ from trezor.enums import (
CardanoCertificateType,
CardanoNativeScriptHashDisplayFormat,
CardanoNativeScriptType,
CardanoTxSigningMode,
)
from trezor.messages import CardanoAddressParametersType
from trezor.strings import format_amount
@ -193,26 +192,25 @@ async def show_script_hash(
)
async def show_transaction_signing_mode(
ctx: wire.Context, signing_mode: CardanoTxSigningMode
) -> None:
if signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION:
await confirm_metadata(
ctx,
"confirm_signing_mode",
title="Confirm transaction",
content="Confirming a multisig transaction.",
larger_vspace=True,
br_code=ButtonRequestType.Other,
)
elif signing_mode == CardanoTxSigningMode.PLUTUS_TRANSACTION:
await confirm_metadata(
ctx,
"confirm_signing_mode",
title="Confirm transaction",
content="Confirming a Plutus transaction - loss of collateral is possible. Check all items carefully.",
br_code=ButtonRequestType.Other,
)
async def show_multisig_transaction(ctx: wire.Context) -> None:
await confirm_metadata(
ctx,
"confirm_signing_mode",
title="Confirm transaction",
content="Confirming a multisig transaction.",
larger_vspace=True,
br_code=ButtonRequestType.Other,
)
async def show_plutus_transaction(ctx: wire.Context) -> None:
await confirm_metadata(
ctx,
"confirm_signing_mode",
title="Confirm transaction",
content="Confirming a Plutus transaction - loss of collateral is possible. Check all items carefully.",
br_code=ButtonRequestType.Other,
)
async def confirm_input(ctx: wire.Context, input: CardanoTxInput) -> None:

File diff suppressed because it is too large Load Diff

@ -0,0 +1,44 @@
from typing import Type
from trezor import log, wire
from trezor.enums import CardanoTxSigningMode
from trezor.messages import CardanoSignTxFinished, CardanoSignTxInit
from .. import seed
from .signer import Signer
@seed.with_keychain
async def sign_tx(
ctx: wire.Context, msg: messages.CardanoSignTxInit, keychain: seed.Keychain
) -> messages.CardanoSignTxFinished:
signer_type: Type[Signer]
if msg.signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
from .ordinary_signer import OrdinarySigner
signer_type = OrdinarySigner
elif msg.signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER:
from .pool_owner_signer import PoolOwnerSigner
signer_type = PoolOwnerSigner
elif msg.signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION:
from .multisig_signer import MultisigSigner
signer_type = MultisigSigner
elif msg.signing_mode == CardanoTxSigningMode.PLUTUS_TRANSACTION:
from .plutus_signer import PlutusSigner
signer_type = PlutusSigner
else:
raise RuntimeError # should be unreachable
signer = signer_type(ctx, msg, keychain)
try:
await signer.sign()
except ValueError as e:
if __debug__:
log.exception(__name__, e)
raise wire.ProcessError("Signing failed")
return CardanoSignTxFinished()

@ -0,0 +1,88 @@
from trezor import wire
from trezor.enums import CardanoCertificateType
from trezor.messages import (
CardanoSignTxInit,
CardanoTxCertificate,
CardanoTxOutput,
CardanoTxWithdrawal,
CardanoTxWitnessRequest,
)
from .. import seed
from ..helpers import (
INVALID_CERTIFICATE,
INVALID_OUTPUT,
INVALID_TX_SIGNING_REQUEST,
INVALID_WITHDRAWAL,
INVALID_WITNESS_REQUEST,
)
from ..helpers.paths import SCHEMA_MINT
from ..layout import show_multisig_transaction
from ..seed import is_multisig_path
from .signer import Signer
class MultisigSigner(Signer):
"""
The multisig signing mode only allows signing with multisig (and minting) keys.
"""
def __init__(
self, ctx: wire.Context, msg: CardanoSignTxInit, keychain: seed.Keychain
) -> None:
super().__init__(ctx, msg, keychain)
def _validate_tx_signing_request(self) -> None:
super()._validate_tx_signing_request()
if (
self.msg.collateral_inputs_count != 0
or self.msg.required_signers_count != 0
):
raise INVALID_TX_SIGNING_REQUEST
async def _show_tx_signing_request(self) -> None:
await show_multisig_transaction(self.ctx)
await super()._show_tx_signing_request()
async def _confirm_tx(self, tx_hash: bytes) -> None:
# 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,
is_network_id_verifiable,
tx_hash=None,
)
def _validate_output(self, output: messages.CardanoTxOutput) -> None:
super()._validate_output(output)
if output.address_parameters is not None:
raise INVALID_OUTPUT
def _validate_certificate(self, certificate: CardanoTxCertificate) -> None:
super()._validate_certificate(certificate)
if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
raise INVALID_CERTIFICATE
if certificate.path or certificate.key_hash:
raise INVALID_CERTIFICATE
def _validate_withdrawal(self, withdrawal: CardanoTxWithdrawal) -> None:
super()._validate_withdrawal(withdrawal)
if withdrawal.path or withdrawal.key_hash:
raise INVALID_WITHDRAWAL
def _validate_witness_request(
self, witness_request: CardanoTxWitnessRequest
) -> None:
super()._validate_witness_request(witness_request)
is_minting = SCHEMA_MINT.match(witness_request.path)
transaction_has_token_minting = self.msg.minting_asset_groups_count > 0
if not is_multisig_path(witness_request.path) and not is_minting:
raise INVALID_WITNESS_REQUEST
if is_minting and not transaction_has_token_minting:
raise INVALID_WITNESS_REQUEST

@ -0,0 +1,102 @@
from trezor import wire
from trezor.enums import CardanoCertificateType
from trezor.messages import (
CardanoSignTxInit,
CardanoTxCertificate,
CardanoTxWithdrawal,
CardanoTxWitnessRequest,
)
from .. import seed
from ..helpers import (
INVALID_CERTIFICATE,
INVALID_TX_SIGNING_REQUEST,
INVALID_WITHDRAWAL,
INVALID_WITNESS_REQUEST,
)
from ..helpers.paths import (
SCHEMA_MINT,
SCHEMA_PAYMENT,
SCHEMA_STAKING,
WITNESS_PATH_NAME,
)
from ..layout import confirm_witness_request
from ..seed import is_byron_path, is_shelley_path
from .signer import Signer
class OrdinarySigner(Signer):
"""
Ordinary txs are meant for usual actions, such as sending funds from addresses
controlled by 1852' keys, dealing with staking and minting/burning tokens.
"""
def __init__(
self, ctx: wire.Context, msg: CardanoSignTxInit, keychain: seed.Keychain
) -> None:
super().__init__(ctx, msg, keychain)
def _validate_tx_signing_request(self) -> None:
super()._validate_tx_signing_request()
if (
self.msg.collateral_inputs_count != 0
or self.msg.required_signers_count != 0
):
raise INVALID_TX_SIGNING_REQUEST
async def _confirm_tx(self, tx_hash: bytes) -> None:
# 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,
is_network_id_verifiable,
tx_hash=None,
)
def _validate_certificate(self, certificate: messages.CardanoTxCertificate) -> None:
super()._validate_certificate(certificate)
if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
raise INVALID_CERTIFICATE
if certificate.script_hash or certificate.key_hash:
raise INVALID_CERTIFICATE
def _validate_withdrawal(self, withdrawal: CardanoTxWithdrawal) -> None:
super()._validate_withdrawal(withdrawal)
if withdrawal.script_hash or withdrawal.key_hash:
raise INVALID_WITHDRAWAL
def _validate_witness_request(
self, witness_request: CardanoTxWitnessRequest
) -> None:
super()._validate_witness_request(witness_request)
is_minting = SCHEMA_MINT.match(witness_request.path)
transaction_has_token_minting = self.msg.minting_asset_groups_count > 0
if not (
is_byron_path(witness_request.path)
or is_shelley_path(witness_request.path)
or is_minting
):
raise INVALID_WITNESS_REQUEST
if is_minting and not transaction_has_token_minting:
raise INVALID_WITNESS_REQUEST
async def _show_witness_request(self, witness_path: list[int]) -> None:
# 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
# path to the user depending on Trezor's configuration. If it's a minting path,
# we always show it.
is_payment = SCHEMA_PAYMENT.match(witness_path)
is_staking = SCHEMA_STAKING.match(witness_path)
is_minting = SCHEMA_MINT.match(witness_path)
if is_minting:
await 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)

@ -0,0 +1,112 @@
from trezor import wire
from trezor.enums import CardanoCertificateType
from trezor.messages import (
CardanoAddressParametersType,
CardanoSignTxInit,
CardanoTxCertificate,
CardanoTxInput,
CardanoTxOutput,
CardanoTxWitnessRequest,
)
from .. import seed
from ..helpers import INVALID_CERTIFICATE, INVALID_WITNESS_REQUEST
from ..helpers.credential import Credential, should_show_address_credentials
from ..helpers.paths import SCHEMA_MINT
from ..layout import (
confirm_input,
confirm_transaction,
show_device_owned_output_credentials,
show_plutus_transaction,
show_warning_no_collateral_inputs,
show_warning_no_script_data_hash,
)
from ..seed import is_multisig_path, is_shelley_path
from .signer import Signer
class PlutusSigner(Signer):
"""
The Plutus siging mode is meant for txs that involve Plutus script evaluation. The
validation rules are less strict, but more tx items/warnings are shown to the user.
"""
def __init__(
self, ctx: wire.Context, msg: CardanoSignTxInit, keychain: seed.Keychain
) -> None:
super().__init__(ctx, msg, keychain)
async def _show_tx_signing_request(self) -> None:
await show_plutus_transaction(self.ctx)
await super()._show_tx_signing_request()
# These items should be present if a Plutus script is to be executed.
if self.msg.script_data_hash is None:
await show_warning_no_script_data_hash(self.ctx)
if self.msg.collateral_inputs_count == 0:
await show_warning_no_collateral_inputs(self.ctx)
async def _confirm_transaction(self, tx_hash: bytes) -> None:
# 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
# tedious to check one by one on the Trezor screen).
is_network_id_verifiable = self._is_network_id_verifiable()
await confirm_transaction(
self.ctx,
self.msg.fee,
self.msg.network_id,
self.msg.protocol_magic,
self.msg.ttl,
self.msg.validity_interval_start,
is_network_id_verifiable,
tx_hash,
)
async def _show_input(self, input: CardanoTxInput) -> None:
# super() omitted intentionally
# The inputs are not interchangeable (because of datums), so we must show them.
await confirm_input(self.ctx, input)
async def _show_output_credentials(
self, address_parameters: CardanoAddressParametersType
) -> None:
# 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.
show_both_credentials = should_show_address_credentials(address_parameters)
await show_device_owned_output_credentials(
self.ctx,
Credential.payment_credential(address_parameters),
Credential.stake_credential(address_parameters),
show_both_credentials,
)
def _should_show_output(self, output: CardanoTxOutput) -> bool:
# super() omitted intentionally
# All outputs need to be shown (even device-owned), because they might influence
# the script evaluation.
return True
def _is_change_output(self, output: CardanoTxOutput) -> bool:
# super() omitted intentionally
# In Plutus txs, we don't call device-owned outputs "change" outputs.
return False
def _validate_certificate(self, certificate: CardanoTxCertificate) -> None:
super()._validate_certificate(certificate)
if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
raise INVALID_CERTIFICATE
def _validate_witness_request(
self, witness_request: CardanoTxWitnessRequest
) -> None:
super()._validate_witness_request(witness_request)
is_minting = SCHEMA_MINT.match(witness_request.path)
# In Plutus txs, we allow minting witnesses even when the tx doesn't have token minting.
if not (
is_shelley_path(witness_request.path)
or is_multisig_path(witness_request.path)
or is_minting
):
raise INVALID_WITNESS_REQUEST

@ -0,0 +1,92 @@
from trezor import wire
from trezor.enums import CardanoCertificateType
from trezor.messages import (
CardanoSignTxInit,
CardanoTxCertificate,
CardanoTxOutput,
CardanoTxWitnessRequest,
)
from .. import seed
from ..helpers import (
INVALID_CERTIFICATE,
INVALID_OUTPUT,
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE,
INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES,
INVALID_TX_SIGNING_REQUEST,
)
from ..helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT
from ..layout import confirm_stake_pool_registration_final
from .signer import Signer
class PoolOwnerSigner(Signer):
"""
We have a separate tx signing flow for stake pool registration because it's a
transaction where the witnessable entries (i.e. inputs, withdrawals, etc.) are not
supposed to be controlled by the HW wallet, which means the user is vulnerable to
unknowingly supplying a witness for an UTXO or other tx entry they think is external,
resulting in the co-signers gaining control over their funds (Something SLIP-0019 is
dealing with for BTC but no similar standard is currently available for Cardano).
Hence we completely forbid witnessing inputs and other entries of the transaction
except the stake pool certificate itself and we provide a witness only to the user's
staking key in the list of pool owners.
"""
def __init__(
self, ctx: wire.Context, msg: CardanoSignTxInit, keychain: seed.Keychain
) -> None:
super().__init__(ctx, msg, keychain)
def _validate_tx_signing_request(self) -> None:
super()._validate_tx_signing_request()
if (
self.msg.certificates_count != 1
or self.msg.withdrawals_count != 0
or self.msg.minting_asset_groups_count != 0
):
raise INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE
if (
self.msg.script_data_hash is not None
or self.msg.collateral_inputs_count != 0
or self.msg.required_signers_count != 0
):
raise INVALID_TX_SIGNING_REQUEST
async def _confirm_transaction(self, tx_hash: bytes) -> None:
# super() omitted intentionally
await confirm_stake_pool_registration_final(
self.ctx,
self.msg.protocol_magic,
self.msg.ttl,
self.msg.validity_interval_start,
)
def _validate_output(self, output: CardanoTxOutput) -> None:
super()._validate_output(output)
if output.address_parameters is not None:
raise INVALID_OUTPUT
if output.datum_hash is not None:
raise INVALID_OUTPUT
def _should_show_output(self, output: CardanoTxOutput) -> bool:
# super() omitted intentionally
# There are no spending witnesses, it is thus safe to hide outputs.
return False
def _validate_certificate(self, certificate: CardanoTxCertificate) -> None:
super()._validate_certificate(certificate)
if certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION:
raise INVALID_CERTIFICATE
def _validate_witness_request(
self, witness_request: CardanoTxWitnessRequest
) -> None:
super()._validate_witness_request(witness_request)
if not SCHEMA_STAKING_ANY_ACCOUNT.match(witness_request.path):
raise INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES
def _is_network_id_verifiable(self) -> bool:
# super() omitted intentionally
return True

@ -0,0 +1,926 @@
from micropython import const
from typing import TYPE_CHECKING
from trezor import wire
from trezor.crypto import hashlib
from trezor.crypto.curve import ed25519
from trezor.enums import (
CardanoAddressType,
CardanoCertificateType,
CardanoTxWitnessType,
)
from trezor.messages import (
CardanoAddressParametersType,
CardanoAssetGroup,
CardanoPoolOwner,
CardanoPoolRelayParameters,
CardanoSignTxInit,
CardanoToken,
CardanoTxAuxiliaryData,
CardanoTxBodyHash,
CardanoTxCertificate,
CardanoTxCollateralInput,
CardanoTxHostAck,
CardanoTxInput,
CardanoTxItemAck,
CardanoTxMint,
CardanoTxOutput,
CardanoTxRequiredSigner,
CardanoTxWithdrawal,
CardanoTxWitnessRequest,
CardanoTxWitnessResponse,
)
from apps.common import cbor, safety_checks
from .. import seed
from ..address import (
ADDRESS_TYPES_PAYMENT_SCRIPT,
derive_address_bytes,
derive_human_readable_address,
get_address_bytes_unsafe,
get_address_type,
validate_output_address,
validate_output_address_parameters,
)
from ..auxiliary_data import (
get_auxiliary_data_hash_and_supplement,
show_auxiliary_data,
validate_auxiliary_data,
)
from ..certificates import (
assert_certificate_cond,
cborize_certificate,
cborize_initial_pool_registration_certificate_fields,
cborize_pool_metadata,
cborize_pool_owner,
cborize_pool_relay,
validate_certificate,
validate_pool_owner,
validate_pool_relay,
)
from ..helpers import (
ADDRESS_KEY_HASH_SIZE,
INPUT_PREV_HASH_SIZE,
INVALID_COLLATERAL_INPUT,
INVALID_INPUT,
INVALID_OUTPUT,
INVALID_OUTPUT_DATUM_HASH,
INVALID_REQUIRED_SIGNER,
INVALID_SCRIPT_DATA_HASH,
INVALID_TOKEN_BUNDLE_MINT,
INVALID_TOKEN_BUNDLE_OUTPUT,
INVALID_TX_SIGNING_REQUEST,
INVALID_WITHDRAWAL,
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_address_credentials
from ..helpers.hash_builder_collection import HashBuilderDict, HashBuilderList
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 ..layout import (
confirm_certificate,
confirm_collateral_input,
confirm_required_signer,
confirm_script_data_hash,
confirm_sending,
confirm_sending_token,
confirm_stake_pool_metadata,
confirm_stake_pool_owner,
confirm_stake_pool_parameters,
confirm_token_minting,
confirm_transaction,
confirm_withdrawal,
confirm_witness_request,
show_change_output_credentials,
show_warning_path,
show_warning_tx_contains_mint,
show_warning_tx_network_unverifiable,
show_warning_tx_output_contains_datum_hash,
show_warning_tx_output_contains_tokens,
show_warning_tx_output_no_datum_hash,
)
from ..seed import is_byron_path, is_minting_path, is_multisig_path, is_shelley_path
if TYPE_CHECKING:
from typing import Any
from apps.common.paths import PathSchema
CardanoTxResponseType = CardanoTxItemAck | CardanoTxWitnessResponse
MINTING_POLICY_ID_LENGTH = 28
MAX_ASSET_NAME_LENGTH = 32
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)
POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT = 10
class Signer:
"""
This class encapsulates the entire tx signing process. By default, most tx items are
allowed and shown to the user. For each signing mode, there is a subclass that
overrides some methods, usually to add more validation rules and show/hide some
items. Each tx item is processed in a _process_xyz() method which handles validation,
user confirmation and serialization of the tx item.
"""
def __init__(
self, ctx: wire.Context, msg: CardanoSignTxInit, keychain: seed.Keychain
) -> None:
self.ctx = ctx
self.msg = msg
self.keychain = keychain
self.account_path_checker = AccountPathChecker()
# Inputs, outputs and fee are mandatory, count the number of optional fields present.
tx_body_map_item_count = 3 + sum(
(
msg.ttl is not None,
msg.certificates_count > 0,
msg.withdrawals_count > 0,
msg.has_auxiliary_data,
msg.validity_interval_start is not None,
msg.minting_asset_groups_count > 0,
msg.include_network_id,
msg.script_data_hash is not None,
msg.collateral_inputs_count > 0,
msg.required_signers_count > 0,
)
)
self.tx_dict: HashBuilderDict[int, Any] = HashBuilderDict(
tx_body_map_item_count, INVALID_TX_SIGNING_REQUEST
)
async def sign(self) -> None:
hash_fn = hashlib.blake2b(outlen=32)
self.tx_dict.start(hash_fn)
with self.tx_dict:
await self._processs_tx_signing_request()
tx_hash = hash_fn.digest()
await self._confirm_transaction(tx_hash)
response_after_witness_requests = await self._process_witness_requests(tx_hash)
await self.ctx.call(response_after_witness_requests, CardanoTxHostAck)
await self.ctx.call(CardanoTxBodyHash(tx_hash=tx_hash), CardanoTxHostAck)
# signing request
async def _processs_tx_signing_request(self) -> None:
self._validate_tx_signing_request()
await self._show_tx_signing_request()
inputs_list: HashBuilderList[tuple[bytes, int]] = HashBuilderList(
self.msg.inputs_count
)
with self.tx_dict.add(TX_BODY_KEY_INPUTS, inputs_list):
await self._process_inputs(inputs_list)
outputs_list: HashBuilderList = HashBuilderList(self.msg.outputs_count)
with self.tx_dict.add(TX_BODY_KEY_OUTPUTS, outputs_list):
await self._process_outputs(outputs_list)
self.tx_dict.add(TX_BODY_KEY_FEE, self.msg.fee)
if self.msg.ttl is not None:
self.tx_dict.add(TX_BODY_KEY_TTL, self.msg.ttl)
if self.msg.certificates_count > 0:
certificates_list: HashBuilderList = HashBuilderList(
self.msg.certificates_count
)
with self.tx_dict.add(TX_BODY_KEY_CERTIFICATES, certificates_list):
await self._process_certificates(certificates_list)
if self.msg.withdrawals_count > 0:
withdrawals_dict: HashBuilderDict[bytes, int] = HashBuilderDict(
self.msg.withdrawals_count, INVALID_WITHDRAWAL
)
with self.tx_dict.add(TX_BODY_KEY_WITHDRAWALS, withdrawals_dict):
await self._process_withdrawals(withdrawals_dict)
if self.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 self.msg.minting_asset_groups_count > 0:
minting_dict: HashBuilderDict[bytes, HashBuilderDict] = HashBuilderDict(
self.msg.minting_asset_groups_count, INVALID_TOKEN_BUNDLE_MINT
)
with self.tx_dict.add(TX_BODY_KEY_MINT, minting_dict):
await self._process_minting(minting_dict)
if self.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 self.msg.required_signers_count > 0:
required_signers_list: HashBuilderList[bytes] = HashBuilderList(
self.msg.required_signers_count
)
with self.tx_dict.add(TX_BODY_KEY_REQUIRED_SIGNERS, required_signers_list):
await self._process_required_signers(required_signers_list)
if self.msg.include_network_id:
self.tx_dict.add(TX_BODY_KEY_NETWORK_ID, self.msg.network_id)
def _validate_tx_signing_request(self) -> None:
if self.msg.fee > LOVELACE_MAX_SUPPLY:
raise wire.ProcessError("Fee is out of range!")
validate_network_info(self.msg.network_id, self.msg.protocol_magic)
async def _show_tx_signing_request(self) -> None:
if not self._is_network_id_verifiable():
await show_warning_tx_network_unverifiable(self.ctx)
async def _confirm_tx(self, tx_hash: bytes) -> None:
# Final signing confirmation is handled separately in each signing mode.
raise NotImplementedError
# inputs
async def _process_inputs(
self, inputs_list: HashBuilderList[tuple[bytes, int]]
) -> None:
for _ in range(self.msg.inputs_count):
input: CardanoTxInput = await self.ctx.call(
CardanoTxItemAck(), CardanoTxInput
)
self._validate_input(input)
await self._show_input(input)
inputs_list.append((input.prev_hash, input.prev_index))
def _validate_input(self, input: CardanoTxInput) -> None:
if len(input.prev_hash) != INPUT_PREV_HASH_SIZE:
raise INVALID_INPUT
async def _show_input(self, input: CardanoTxInput) -> None:
# We never show the inputs, except for Plutus txs.
pass
# outputs
async def _process_outputs(self, outputs_list: HashBuilderList) -> None:
total_amount = 0
for _ in range(self.msg.outputs_count):
output: CardanoTxOutput = await self.ctx.call(
CardanoTxItemAck(), CardanoTxOutput
)
self._validate_output(output)
await self._show_output(output)
output_address = self._get_output_address(output)
has_datum_hash = output.datum_hash is not None
output_list: HashBuilderList = HashBuilderList(2 + int(has_datum_hash))
with outputs_list.append(output_list):
output_list.append(output_address)
if output.asset_groups_count == 0:
# output structure is: [address, amount, datum_hash?]
output_list.append(output.amount)
else:
# output structure is: [address, [amount, asset_groups], datum_hash?]
output_value_list: HashBuilderList = HashBuilderList(2)
with output_list.append(output_value_list):
output_value_list.append(output.amount)
asset_groups_dict: HashBuilderDict[
bytes, HashBuilderDict[bytes, int]
] = HashBuilderDict(
output.asset_groups_count, INVALID_TOKEN_BUNDLE_OUTPUT
)
with output_value_list.append(asset_groups_dict):
await self._process_asset_groups(
asset_groups_dict,
output.asset_groups_count,
self._should_show_output(output),
)
if has_datum_hash:
output_list.append(output.datum_hash)
total_amount += output.amount
if total_amount > LOVELACE_MAX_SUPPLY:
raise wire.ProcessError("Total transaction amount is out of range!")
def _validate_output(self, output: CardanoTxOutput) -> None:
if output.address_parameters is not None and output.address is not None:
raise INVALID_OUTPUT
if output.address_parameters is not None:
validate_output_address_parameters(output.address_parameters)
self._fail_if_strict_and_unusual(output.address_parameters)
elif output.address is not None:
validate_output_address(
output.address, self.msg.protocol_magic, self.msg.network_id
)
else:
raise INVALID_OUTPUT
if output.datum_hash is not None:
if len(output.datum_hash) != OUTPUT_DATUM_HASH_SIZE:
raise INVALID_OUTPUT_DATUM_HASH
address_type = self._get_output_address_type(output)
if address_type not in ADDRESS_TYPES_PAYMENT_SCRIPT:
raise INVALID_OUTPUT
self.account_path_checker.add_output(output)
async def _show_output(self, output: CardanoTxOutput) -> None:
if not self._should_show_output(output):
return
if output.datum_hash is not None:
await show_warning_tx_output_contains_datum_hash(
self.ctx, output.datum_hash
)
address_type = self._get_output_address_type(output)
if output.datum_hash is None and address_type in ADDRESS_TYPES_PAYMENT_SCRIPT:
await show_warning_tx_output_no_datum_hash(self.ctx)
if output.asset_groups_count > 0:
await show_warning_tx_output_contains_tokens(self.ctx)
if output.address_parameters is not None:
address = derive_human_readable_address(
self.keychain,
output.address_parameters,
self.msg.protocol_magic,
self.msg.network_id,
)
await self._show_output_credentials(output.address_parameters)
else:
assert output.address is not None # _validate_output
address = output.address
await confirm_sending(
self.ctx,
output.amount,
address,
self._is_change_output(output),
self.msg.network_id,
)
async def _show_output_credentials(
self, address_parameters: CardanoAddressParametersType
) -> None:
await show_change_output_credentials(
self.ctx,
Credential.payment_credential(address_parameters),
Credential.stake_credential(address_parameters),
)
def _should_show_output(self, output: CardanoTxOutput) -> bool:
"""
Determines whether the output should be shown. Extracted from _show_output because
of readability and because the same decision is made when displaying output tokens.
"""
if output.datum_hash is not None:
# The `return False` case below should not be reachable when datum hash is
# present, but let's make it explicit.
return True
address_type = self._get_output_address_type(output)
if output.datum_hash is None and address_type in ADDRESS_TYPES_PAYMENT_SCRIPT:
# Plutus script address without a datum hash is unspendable, we must show a warning.
return True
if output.address_parameters is not None: # change output
if not should_show_address_credentials(output.address_parameters):
# We don't need to display simple address outputs.
return False
return True
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
# asset groups
async def _process_asset_groups(
self,
asset_groups_dict: HashBuilderDict[bytes, HashBuilderDict[bytes, int]],
asset_groups_count: int,
should_show_tokens: bool,
) -> None:
for _ in range(asset_groups_count):
asset_group: CardanoAssetGroup = await self.ctx.call(
CardanoTxItemAck(), CardanoAssetGroup
)
self._validate_asset_group(asset_group)
tokens: HashBuilderDict[bytes, int] = HashBuilderDict(
asset_group.tokens_count, INVALID_TOKEN_BUNDLE_OUTPUT
)
with asset_groups_dict.add(asset_group.policy_id, tokens):
await self._process_tokens(
tokens,
asset_group.policy_id,
asset_group.tokens_count,
should_show_tokens,
)
def _validate_asset_group(
self, asset_group: CardanoAssetGroup, is_mint: bool = False
) -> None:
INVALID_TOKEN_BUNDLE = (
INVALID_TOKEN_BUNDLE_MINT if is_mint else INVALID_TOKEN_BUNDLE_OUTPUT
)
if len(asset_group.policy_id) != MINTING_POLICY_ID_LENGTH:
raise INVALID_TOKEN_BUNDLE
if asset_group.tokens_count == 0:
raise INVALID_TOKEN_BUNDLE
# tokens
async def _process_tokens(
self,
tokens_dict: HashBuilderDict[bytes, int],
policy_id: bytes,
tokens_count: int,
should_show_tokens: bool,
) -> None:
for _ in range(tokens_count):
token: CardanoToken = await self.ctx.call(CardanoTxItemAck(), CardanoToken)
self._validate_token(token)
if should_show_tokens:
await confirm_sending_token(self.ctx, policy_id, token)
assert token.amount is not None # _validate_token
tokens_dict.add(token.asset_name_bytes, token.amount)
def _validate_token(self, token: CardanoToken, is_mint: bool = False) -> None:
INVALID_TOKEN_BUNDLE = (
INVALID_TOKEN_BUNDLE_MINT if is_mint else INVALID_TOKEN_BUNDLE_OUTPUT
)
if is_mint:
if token.mint_amount is None or token.amount is not None:
raise INVALID_TOKEN_BUNDLE
else:
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:
raise INVALID_TOKEN_BUNDLE
# certificates
async def _process_certificates(self, certificates_list: HashBuilderList) -> None:
for _ in range(self.msg.certificates_count):
certificate: CardanoTxCertificate = await self.ctx.call(
CardanoTxItemAck(), CardanoTxCertificate
)
self._validate_certificate(certificate)
await self._show_certificate(certificate)
if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
pool_parameters = certificate.pool_parameters
assert pool_parameters is not None # _validate_certificate
pool_items_list: HashBuilderList = HashBuilderList(
POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT
)
with certificates_list.append(pool_items_list):
for item in cborize_initial_pool_registration_certificate_fields(
certificate
):
pool_items_list.append(item)
pool_owners_list: HashBuilderList[bytes] = HashBuilderList(
pool_parameters.owners_count
)
with pool_items_list.append(pool_owners_list):
await self._process_pool_owners(
pool_owners_list, pool_parameters.owners_count
)
relays_list: HashBuilderList[cbor.CborSequence] = HashBuilderList(
pool_parameters.relays_count
)
with pool_items_list.append(relays_list):
await self._process_pool_relays(
relays_list, pool_parameters.relays_count
)
pool_items_list.append(
cborize_pool_metadata(pool_parameters.metadata)
)
else:
certificates_list.append(
cborize_certificate(self.keychain, certificate)
)
def _validate_certificate(self, certificate: CardanoTxCertificate) -> None:
validate_certificate(
certificate,
self.msg.protocol_magic,
self.msg.network_id,
self.account_path_checker,
)
async def _show_certificate(self, certificate: CardanoTxCertificate) -> None:
if certificate.path:
await self._fail_or_warn_if_invalid_path(
SCHEMA_STAKING, certificate.path, CERTIFICATE_PATH_NAME
)
if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
assert certificate.pool_parameters is not None
await confirm_stake_pool_parameters(
self.ctx, certificate.pool_parameters, self.msg.network_id
)
await confirm_stake_pool_metadata(
self.ctx, certificate.pool_parameters.metadata
)
else:
await confirm_certificate(self.ctx, certificate)
# pool owners
async def _process_pool_owners(
self, pool_owners_list: HashBuilderList[bytes], owners_count: int
) -> None:
owners_as_path_count = 0
for _ in range(owners_count):
owner: CardanoPoolOwner = await self.ctx.call(
CardanoTxItemAck(), CardanoPoolOwner
)
validate_pool_owner(owner, self.account_path_checker)
await self._show_pool_owner(owner)
pool_owners_list.append(cborize_pool_owner(self.keychain, owner))
if owner.staking_key_path:
owners_as_path_count += 1
assert_certificate_cond(owners_as_path_count == 1)
async def _show_pool_owner(self, owner: 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
)
await confirm_stake_pool_owner(
self.ctx, self.keychain, owner, self.msg.protocol_magic, self.msg.network_id
)
# pool relays
async def _process_pool_relays(
self,
relays_list: HashBuilderList[cbor.CborSequence],
relays_count: int,
) -> None:
for _ in range(relays_count):
relay: CardanoPoolRelayParameters = await self.ctx.call(
CardanoTxItemAck(), CardanoPoolRelayParameters
)
validate_pool_relay(relay)
relays_list.append(cborize_pool_relay(relay))
# withdrawals
async def _process_withdrawals(
self, withdrawals_dict: HashBuilderDict[bytes, int]
) -> None:
for _ in range(self.msg.withdrawals_count):
withdrawal: CardanoTxWithdrawal = await self.ctx.call(
CardanoTxItemAck(), CardanoTxWithdrawal
)
self._validate_withdrawal(withdrawal)
reward_address_bytes = self._derive_withdrawal_reward_address_bytes(
withdrawal
)
await confirm_withdrawal(
self.ctx, withdrawal, reward_address_bytes, self.msg.network_id
)
withdrawals_dict.add(reward_address_bytes, withdrawal.amount)
def _validate_withdrawal(self, withdrawal: CardanoTxWithdrawal) -> None:
validate_stake_credential(
withdrawal.path,
withdrawal.script_hash,
withdrawal.key_hash,
INVALID_WITHDRAWAL,
)
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY:
raise INVALID_WITHDRAWAL
self.account_path_checker.add_withdrawal(withdrawal)
# auxiliary data
async def _process_auxiliary_data(self) -> None:
auxiliary_data: CardanoTxAuxiliaryData = await self.ctx.call(
CardanoTxItemAck(), CardanoTxAuxiliaryData
)
validate_auxiliary_data(auxiliary_data)
(
auxiliary_data_hash,
auxiliary_data_supplement,
) = get_auxiliary_data_hash_and_supplement(
self.keychain, auxiliary_data, self.msg.protocol_magic, self.msg.network_id
)
await show_auxiliary_data(
self.ctx,
self.keychain,
auxiliary_data_hash,
auxiliary_data.catalyst_registration_parameters,
self.msg.protocol_magic,
self.msg.network_id,
)
self.tx_dict.add(TX_BODY_KEY_AUXILIARY_DATA, auxiliary_data_hash)
await self.ctx.call(auxiliary_data_supplement, CardanoTxHostAck)
# minting
async def _process_minting(
self, minting_dict: HashBuilderDict[bytes, HashBuilderDict]
) -> None:
token_minting: CardanoTxMint = await self.ctx.call(
CardanoTxItemAck(), CardanoTxMint
)
await show_warning_tx_contains_mint(self.ctx)
for _ in range(token_minting.asset_groups_count):
asset_group: CardanoAssetGroup = await self.ctx.call(
CardanoTxItemAck(), CardanoAssetGroup
)
self._validate_asset_group(asset_group, is_mint=True)
tokens: HashBuilderDict[bytes, int] = HashBuilderDict(
asset_group.tokens_count, INVALID_TOKEN_BUNDLE_MINT
)
with minting_dict.add(asset_group.policy_id, tokens):
await self._process_minting_tokens(
tokens,
asset_group.policy_id,
asset_group.tokens_count,
)
# minting tokens
async def _process_minting_tokens(
self,
tokens: HashBuilderDict[bytes, int],
policy_id: bytes,
tokens_count: int,
) -> None:
for _ in range(tokens_count):
token: CardanoToken = await self.ctx.call(CardanoTxItemAck(), CardanoToken)
self._validate_token(token, is_mint=True)
await confirm_token_minting(self.ctx, policy_id, token)
assert token.mint_amount is not None # _validate_token
tokens.add(token.asset_name_bytes, token.mint_amount)
# script data hash
async def _process_script_data_hash(self) -> None:
assert self.msg.script_data_hash is not None
self._validate_script_data_hash()
await 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)
def _validate_script_data_hash(self) -> None:
assert self.msg.script_data_hash is not None
if len(self.msg.script_data_hash) != SCRIPT_DATA_HASH_SIZE:
raise INVALID_SCRIPT_DATA_HASH
# collateral inputs
async def _process_collateral_inputs(
self, collateral_inputs_list: HashBuilderList[tuple[bytes, int]]
) -> None:
for _ in range(self.msg.collateral_inputs_count):
collateral_input: CardanoTxCollateralInput = await self.ctx.call(
CardanoTxItemAck(), CardanoTxCollateralInput
)
self._validate_collateral_input(collateral_input)
await confirm_collateral_input(self.ctx, collateral_input)
collateral_inputs_list.append(
(collateral_input.prev_hash, collateral_input.prev_index)
)
def _validate_collateral_input(
self, collateral_input: CardanoTxCollateralInput
) -> None:
if len(collateral_input.prev_hash) != INPUT_PREV_HASH_SIZE:
raise INVALID_COLLATERAL_INPUT
# required signers
async def _process_required_signers(
self, required_signers_list: HashBuilderList[bytes]
) -> None:
for _ in range(self.msg.required_signers_count):
required_signer: CardanoTxRequiredSigner = await self.ctx.call(
CardanoTxItemAck(), CardanoTxRequiredSigner
)
self._validate_required_signer(required_signer)
await confirm_required_signer(self.ctx, required_signer)
key_hash = required_signer.key_hash or get_public_key_hash(
self.keychain, required_signer.key_path
)
required_signers_list.append(key_hash)
def _validate_required_signer(
self, required_signer: CardanoTxRequiredSigner
) -> None:
if required_signer.key_hash and required_signer.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:
if not (
is_shelley_path(required_signer.key_path)
or is_multisig_path(required_signer.key_path)
or is_minting_path(required_signer.key_path)
):
raise INVALID_REQUIRED_SIGNER
else:
raise INVALID_REQUIRED_SIGNER
# witness requests
async def _process_witness_requests(self, tx_hash: bytes) -> CardanoTxResponseType:
response: CardanoTxResponseType = CardanoTxItemAck()
for _ in range(self.msg.witness_requests_count):
witness_request = await self.ctx.call(response, CardanoTxWitnessRequest)
self._validate_witness_request(witness_request)
path = witness_request.path
await self._show_witness_request(path)
if is_byron_path(path):
response = self._get_byron_witness(path, tx_hash)
else:
response = self._get_shelley_witness(path, tx_hash)
return response
def _validate_witness_request(
self, witness_request: CardanoTxWitnessRequest
) -> None:
self.account_path_checker.add_witness_request(witness_request)
async def _show_witness_request(
self,
witness_path: list[int],
) -> None:
await confirm_witness_request(self.ctx, witness_path)
# helpers
def _is_network_id_verifiable(self) -> bool:
"""
Checks whether there is at least one element that contains information about
network ID, otherwise Trezor cannot guarantee that the tx is actually meant for
the given network.
Note: Shelley addresses contain network id. The intended network of Byron
addresses can be determined based on whether they contain the protocol magic.
These checks are performed during address validation.
"""
return (
self.msg.include_network_id
or self.msg.outputs_count != 0
or self.msg.withdrawals_count != 0
)
def _get_output_address(self, output: CardanoTxOutput) -> bytes:
if output.address_parameters:
return derive_address_bytes(
self.keychain,
output.address_parameters,
self.msg.protocol_magic,
self.msg.network_id,
)
else:
assert output.address is not None # _validate_output
return get_address_bytes_unsafe(output.address)
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
return get_address_type(get_address_bytes_unsafe(output.address))
def _derive_withdrawal_reward_address_bytes(
self, withdrawal: CardanoTxWithdrawal
) -> bytes:
reward_address_type = (
CardanoAddressType.REWARD
if withdrawal.path or withdrawal.key_hash
else CardanoAddressType.REWARD_SCRIPT
)
return derive_address_bytes(
self.keychain,
CardanoAddressParametersType(
address_type=reward_address_type,
address_n_staking=withdrawal.path,
staking_key_hash=withdrawal.key_hash,
script_staking_hash=withdrawal.script_hash,
),
self.msg.protocol_magic,
self.msg.network_id,
)
def _get_byron_witness(
self, path: list[int], tx_hash: bytes
) -> CardanoTxWitnessResponse:
node = self.keychain.derive(path)
return CardanoTxWitnessResponse(
type=CardanoTxWitnessType.BYRON_WITNESS,
pub_key=derive_public_key(self.keychain, path),
signature=self._sign_tx_hash(tx_hash, path),
chain_code=node.chain_code(),
)
def _get_shelley_witness(
self, path: list[int], tx_hash: bytes
) -> CardanoTxWitnessResponse:
return CardanoTxWitnessResponse(
type=CardanoTxWitnessType.SHELLEY_WITNESS,
pub_key=derive_public_key(self.keychain, path),
signature=self._sign_tx_hash(tx_hash, path),
)
def _sign_tx_hash(self, tx_body_hash: bytes, path: list[int]) -> bytes:
node = self.keychain.derive(path)
return ed25519.sign_ext(
node.private_key(), node.private_key_ext(), tx_body_hash
)
async def _fail_or_warn_if_invalid_path(
self, schema: PathSchema, path: list[int], path_name: str
) -> None:
if not schema.match(path):
await self._fail_or_warn_path(path, path_name)
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()}")
else:
await show_warning_path(self.ctx, path, path_name)
def _fail_if_strict_and_unusual(
self, address_parameters: CardanoAddressParametersType
) -> None:
if not safety_checks.is_strict():
return
if Credential.payment_credential(address_parameters).is_unusual_path:
raise wire.DataError(f"Invalid {CHANGE_OUTPUT_PATH_NAME.lower()}")
if Credential.stake_credential(address_parameters).is_unusual_path:
raise wire.DataError(f"Invalid {CHANGE_OUTPUT_STAKING_PATH_NAME.lower()}")

@ -1,6 +1,6 @@
from common import *
from trezor import wire
from trezor.enums import CardanoCertificateType, CardanoTxSigningMode
from trezor.enums import CardanoCertificateType
from trezor.messages import CardanoTxCertificate, CardanoPoolParametersType
from apps.common.paths import HARDENED
@ -15,465 +15,346 @@ if not utils.BITCOIN_ONLY:
class TestCardanoCertificate(unittest.TestCase):
def test_validate_certificate(self):
valid_test_vectors = [
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.MULTISIG_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.MULTISIG_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.MULTISIG_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
metadata=None,
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
metadata=None,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
]
invalid_test_vectors = [
# STAKE_REGISTRATION neither path or script_hash is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
),
# STAKE_REGISTRATION both path and script_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_REGISTRATION both script_hash and key_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
# STAKE_REGISTRATION pool is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_REGISTRATION pool parameters are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_DELEGATION neither path or script_hash is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_DELEGATION both path and script_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_DELEGATION both script_hash and key_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
# STAKE_DELEGATION pool parameters are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_DEREGISTRATION neither path or script_hash is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
),
# STAKE_DEREGISTRATION both path and script_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_DEREGISTRATION both script_hash and key_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
# STAKE_DEREGISTRATION pool is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_DEREGISTRATION pool parameters are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_POOL_REGISTRATION pool parameters are not set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
),
# STAKE_POOL_REGISTRATION path is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_POOL_REGISTRATION script hash is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_POOL_REGISTRATION key hash is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_POOL_REGISTRATION pool is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
]
for certificate, signing_mode in valid_test_vectors:
for certificate in valid_test_vectors:
validate_certificate(
certificate,
signing_mode,
protocol_magics.MAINNET,
network_ids.MAINNET,
AccountPathChecker(),
)
for certificate, signing_mode in invalid_test_vectors:
for certificate in invalid_test_vectors:
with self.assertRaises(wire.ProcessError):
validate_certificate(
certificate,
signing_mode,
protocol_magics.MAINNET,
network_ids.MAINNET,
AccountPathChecker(),

@ -1184,8 +1184,8 @@
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[margin_higher_than_1]": "9e1f554bb74f847e8f09201dda808fd1e0cdb68737515f34d6ad435957671c77",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[missing_owner_with_path]": "2dede6a914015cb606124904268abc3cb9034a8e716beb380c62dee29e8a48a4",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_1852_multisi-b7679330": "de16c51797b0e28e766553a31c8ad849c7eb247b0101a1996dba2eea1d59ed2d",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_a_collateral_input]": "875ec83057c301a416c4ea3e77b931132cef45fafc7272cd95cd04bb6d8c4e86",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_a_required_signer]": "875ec83057c301a416c4ea3e77b931132cef45fafc7272cd95cd04bb6d8c4e86",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_a_collateral_input]": "9e1f554bb74f847e8f09201dda808fd1e0cdb68737515f34d6ad435957671c77",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_a_required_signer]": "9e1f554bb74f847e8f09201dda808fd1e0cdb68737515f34d6ad435957671c77",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_long_token_m-9fb3cfe5": "31f7a5a1d32c997c61649ace37fdf3aab3d7bf1cb7c73ffa777ba03ff52470ec",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_output_conta-e3b36436": "875ec83057c301a416c4ea3e77b931132cef45fafc7272cd95cd04bb6d8c4e86",
"TT_cardano-test_sign_tx.py::test_cardano_sign_tx_failed[multisig_transaction_with_repeated_withdrawal]": "691048e2f57f12bd216e1803237f8ce738a0bc66c546601ed2e26fa76ee831dc",

Loading…
Cancel
Save