feat(core/cardano): enable typing for Cardano app

pull/1423/head
matejcik 3 years ago committed by matejcik
parent 32c37aa9cd
commit ccd241fe55

@ -44,9 +44,9 @@ enum CardanoPoolRelayType {
* @embed * @embed
*/ */
message CardanoBlockchainPointerType { message CardanoBlockchainPointerType {
optional uint32 block_index = 1; required uint32 block_index = 1;
optional uint32 tx_index = 2; required uint32 tx_index = 2;
optional uint32 certificate_index = 3; required uint32 certificate_index = 3;
} }
/** /**
@ -58,7 +58,7 @@ message CardanoBlockchainPointerType {
* @embed * @embed
*/ */
message CardanoAddressParametersType { message CardanoAddressParametersType {
optional CardanoAddressType address_type = 1; // one of the CardanoAddressType-s required CardanoAddressType address_type = 1; // one of the CardanoAddressType-s
repeated uint32 address_n = 2; // BIP-32-style path to derive the spending key from master node repeated uint32 address_n = 2; // BIP-32-style path to derive the spending key from master node
repeated uint32 address_n_staking = 3; // BIP-32-style path to derive staking key from master node repeated uint32 address_n_staking = 3; // BIP-32-style path to derive staking key from master node
optional bytes staking_key_hash = 4; // staking key can be derived from address_n_staking, or optional bytes staking_key_hash = 4; // staking key can be derived from address_n_staking, or
@ -75,10 +75,10 @@ message CardanoAddressParametersType {
*/ */
message CardanoGetAddress { message CardanoGetAddress {
// repeated uint32 address_n = 1; // moved to address_parameters // repeated uint32 address_n = 1; // moved to address_parameters
optional bool show_display = 2; // optionally prompt for confirmation on trezor display optional bool show_display = 2 [default=false]; // optionally prompt for confirmation on trezor display
optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets required uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets
optional uint32 network_id = 4; // network id - mainnet or testnet required uint32 network_id = 4; // network id - mainnet or testnet
optional CardanoAddressParametersType address_parameters = 5; // parameters used to derive the address required CardanoAddressParametersType address_parameters = 5; // parameters used to derive the address
} }
/** /**
@ -86,7 +86,7 @@ message CardanoGetAddress {
* @end * @end
*/ */
message CardanoAddress { message CardanoAddress {
optional string address = 1; // Base58 cardano address required string address = 1; // Base58 cardano address
} }
/** /**
@ -105,8 +105,8 @@ message CardanoGetPublicKey {
* @end * @end
*/ */
message CardanoPublicKey { message CardanoPublicKey {
optional string xpub = 1; // Xpub key required string xpub = 1; // Xpub key
optional hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 public node required hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 public node
} }
/** /**
@ -119,10 +119,10 @@ message CardanoSignTx {
repeated CardanoTxInputType inputs = 1; // inputs to be used in transaction repeated CardanoTxInputType inputs = 1; // inputs to be used in transaction
repeated CardanoTxOutputType outputs = 2; // outputs to be used in transaction repeated CardanoTxOutputType outputs = 2; // outputs to be used in transaction
// optional uint32 transactions_count = 3; // left as a comment so we know to skip the id 3 in the future // optional uint32 transactions_count = 3; // left as a comment so we know to skip the id 3 in the future
optional uint32 protocol_magic = 5; // network's protocol magic required uint32 protocol_magic = 5; // network's protocol magic
optional uint64 fee = 6; // transaction fee - added in shelley required uint64 fee = 6; // transaction fee - added in shelley
optional uint64 ttl = 7; // transaction ttl - added in shelley optional uint64 ttl = 7; // transaction ttl - added in shelley
optional uint32 network_id = 8; // network id - mainnet or testnet required uint32 network_id = 8; // network id - mainnet or testnet
repeated CardanoTxCertificateType certificates = 9; // transaction certificates - added in shelley repeated CardanoTxCertificateType certificates = 9; // transaction certificates - added in shelley
repeated CardanoTxWithdrawalType withdrawals = 10; // transaction withdrawals - added in shelley repeated CardanoTxWithdrawalType withdrawals = 10; // transaction withdrawals - added in shelley
optional bytes metadata = 11; // transaction metadata - added in shelley optional bytes metadata = 11; // transaction metadata - added in shelley
@ -133,8 +133,8 @@ message CardanoSignTx {
*/ */
message CardanoTxInputType { message CardanoTxInputType {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional bytes prev_hash = 2; // hash of previous transaction output to spend by this input required bytes prev_hash = 2; // hash of previous transaction output to spend by this input
optional uint32 prev_index = 3; // index of previous output to spend required uint32 prev_index = 3; // index of previous output to spend
// left as a comment so we know to skip the id 4 in the future // left as a comment so we know to skip the id 4 in the future
// optional uint32 type = 4; // optional uint32 type = 4;
} }
@ -144,7 +144,7 @@ message CardanoSignTx {
message CardanoTxOutputType { message CardanoTxOutputType {
optional string address = 1; // target coin address in bech32 or base58 optional string address = 1; // target coin address in bech32 or base58
// repeated uint32 address_n = 2; // moved to address_parameters // repeated uint32 address_n = 2; // moved to address_parameters
optional uint64 amount = 3; // amount to spend required uint64 amount = 3; // amount to spend
optional CardanoAddressParametersType address_parameters = 4; // parameters used to derive the address optional CardanoAddressParametersType address_parameters = 4; // parameters used to derive the address
repeated CardanoAssetGroupType token_bundle = 5; // custom assets - added in mary repeated CardanoAssetGroupType token_bundle = 5; // custom assets - added in mary
} }
@ -206,7 +206,7 @@ message CardanoSignTx {
* Structure representing cardano transaction certificate * Structure representing cardano transaction certificate
*/ */
message CardanoTxCertificateType { message CardanoTxCertificateType {
optional CardanoCertificateType type = 1; // certificate type required CardanoCertificateType type = 1; // certificate type
repeated uint32 path = 2; // BIP-32 path to derive (staking) key repeated uint32 path = 2; // BIP-32 path to derive (staking) key
optional bytes pool = 3; // pool hash optional bytes pool = 3; // pool hash
optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate
@ -216,7 +216,7 @@ message CardanoSignTx {
*/ */
message CardanoTxWithdrawalType { message CardanoTxWithdrawalType {
repeated uint32 path = 1; repeated uint32 path = 1;
optional uint64 amount = 2; required uint64 amount = 2;
} }
} }
@ -225,6 +225,6 @@ message CardanoSignTx {
* @end * @end
*/ */
message CardanoSignedTx { message CardanoSignedTx {
optional bytes tx_hash = 1; // hash of the transaction body required bytes tx_hash = 1; // hash of the transaction body
optional bytes serialized_tx = 2; // serialized, signed transaction required bytes serialized_tx = 2; // serialized, signed transaction
} }

@ -107,6 +107,7 @@ mypy:
mypy --config-file ../setup.cfg \ mypy --config-file ../setup.cfg \
src/main.py \ src/main.py \
src/apps/bitcoin \ src/apps/bitcoin \
src/apps/cardano \
src/apps/webauthn src/apps/webauthn
## code generation: ## code generation:

@ -586,7 +586,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_bip32_from_seed_obj,
#if !BITCOIN_ONLY #if !BITCOIN_ONLY
/// def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> bytes: /// def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> HDNode:
/// """ /// """
/// Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation /// Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation
/// scheme, aka v2 derivation scheme. /// scheme, aka v2 derivation scheme.

@ -117,7 +117,7 @@ def from_seed(seed: bytes, curve_name: str) -> HDNode:
# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h # extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> bytes: def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> HDNode:
""" """
Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation
scheme, aka v2 derivation scheme. scheme, aka v2 derivation scheme.

@ -1,6 +1,6 @@
from trezor import wire from trezor import wire
from trezor.crypto import base58, hashlib from trezor.crypto import base58, hashlib
from trezor.messages import CardanoAddressParametersType, CardanoAddressType from trezor.messages import CardanoAddressType
from apps.common.seed import remove_ed25519_prefix from apps.common.seed import remove_ed25519_prefix
@ -11,9 +11,14 @@ from .helpers.utils import variable_length_encode
from .seed import is_byron_path, is_shelley_path from .seed import is_byron_path, is_shelley_path
if False: if False:
from typing import List from typing import List, Optional
from trezor.messages import CardanoBlockchainPointerType from trezor.messages.CardanoBlockchainPointerType import (
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType CardanoBlockchainPointerType,
)
from trezor.messages.CardanoAddressParametersType import (
CardanoAddressParametersType,
EnumTypeCardanoAddressType,
)
from . import seed from . import seed
ADDRESS_TYPES_SHELLEY = ( ADDRESS_TYPES_SHELLEY = (
@ -84,8 +89,8 @@ def get_address_bytes_unsafe(address: str) -> bytes:
return address_bytes return address_bytes
def _get_address_type(address: bytes) -> int: def _get_address_type(address: bytes) -> EnumTypeCardanoAddressType:
return address[0] >> 4 return address[0] >> 4 # type: ignore
def _validate_shelley_address( def _validate_shelley_address(
@ -93,14 +98,12 @@ def _validate_shelley_address(
) -> None: ) -> None:
address_type = _get_address_type(address_bytes) address_type = _get_address_type(address_bytes)
_validate_address_size(address_bytes, address_type) _validate_address_size(address_bytes)
_validate_address_bech32_hrp(address_str, address_type, network_id) _validate_address_bech32_hrp(address_str, address_type, network_id)
_validate_address_network_id(address_bytes, network_id) _validate_address_network_id(address_bytes, network_id)
def _validate_address_size( def _validate_address_size(address_bytes: bytes) -> None:
address_bytes: bytes, address_type: EnumTypeCardanoAddressType
) -> None:
if not (MIN_ADDRESS_BYTES_LENGTH <= len(address_bytes) <= MAX_ADDRESS_BYTES_LENGTH): if not (MIN_ADDRESS_BYTES_LENGTH <= len(address_bytes) <= MAX_ADDRESS_BYTES_LENGTH):
raise INVALID_ADDRESS raise INVALID_ADDRESS
@ -220,6 +223,8 @@ def _derive_shelley_address(
elif parameters.address_type == CardanoAddressType.ENTERPRISE: elif parameters.address_type == CardanoAddressType.ENTERPRISE:
address = _derive_enterprise_address(keychain, parameters.address_n, network_id) address = _derive_enterprise_address(keychain, parameters.address_n, network_id)
elif parameters.address_type == CardanoAddressType.POINTER: elif parameters.address_type == CardanoAddressType.POINTER:
if parameters.certificate_pointer is None:
raise wire.DataError("Certificate pointer data missing!")
address = _derive_pointer_address( address = _derive_pointer_address(
keychain, keychain,
parameters.address_n, parameters.address_n,
@ -245,7 +250,7 @@ def _derive_base_address(
keychain: seed.Keychain, keychain: seed.Keychain,
path: List[int], path: List[int],
staking_path: List[int], staking_path: List[int],
staking_key_hash: bytes, staking_key_hash: Optional[bytes],
network_id: int, network_id: int,
) -> bytes: ) -> bytes:
header = _create_address_header(CardanoAddressType.BASE, network_id) header = _create_address_header(CardanoAddressType.BASE, network_id)
@ -261,7 +266,7 @@ def _derive_base_address(
def _validate_base_address_staking_info( def _validate_base_address_staking_info(
staking_path: List[int], staking_path: List[int],
staking_key_hash: bytes, staking_key_hash: Optional[bytes],
) -> None: ) -> None:
if (staking_key_hash is None) == (not staking_path): if (staking_key_hash is None) == (not staking_path):
raise wire.DataError( raise wire.DataError(
@ -286,14 +291,6 @@ def _derive_pointer_address(
def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes: def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes:
if (
pointer is None
or pointer.block_index is None
or pointer.tx_index is None
or pointer.certificate_index is None
):
raise wire.DataError("Invalid pointer!")
block_index_encoded = variable_length_encode(pointer.block_index) block_index_encoded = variable_length_encode(pointer.block_index)
tx_index_encoded = variable_length_encode(pointer.tx_index) tx_index_encoded = variable_length_encode(pointer.tx_index)
certificate_index_encoded = variable_length_encode(pointer.certificate_index) certificate_index_encoded = variable_length_encode(pointer.certificate_index)

@ -21,7 +21,7 @@ with base58 encoding and all the nuances of Byron addresses.
""" """
def _encode_address_raw(address_data_encoded) -> bytes: def _encode_address_raw(address_data_encoded: bytes) -> bytes:
return cbor.encode( return cbor.encode(
[cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)] [cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)]
) )

@ -11,6 +11,8 @@ from .helpers import INVALID_CERTIFICATE, LOVELACE_MAX_SUPPLY
from .helpers.paths import SCHEMA_STAKING from .helpers.paths import SCHEMA_STAKING
if False: if False:
from typing import List, Optional
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
from trezor.messages.CardanoPoolParametersType import CardanoPoolParametersType from trezor.messages.CardanoPoolParametersType import CardanoPoolParametersType
from trezor.messages.CardanoPoolRelayParametersType import ( from trezor.messages.CardanoPoolRelayParametersType import (
@ -18,10 +20,10 @@ if False:
) )
from trezor.messages.CardanoPoolOwnerType import CardanoPoolOwnerType from trezor.messages.CardanoPoolOwnerType import CardanoPoolOwnerType
from trezor.messages.CardanoPoolMetadataType import CardanoPoolMetadataType from trezor.messages.CardanoPoolMetadataType import CardanoPoolMetadataType
from typing import List, Optional, Union, Tuple, Any
from . import seed
CborSequence = Union[List[Any], Tuple[Any, ...]] from apps.common.cbor import CborSequence
from . import seed
POOL_HASH_SIZE = 28 POOL_HASH_SIZE = 28
VRF_KEY_HASH_SIZE = 32 VRF_KEY_HASH_SIZE = 32

@ -17,7 +17,10 @@ from .layout import (
from .sign_tx import validate_network_info from .sign_tx import validate_network_info
if False: if False:
from trezor.messages import CardanoAddressParametersType, CardanoGetAddress from trezor.messages.CardanoAddressParametersType import (
CardanoAddressParametersType,
)
from trezor.messages.CardanoGetAddress import CardanoGetAddress
@seed.with_keychain @seed.with_keychain
@ -95,4 +98,6 @@ async def _show_staking_warnings(
address_parameters.staking_key_hash, address_parameters.staking_key_hash,
) )
elif staking_type == staking_use_cases.POINTER_ADDRESS: elif staking_type == staking_use_cases.POINTER_ADDRESS:
# ensured in _derive_shelley_address:
assert address_parameters.certificate_pointer is not None
await show_warning_address_pointer(ctx, address_parameters.certificate_pointer) await show_warning_address_pointer(ctx, address_parameters.certificate_pointer)

@ -12,13 +12,13 @@ from .helpers.paths import SCHEMA_PUBKEY
if False: if False:
from typing import List from typing import List
from trezor.messages import CardanoGetPublicKey from trezor.messages.CardanoGetPublicKey import CardanoGetPublicKey
@seed.with_keychain @seed.with_keychain
async def get_public_key( async def get_public_key(
ctx: wire.Context, msg: CardanoGetPublicKey, keychain: seed.Keychain ctx: wire.Context, msg: CardanoGetPublicKey, keychain: seed.Keychain
): ) -> CardanoPublicKey:
await paths.validate_path( await paths.validate_path(
ctx, ctx,
keychain, keychain,

@ -10,6 +10,8 @@ HRP_TESTNET_REWARD_ADDRESS = "stake_test"
def encode(hrp: str, data: bytes) -> str: def encode(hrp: str, data: bytes) -> str:
converted_bits = bech32.convertbits(data, 8, 5) converted_bits = bech32.convertbits(data, 8, 5)
if converted_bits is None:
raise ValueError
return bech32.bech32_encode(hrp, converted_bits) return bech32.bech32_encode(hrp, converted_bits)
@ -18,12 +20,14 @@ def decode_unsafe(bech: str) -> bytes:
return decode(hrp, bech) return decode(hrp, bech)
def get_hrp(bech: str): def get_hrp(bech: str) -> str:
return bech.rsplit(HRP_SEPARATOR, 1)[0] return bech.rsplit(HRP_SEPARATOR, 1)[0]
def decode(hrp: str, bech: str) -> bytes: def decode(hrp: str, bech: str) -> bytes:
decoded_hrp, data = bech32.bech32_decode(bech, 130) decoded_hrp, data = bech32.bech32_decode(bech, 130)
if data is None:
raise ValueError
if decoded_hrp != hrp: if decoded_hrp != hrp:
raise ValueError raise ValueError

@ -7,8 +7,10 @@ from .utils import to_account_path
if False: if False:
from typing import List from typing import List
from trezor.messages import CardanoAddressParametersType from trezor.messages.CardanoAddressParametersType import (
from .. import seed CardanoAddressParametersType,
)
from ..seed import Keychain
""" """
@ -24,9 +26,7 @@ MISMATCH = 2
POINTER_ADDRESS = 3 POINTER_ADDRESS = 3
def get( def get(keychain: Keychain, address_parameters: CardanoAddressParametersType) -> int:
keychain: seed.Keychain, address_parameters: CardanoAddressParametersType
) -> int:
address_type = address_parameters.address_type address_type = address_parameters.address_type
if address_type == CardanoAddressType.BASE: if address_type == CardanoAddressType.BASE:
if not SCHEMA_ADDRESS.match(address_parameters.address_n): if not SCHEMA_ADDRESS.match(address_parameters.address_n):

@ -6,8 +6,6 @@ from trezor.messages import (
ButtonRequestType, ButtonRequestType,
CardanoAddressType, CardanoAddressType,
CardanoCertificateType, CardanoCertificateType,
CardanoPoolMetadataType,
CardanoPoolOwnerType,
) )
from trezor.strings import format_amount from trezor.strings import format_amount
from trezor.ui.button import ButtonDefault from trezor.ui.button import ButtonDefault
@ -30,14 +28,16 @@ from .helpers.utils import format_account_number, format_optional_int, to_accoun
if False: if False:
from typing import List, Optional from typing import List, Optional
from trezor import wire from trezor import wire
from trezor.messages import ( from trezor.messages.CardanoBlockchainPointerType import (
CardanoBlockchainPointerType, CardanoBlockchainPointerType,
CardanoTxCertificateType,
CardanoTxWithdrawalType,
CardanoPoolParametersType,
CardanoAssetGroupType,
CardanoTokenType,
) )
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType
from trezor.messages.CardanoPoolParametersType import CardanoPoolParametersType
from trezor.messages.CardanoPoolOwnerType import CardanoPoolOwnerType
from trezor.messages.CardanoPoolMetadataType import CardanoPoolMetadataType
from trezor.messages.CardanoAssetGroupType import CardanoAssetGroupType
from trezor.messages.CardanoTokenType import CardanoTokenType
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
@ -85,7 +85,8 @@ async def confirm_sending(
to_lines = list(chunks(to, 17)) to_lines = list(chunks(to, 17))
page1.bold(to_lines[0]) page1.bold(to_lines[0])
pages = [page1] + _paginate_lines(to_lines, 1, "Confirm transaction", ui.ICON_SEND) comp: ui.Component = page1 # otherwise `[page1]` is of the wrong type
pages = [comp] + _paginate_lines(to_lines, 1, "Confirm transaction", ui.ICON_SEND)
await require_confirm(ctx, Paginated(pages)) await require_confirm(ctx, Paginated(pages))
@ -215,7 +216,7 @@ async def show_warning_tx_staking_key_hash(
async def confirm_transaction( async def confirm_transaction(
ctx, ctx: wire.Context,
amount: int, amount: int,
fee: int, fee: int,
protocol_magic: int, protocol_magic: int,
@ -224,7 +225,7 @@ async def confirm_transaction(
has_metadata: bool, has_metadata: bool,
is_network_id_verifiable: bool, is_network_id_verifiable: bool,
) -> None: ) -> None:
pages = [] pages: List[ui.Component] = []
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Transaction amount:") page1.normal("Transaction amount:")
@ -257,7 +258,7 @@ async def confirm_certificate(
# in this call # in this call
assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION
pages = [] pages: List[ui.Component] = []
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Confirm:") page1.normal("Confirm:")
@ -267,6 +268,7 @@ async def confirm_certificate(
pages.append(page1) pages.append(page1)
if certificate.type == CardanoCertificateType.STAKE_DELEGATION: if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
assert certificate.pool is not None # validate_certificate
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page2.normal("to pool:") page2.normal("to pool:")
page2.bold(hexlify(certificate.pool).decode()) page2.bold(hexlify(certificate.pool).decode())
@ -304,11 +306,11 @@ async def confirm_stake_pool_parameters(
async def confirm_stake_pool_owners( async def confirm_stake_pool_owners(
ctx: wire.Context, ctx: wire.Context,
keychain: seed.keychain, keychain: seed.Keychain,
owners: List[CardanoPoolOwnerType], owners: List[CardanoPoolOwnerType],
network_id: int, network_id: int,
) -> None: ) -> None:
pages = [] pages: List[ui.Component] = []
for index, owner in enumerate(owners, 1): for index, owner in enumerate(owners, 1):
page = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page.normal("Pool owner #%d:" % (index)) page.normal("Pool owner #%d:" % (index))
@ -324,6 +326,7 @@ async def confirm_stake_pool_owners(
) )
) )
else: else:
assert owner.staking_key_hash is not None # validate_pool_owners
page.bold( page.bold(
encode_human_readable_address( encode_human_readable_address(
pack_reward_address_bytes(owner.staking_key_hash, network_id) pack_reward_address_bytes(owner.staking_key_hash, network_id)
@ -360,7 +363,10 @@ async def confirm_stake_pool_metadata(
async def confirm_transaction_network_ttl( async def confirm_transaction_network_ttl(
ctx, protocol_magic: int, ttl: Optional[int], validity_interval_start: Optional[int] ctx: wire.Context,
protocol_magic: int,
ttl: Optional[int],
validity_interval_start: Optional[int],
) -> None: ) -> None:
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
page1.normal("Network:") page1.normal("Network:")
@ -428,13 +434,17 @@ async def show_address(
for address_line in address_lines[: lines_per_page - lines_used_on_first_page]: for address_line in address_lines[: lines_per_page - lines_used_on_first_page]:
page1.bold(address_line) page1.bold(address_line)
pages: List[ui.Component] = []
pages.append(page1)
# append remaining pages containing the rest of the address # append remaining pages containing the rest of the address
pages = [page1] + _paginate_lines( pages.extend(
address_lines, _paginate_lines(
lines_per_page - lines_used_on_first_page, address_lines,
address_type_label, lines_per_page - lines_used_on_first_page,
ui.ICON_RECEIVE, address_type_label,
lines_per_page, ui.ICON_RECEIVE,
lines_per_page,
)
) )
return await confirm( return await confirm(
@ -449,7 +459,7 @@ async def show_address(
def _paginate_lines( def _paginate_lines(
lines: List[str], offset: int, desc: str, icon: str, lines_per_page: int = 4 lines: List[str], offset: int, desc: str, icon: str, lines_per_page: int = 4
) -> List[ui.Component]: ) -> List[ui.Component]:
pages = [] pages: List[ui.Component] = []
if len(lines) > offset: if len(lines) > offset:
to_pages = list(chunks(lines[offset:], lines_per_page)) to_pages = list(chunks(lines[offset:], lines_per_page))
for page in to_pages: for page in to_pages:
@ -465,7 +475,7 @@ async def show_warning_address_foreign_staking_key(
ctx: wire.Context, ctx: wire.Context,
account_path: List[int], account_path: List[int],
staking_account_path: List[int], staking_account_path: List[int],
staking_key_hash: bytes, staking_key_hash: Optional[bytes],
) -> None: ) -> None:
page1 = Text("Warning", ui.ICON_WRONG, ui.RED) page1 = Text("Warning", ui.ICON_WRONG, ui.RED)
page1.normal("Stake rights associated") page1.normal("Stake rights associated")
@ -480,6 +490,7 @@ async def show_warning_address_foreign_staking_key(
page2.bold(address_n_to_str(staking_account_path)) page2.bold(address_n_to_str(staking_account_path))
page2.br_half() page2.br_half()
else: else:
assert staking_key_hash is not None # _validate_base_address_staking_info
page2.normal("Staking key:") page2.normal("Staking key:")
page2.bold(hexlify(staking_key_hash).decode()) page2.bold(hexlify(staking_key_hash).decode())
page2.normal("Continue?") page2.normal("Continue?")

@ -9,8 +9,12 @@ from apps.common.seed import get_seed
from .helpers import paths from .helpers import paths
if False: if False:
from typing import Callable, Awaitable
from apps.common.paths import Bip32Path from apps.common.paths import Bip32Path
from apps.common.keychain import MsgIn, MsgOut, Handler, HandlerWithKeychain from apps.common.keychain import MsgIn, MsgOut, Handler
HandlerWithKeychain = Callable[[wire.Context, MsgIn, "Keychain"], Awaitable[MsgOut]]
class Keychain: class Keychain:
@ -52,11 +56,11 @@ class Keychain:
# self.root.__del__() # self.root.__del__()
def is_byron_path(path: Bip32Path): def is_byron_path(path: Bip32Path) -> bool:
return path[: len(paths.BYRON_ROOT)] == paths.BYRON_ROOT return path[: len(paths.BYRON_ROOT)] == paths.BYRON_ROOT
def is_shelley_path(path: Bip32Path): def is_shelley_path(path: Bip32Path) -> bool:
return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT return path[: len(paths.SHELLEY_ROOT)] == paths.SHELLEY_ROOT
@ -75,7 +79,9 @@ async def get_keychain(ctx: wire.Context) -> Keychain:
if mnemonic.is_bip39(): if mnemonic.is_bip39():
passphrase = await get_passphrase(ctx) passphrase = await get_passphrase(ctx)
# derive the root node from mnemonic and passphrase via Cardano Icarus algorithm # derive the root node from mnemonic and passphrase via Cardano Icarus algorithm
root = bip32.from_mnemonic_cardano(mnemonic.get_secret().decode(), passphrase) secret_bytes = mnemonic.get_secret()
assert secret_bytes is not None
root = bip32.from_mnemonic_cardano(secret_bytes.decode(), passphrase)
else: else:
# derive the root node via SLIP-0023 # derive the root node via SLIP-0023
seed = await get_seed(ctx) seed = await get_seed(ctx)

@ -57,13 +57,16 @@ from .layout import (
from .seed import is_byron_path, is_shelley_path from .seed import is_byron_path, is_shelley_path
if False: if False:
from typing import Any, Dict, List, Optional, Tuple, Union
from trezor.messages.CardanoSignTx import CardanoSignTx from trezor.messages.CardanoSignTx import CardanoSignTx
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
from trezor.messages.CardanoTxInputType import CardanoTxInputType from trezor.messages.CardanoTxInputType import CardanoTxInputType
from trezor.messages.CardanoTxOutputType import CardanoTxOutputType from trezor.messages.CardanoTxOutputType import CardanoTxOutputType
from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType
from trezor.messages.CardanoAssetGroupType import CardanoAssetGroupType from trezor.messages.CardanoAssetGroupType import CardanoAssetGroupType
from typing import Dict, List, Tuple, Union
from apps.common.cbor import CborSequence
CborizedTokenBundle = Dict[bytes, Dict[bytes, int]] CborizedTokenBundle = Dict[bytes, Dict[bytes, int]]
CborizedTxOutput = Tuple[bytes, Union[int, Tuple[int, CborizedTokenBundle]]] CborizedTxOutput = Tuple[bytes, Union[int, Tuple[int, CborizedTokenBundle]]]
@ -156,7 +159,7 @@ async def _sign_stake_pool_registration_tx(
return tx return tx
def _has_stake_pool_registration(msg: CardanoSignTx): def _has_stake_pool_registration(msg: CardanoSignTx) -> bool:
return any( return any(
cert.type == CardanoCertificateType.STAKE_POOL_REGISTRATION cert.type == CardanoCertificateType.STAKE_POOL_REGISTRATION
for cert in msg.certificates for cert in msg.certificates
@ -176,7 +179,9 @@ def validate_network_info(network_id: int, protocol_magic: int) -> None:
raise wire.ProcessError("Invalid network id/protocol magic combination!") raise wire.ProcessError("Invalid network id/protocol magic combination!")
def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTx): def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTx) -> None:
# ensures that there is exactly one certificate, which is stake pool registration,
# and no withdrawals
if ( if (
len(msg.certificates) != 1 len(msg.certificates) != 1
or not _has_stake_pool_registration(msg) or not _has_stake_pool_registration(msg)
@ -264,7 +269,7 @@ def _validate_max_tx_output_size(
) )
def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]): def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]) -> None:
if any(i.address_n for i in inputs): if any(i.address_n for i in inputs):
raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS
@ -285,7 +290,7 @@ def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None:
raise INVALID_WITHDRAWAL raise INVALID_WITHDRAWAL
def _validate_metadata(metadata: bytes) -> None: def _validate_metadata(metadata: Optional[bytes]) -> None:
if not metadata: if not metadata:
return return
@ -388,7 +393,7 @@ def _cborize_output(
keychain, output.address_parameters, protocol_magic, network_id keychain, output.address_parameters, protocol_magic, network_id
) )
else: else:
# output address is validated in _validate_outputs before this happens assert output.address is not None # _validate_outputs
address = get_address_bytes_unsafe(output.address) address = get_address_bytes_unsafe(output.address)
if not output.token_bundle: if not output.token_bundle:
@ -400,11 +405,11 @@ def _cborize_output(
def _cborize_token_bundle( def _cborize_token_bundle(
token_bundle: List[CardanoAssetGroupType], token_bundle: List[CardanoAssetGroupType],
) -> CborizedTokenBundle: ) -> CborizedTokenBundle:
result = {} result: CborizedTokenBundle = {}
for token_group in token_bundle: for token_group in token_bundle:
cborized_policy_id = bytes(token_group.policy_id) cborized_policy_id = bytes(token_group.policy_id)
result[cborized_policy_id] = cborized_token_group = {} cborized_token_group = result[cborized_policy_id] = {}
for token in token_group.tokens: for token in token_group.tokens:
cborized_asset_name = bytes(token.asset_name_bytes) cborized_asset_name = bytes(token.asset_name_bytes)
@ -416,7 +421,7 @@ def _cborize_token_bundle(
def _cborize_certificates( def _cborize_certificates(
keychain: seed.Keychain, keychain: seed.Keychain,
certificates: List[CardanoTxCertificateType], certificates: List[CardanoTxCertificateType],
) -> List[Tuple]: ) -> List[CborSequence]:
return [cborize_certificate(keychain, cert) for cert in certificates] return [cborize_certificate(keychain, cert) for cert in certificates]
@ -469,7 +474,7 @@ def _cborize_witnesses(
# use key 0 for shelley witnesses and key 2 for byron witnesses # use key 0 for shelley witnesses and key 2 for byron witnesses
# according to the spec in shelley.cddl in cardano-ledger-specs # according to the spec in shelley.cddl in cardano-ledger-specs
witnesses = {} witnesses: Dict[Any, Any] = {}
if shelley_witnesses: if shelley_witnesses:
witnesses[0] = shelley_witnesses witnesses[0] = shelley_witnesses
if byron_witnesses: if byron_witnesses:
@ -499,6 +504,8 @@ def _cborize_shelley_witnesses(
): ):
paths.add(tuple(certificate.path)) paths.add(tuple(certificate.path))
elif certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION: elif certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
# ensured by validate_certificate:
assert certificate.pool_parameters is not None # validate_certificate
for pool_owner in certificate.pool_parameters.owners: for pool_owner in certificate.pool_parameters.owners:
if pool_owner.staking_key_path: if pool_owner.staking_key_path:
paths.add(tuple(pool_owner.staking_key_path)) paths.add(tuple(pool_owner.staking_key_path))
@ -514,7 +521,7 @@ def _cborize_shelley_witnesses(
def _cborize_shelley_witness( def _cborize_shelley_witness(
keychain: seed.Keychain, tx_body_hash: bytes, path: List[int] keychain: seed.Keychain, tx_body_hash: bytes, path: List[int]
) -> List[Tuple[bytes, bytes]]: ) -> Tuple[bytes, bytes]:
node = keychain.derive(path) node = keychain.derive(path)
signature = ed25519.sign_ext( signature = ed25519.sign_ext(
@ -587,6 +594,9 @@ async def _show_stake_pool_registration_tx(
) -> None: ) -> None:
stake_pool_registration_certificate = msg.certificates[0] stake_pool_registration_certificate = msg.certificates[0]
pool_parameters = stake_pool_registration_certificate.pool_parameters pool_parameters = stake_pool_registration_certificate.pool_parameters
# _validate_stake_pool_registration_tx_structure ensures that there is only one
# certificate, and validate_certificate ensures that the structure is valid
assert pool_parameters is not None
# display the transaction (certificate) in UI # display the transaction (certificate) in UI
await confirm_stake_pool_parameters( await confirm_stake_pool_parameters(
@ -619,6 +629,7 @@ async def _show_outputs(
if _should_hide_output(output.address_parameters.address_n, msg.inputs): if _should_hide_output(output.address_parameters.address_n, msg.inputs):
continue continue
else: else:
assert output.address is not None # _validate_outputs
address = output.address address = output.address
total_amount += output.amount total_amount += output.amount
@ -637,13 +648,15 @@ async def _show_change_output_staking_warnings(
address_parameters: CardanoAddressParametersType, address_parameters: CardanoAddressParametersType,
address: str, address: str,
amount: int, amount: int,
): ) -> None:
address_type = address_parameters.address_type address_type = address_parameters.address_type
staking_use_case = staking_use_cases.get(keychain, address_parameters) staking_use_case = staking_use_cases.get(keychain, address_parameters)
if staking_use_case == staking_use_cases.NO_STAKING: if staking_use_case == staking_use_cases.NO_STAKING:
await show_warning_tx_no_staking_info(ctx, address_type, amount) await show_warning_tx_no_staking_info(ctx, address_type, amount)
elif staking_use_case == staking_use_cases.POINTER_ADDRESS: elif staking_use_case == staking_use_cases.POINTER_ADDRESS:
# ensured in _derive_shelley_address:
assert address_parameters.certificate_pointer is not None
await show_warning_tx_pointer_address( await show_warning_tx_pointer_address(
ctx, ctx,
address_parameters.certificate_pointer, address_parameters.certificate_pointer,
@ -657,6 +670,8 @@ async def _show_change_output_staking_warnings(
amount, amount,
) )
else: else:
# ensured in _validate_base_address_staking_info:
assert address_parameters.staking_key_hash
await show_warning_tx_staking_key_hash( await show_warning_tx_staking_key_hash(
ctx, ctx,
address_parameters.staking_key_hash, address_parameters.staking_key_hash,

@ -8,9 +8,10 @@ from micropython import const
from trezor import log from trezor import log
if False: if False:
from typing import Any, Iterable, List, Tuple from typing import Any, Iterable, List, Tuple, Union
Value = Any Value = Any
CborSequence = Union[List[Value], Tuple[Value, ...]]
_CBOR_TYPE_MASK = const(0xE0) _CBOR_TYPE_MASK = const(0xE0)
_CBOR_INFO_BITS = const(0x1F) _CBOR_INFO_BITS = const(0x1F)

@ -22,9 +22,6 @@ if False:
from typing_extensions import Protocol from typing_extensions import Protocol
from trezor import wire from trezor import wire
# XXX this is a circular import, but it's only for typing
from .keychain import Keychain
Bip32Path = Sequence[int] Bip32Path = Sequence[int]
Slip21Path = Sequence[bytes] Slip21Path = Sequence[bytes]
PathType = TypeVar("PathType", Bip32Path, Slip21Path) PathType = TypeVar("PathType", Bip32Path, Slip21Path)
@ -33,6 +30,13 @@ if False:
def match(self, path: Bip32Path) -> bool: def match(self, path: Bip32Path) -> bool:
... ...
class KeychainValidatorType(Protocol):
def is_in_keychain(self, path: Bip32Path) -> bool:
...
def verify_path(self, path: Bip32Path) -> None:
...
class Interval: class Interval:
"""Helper for testing membership in an interval.""" """Helper for testing membership in an interval."""
@ -247,7 +251,10 @@ PATTERN_SEP5 = "m/44'/coin_type'/account'"
async def validate_path( async def validate_path(
ctx: wire.Context, keychain: Keychain, path: Bip32Path, *additional_checks: bool ctx: wire.Context,
keychain: KeychainValidatorType,
path: Bip32Path,
*additional_checks: bool,
) -> None: ) -> None:
keychain.verify_path(path) keychain.verify_path(path)
if not keychain.is_in_keychain(path) or not all(additional_checks): if not keychain.is_in_keychain(path) or not all(additional_checks):

@ -419,33 +419,6 @@ class TestCardanoAddress(unittest.TestCase):
) )
derive_human_readable_address(keychain, address_parameters, 0, 0) derive_human_readable_address(keychain, address_parameters, 0, 0)
# block index is None
with self.assertRaises(wire.DataError):
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=CardanoBlockchainPointerType(block_index=None, tx_index=2, certificate_index=3),
)
derive_human_readable_address(keychain, address_parameters, 0, 0)
# tx index is None
with self.assertRaises(wire.DataError):
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=CardanoBlockchainPointerType(block_index=1, tx_index=None, certificate_index=3),
)
derive_human_readable_address(keychain, address_parameters, 0, 0)
# certificate index is None
with self.assertRaises(wire.DataError):
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=CardanoBlockchainPointerType(block_index=1, tx_index=2, certificate_index=None),
)
derive_human_readable_address(keychain, address_parameters, 0, 0)
def test_reward_address(self): def test_reward_address(self):
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice" mnemonic = "test walk nut penalty hip pave soap entry language right filter choice"
passphrase = "" passphrase = ""

Loading…
Cancel
Save