feat(cardano): add support for script addresses derivation

pull/1846/head
gabrielkerekes 3 years ago committed by matejcik
parent dd9652cd07
commit 21281d7cf4

@ -128,6 +128,8 @@ message CardanoAddressParametersType {
// can be sent directly e.g. if it doesn't belong to // can be sent directly e.g. if it doesn't belong to
// the same account as address_n // the same account as address_n
optional CardanoBlockchainPointerType certificate_pointer = 5; // a pointer to the staking key registration certificate optional CardanoBlockchainPointerType certificate_pointer = 5; // a pointer to the staking key registration certificate
optional bytes script_payment_hash = 6;
optional bytes script_staking_hash = 7;
} }
/** /**

@ -0,0 +1,80 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"parameters": {
"address_type": "base_script_key",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"staking_path": "m/1852'/1815'/0'/2/0",
"network_id": 1,
"protocol_magic": 764824073
},
"result": {
"expected_address": "addr1zyx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsf42dkl"
}
},
{
"parameters": {
"address_type": "base_script_key",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"staking_path": "m/1852'/1815'/0'/2/0",
"network_id": 0,
"protocol_magic": 42
},
"result": {
"expected_address": "addr_test1zqx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms2rhd6q"
}
},
{
"parameters": {
"address_type": "base_key_script",
"path": "m/1852'/1815'/0'/0/0",
"script_staking_hash": "8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9",
"network_id": 1,
"protocol_magic": 764824073
},
"result": {
"expected_address": "addr1yxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5s8vnrtt"
}
},
{
"parameters": {
"address_type": "base_key_script",
"path": "m/1852'/1815'/0'/0/0",
"script_staking_hash": "8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9",
"network_id": 0,
"protocol_magic": 42
},
"result": {
"expected_address": "addr_test1yzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5sy6wr85"
}
},
{
"parameters": {
"address_type": "base_script_script",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"script_staking_hash": "8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9",
"network_id": 1,
"protocol_magic": 764824073
},
"result": {
"expected_address": "addr1xyx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5s3gftll"
}
},
{
"parameters": {
"address_type": "base_script_script",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"script_staking_hash": "8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9",
"network_id": 0,
"protocol_magic": 42
},
"result": {
"expected_address": "addr_test1xqx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5sj75tnq"
}
}
]
}

@ -25,6 +25,28 @@
"result": { "result": {
"expected_address": "addr_test1vzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92s8k2y47" "expected_address": "addr_test1vzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92s8k2y47"
} }
},
{
"parameters": {
"address_type": "enterprise_script",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"network_id": 1,
"protocol_magic": 764824073
},
"result": {
"expected_address": "addr1wyx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsqee7sp"
}
},
{
"parameters": {
"address_type": "enterprise_script",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"network_id": 0,
"protocol_magic": 42
},
"result": {
"expected_address": "addr_test1wqx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsm3dzly"
}
} }
] ]
} }

@ -31,6 +31,34 @@
"result": { "result": {
"expected_address": "addr_test1gzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925ph3wczvf2ag2x9t" "expected_address": "addr_test1gzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925ph3wczvf2ag2x9t"
} }
},
{
"parameters": {
"address_type": "pointer_script",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"block_index": 24157,
"tx_index": 177,
"certificate_index": 42,
"network_id": 1,
"protocol_magic": 764824073
},
"result": {
"expected_address": "addr12yx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5ph3wczvf2zmd4yp"
}
},
{
"parameters": {
"address_type": "pointer_script",
"script_payment_hash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe",
"block_index": 24157,
"tx_index": 177,
"certificate_index": 42,
"network_id": 0,
"protocol_magic": 42
},
"result": {
"expected_address": "addr_test12qx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5ph3wczvf2d4sugn"
}
} }
] ]
} }

@ -6,8 +6,8 @@
"tests": [ "tests": [
{ {
"parameters": { "parameters": {
"path": "m/1852'/1815'/0'/2/0",
"address_type": "reward", "address_type": "reward",
"staking_path": "m/1852'/1815'/0'/2/0",
"network_id": 1, "network_id": 1,
"protocol_magic": 764824073 "protocol_magic": 764824073
}, },
@ -17,14 +17,36 @@
}, },
{ {
"parameters": { "parameters": {
"path": "m/1852'/1815'/0'/2/0",
"address_type": "reward", "address_type": "reward",
"staking_path": "m/1852'/1815'/0'/2/0",
"network_id": 0, "network_id": 0,
"protocol_magic": 42 "protocol_magic": 42
}, },
"result": { "result": {
"expected_address": "stake_test1uqfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yac643znq" "expected_address": "stake_test1uqfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yac643znq"
} }
},
{
"parameters": {
"address_type": "reward_script",
"script_staking_hash": "8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9",
"network_id": 1,
"protocol_magic": 764824073
},
"result": {
"expected_address": "stake17xxhh6785k83c76lklynjyr3anfm2xcry624ytuv24f582gt5mad4"
}
},
{
"parameters": {
"address_type": "reward_script",
"script_staking_hash": "8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9",
"network_id": 0,
"protocol_magic": 42
},
"result": {
"expected_address": "stake_test17zxhh6785k83c76lklynjyr3anfm2xcry624ytuv24f582gv73lfg"
}
} }
] ]
} }

@ -462,6 +462,8 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.helpers.account_path_check import apps.cardano.helpers.account_path_check
apps.cardano.helpers.bech32 apps.cardano.helpers.bech32
import apps.cardano.helpers.bech32 import apps.cardano.helpers.bech32
apps.cardano.helpers.credential
import apps.cardano.helpers.credential
apps.cardano.helpers.hash_builder_collection apps.cardano.helpers.hash_builder_collection
import apps.cardano.helpers.hash_builder_collection import apps.cardano.helpers.hash_builder_collection
apps.cardano.helpers.network_ids apps.cardano.helpers.network_ids
@ -470,8 +472,6 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.helpers.paths import apps.cardano.helpers.paths
apps.cardano.helpers.protocol_magics apps.cardano.helpers.protocol_magics
import apps.cardano.helpers.protocol_magics import apps.cardano.helpers.protocol_magics
apps.cardano.helpers.staking_use_cases
import apps.cardano.helpers.staking_use_cases
apps.cardano.helpers.utils apps.cardano.helpers.utils
import apps.cardano.helpers.utils import apps.cardano.helpers.utils
apps.cardano.layout apps.cardano.layout

@ -1,4 +1,4 @@
from trezor.crypto import base58, hashlib from trezor.crypto import base58
from trezor.enums import CardanoAddressType from trezor.enums import CardanoAddressType
from .byron_address import derive_byron_address, validate_byron_address from .byron_address import derive_byron_address, validate_byron_address
@ -7,17 +7,20 @@ from .helpers import (
INVALID_ADDRESS, INVALID_ADDRESS,
INVALID_ADDRESS_PARAMETERS, INVALID_ADDRESS_PARAMETERS,
NETWORK_MISMATCH, NETWORK_MISMATCH,
SCRIPT_HASH_SIZE,
bech32, bech32,
network_ids, network_ids,
) )
from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT
from .helpers.utils import derive_public_key, variable_length_encode from .helpers.utils import get_public_key_hash, 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 Any
from trezor.messages import ( from trezor.messages import (
CardanoBlockchainPointerType,
CardanoAddressParametersType, CardanoAddressParametersType,
CardanoBlockchainPointerType,
) )
from . import seed from . import seed
@ -33,6 +36,7 @@ ADDRESS_TYPES_SHELLEY = (
CardanoAddressType.REWARD, CardanoAddressType.REWARD,
CardanoAddressType.REWARD_SCRIPT, CardanoAddressType.REWARD_SCRIPT,
) )
MIN_ADDRESS_BYTES_LENGTH = 29 MIN_ADDRESS_BYTES_LENGTH = 29
MAX_ADDRESS_BYTES_LENGTH = 65 MAX_ADDRESS_BYTES_LENGTH = 65
@ -43,20 +47,56 @@ def validate_address_parameters(parameters: CardanoAddressParametersType) -> Non
if parameters.address_type == CardanoAddressType.BYRON: if parameters.address_type == CardanoAddressType.BYRON:
if not is_byron_path(parameters.address_n): if not is_byron_path(parameters.address_n):
raise INVALID_ADDRESS_PARAMETERS raise INVALID_ADDRESS_PARAMETERS
elif parameters.address_type in ADDRESS_TYPES_SHELLEY:
elif parameters.address_type == CardanoAddressType.BASE:
if not is_shelley_path(parameters.address_n):
raise INVALID_ADDRESS_PARAMETERS
_validate_base_address_staking_info(
parameters.address_n_staking, parameters.staking_key_hash
)
elif parameters.address_type == CardanoAddressType.BASE_SCRIPT_KEY:
_validate_script_hash(parameters.script_payment_hash)
_validate_base_address_staking_info(
parameters.address_n_staking, parameters.staking_key_hash
)
elif parameters.address_type == CardanoAddressType.BASE_KEY_SCRIPT:
if not is_shelley_path(parameters.address_n):
raise INVALID_ADDRESS_PARAMETERS
_validate_script_hash(parameters.script_staking_hash)
elif parameters.address_type == CardanoAddressType.BASE_SCRIPT_SCRIPT:
_validate_script_hash(parameters.script_payment_hash)
_validate_script_hash(parameters.script_staking_hash)
elif parameters.address_type == CardanoAddressType.POINTER:
if not is_shelley_path(parameters.address_n):
raise INVALID_ADDRESS_PARAMETERS
if parameters.certificate_pointer is None:
raise INVALID_ADDRESS_PARAMETERS
elif parameters.address_type == CardanoAddressType.POINTER_SCRIPT:
_validate_script_hash(parameters.script_payment_hash)
if parameters.certificate_pointer is None:
raise INVALID_ADDRESS_PARAMETERS
elif parameters.address_type == CardanoAddressType.ENTERPRISE:
if not is_shelley_path(parameters.address_n): if not is_shelley_path(parameters.address_n):
raise INVALID_ADDRESS_PARAMETERS raise INVALID_ADDRESS_PARAMETERS
if parameters.address_type == CardanoAddressType.BASE: elif parameters.address_type == CardanoAddressType.ENTERPRISE_SCRIPT:
_validate_base_address_staking_info( _validate_script_hash(parameters.script_payment_hash)
parameters.address_n_staking, parameters.staking_key_hash
) elif parameters.address_type == CardanoAddressType.REWARD:
elif parameters.address_type == CardanoAddressType.POINTER: if not is_shelley_path(parameters.address_n_staking):
if parameters.certificate_pointer is None: raise INVALID_ADDRESS_PARAMETERS
raise INVALID_ADDRESS_PARAMETERS if not SCHEMA_STAKING_ANY_ACCOUNT.match(parameters.address_n_staking):
elif parameters.address_type == CardanoAddressType.REWARD: raise INVALID_ADDRESS_PARAMETERS
if not SCHEMA_STAKING_ANY_ACCOUNT.match(parameters.address_n):
raise INVALID_ADDRESS_PARAMETERS elif parameters.address_type == CardanoAddressType.REWARD_SCRIPT:
_validate_script_hash(parameters.script_staking_hash)
else: else:
raise INVALID_ADDRESS_PARAMETERS raise INVALID_ADDRESS_PARAMETERS
@ -64,23 +104,86 @@ def validate_address_parameters(parameters: CardanoAddressParametersType) -> Non
def _validate_address_parameters_structure( def _validate_address_parameters_structure(
parameters: CardanoAddressParametersType, parameters: CardanoAddressParametersType,
) -> None: ) -> None:
address_n = parameters.address_n
address_n_staking = parameters.address_n_staking address_n_staking = parameters.address_n_staking
staking_key_hash = parameters.staking_key_hash staking_key_hash = parameters.staking_key_hash
certificate_pointer = parameters.certificate_pointer certificate_pointer = parameters.certificate_pointer
script_payment_hash = parameters.script_payment_hash
fields_to_be_empty: tuple = () script_staking_hash = parameters.script_staking_hash
if parameters.address_type in (
CardanoAddressType.BYRON, fields_to_be_empty: dict[CardanoAddressType, tuple[Any, ...]] = {
CardanoAddressType.REWARD, CardanoAddressType.BASE: (
CardanoAddressType.ENTERPRISE, certificate_pointer,
script_payment_hash,
script_staking_hash,
),
CardanoAddressType.BASE_KEY_SCRIPT: (
address_n_staking,
certificate_pointer,
script_payment_hash,
),
CardanoAddressType.BASE_SCRIPT_KEY: (
address_n,
certificate_pointer,
script_staking_hash,
),
CardanoAddressType.BASE_SCRIPT_SCRIPT: (
address_n,
address_n_staking,
certificate_pointer,
),
CardanoAddressType.POINTER: (
address_n_staking,
staking_key_hash,
script_payment_hash,
script_staking_hash,
),
CardanoAddressType.POINTER_SCRIPT: (
address_n,
address_n_staking,
staking_key_hash,
script_staking_hash,
),
CardanoAddressType.ENTERPRISE: (
address_n_staking,
staking_key_hash,
certificate_pointer,
script_payment_hash,
script_staking_hash,
),
CardanoAddressType.ENTERPRISE_SCRIPT: (
address_n,
address_n_staking,
staking_key_hash,
certificate_pointer,
script_staking_hash,
),
CardanoAddressType.BYRON: (
address_n_staking,
staking_key_hash,
certificate_pointer,
script_payment_hash,
script_staking_hash,
),
CardanoAddressType.REWARD: (
address_n,
staking_key_hash,
certificate_pointer,
script_payment_hash,
script_staking_hash,
),
CardanoAddressType.REWARD_SCRIPT: (
address_n,
address_n_staking,
staking_key_hash,
certificate_pointer,
script_payment_hash,
),
}
if parameters.address_type not in fields_to_be_empty or any(
fields_to_be_empty[parameters.address_type]
): ):
fields_to_be_empty = (address_n_staking, staking_key_hash, certificate_pointer)
elif parameters.address_type == CardanoAddressType.BASE:
fields_to_be_empty = (certificate_pointer,)
elif parameters.address_type == CardanoAddressType.POINTER:
fields_to_be_empty = (address_n_staking, staking_key_hash)
if any(fields_to_be_empty):
raise INVALID_ADDRESS_PARAMETERS raise INVALID_ADDRESS_PARAMETERS
@ -101,6 +204,11 @@ def _validate_base_address_staking_info(
raise INVALID_ADDRESS_PARAMETERS raise INVALID_ADDRESS_PARAMETERS
def _validate_script_hash(script_hash: bytes | None) -> None:
if not script_hash or len(script_hash) != SCRIPT_HASH_SIZE:
raise INVALID_ADDRESS_PARAMETERS
def _validate_address_and_get_type( def _validate_address_and_get_type(
address: str, protocol_magic: int, network_id: int address: str, protocol_magic: int, network_id: int
) -> int: ) -> int:
@ -189,7 +297,7 @@ def _get_bech32_hrp_for_address(
# Byron address uses base58 encoding # Byron address uses base58 encoding
raise ValueError raise ValueError
if address_type == CardanoAddressType.REWARD: if address_type in (CardanoAddressType.REWARD, CardanoAddressType.REWARD_SCRIPT):
if network_ids.is_mainnet(network_id): if network_ids.is_mainnet(network_id):
return bech32.HRP_REWARD_ADDRESS return bech32.HRP_REWARD_ADDRESS
else: else:
@ -210,11 +318,6 @@ def _get_address_network_id(address: bytes) -> int:
return address[0] & 0x0F return address[0] & 0x0F
def get_public_key_hash(keychain: seed.Keychain, path: list[int]) -> bytes:
public_key = derive_public_key(keychain, path)
return hashlib.blake2b(data=public_key, outlen=ADDRESS_KEY_HASH_SIZE).digest()
def derive_human_readable_address( def derive_human_readable_address(
keychain: seed.Keychain, keychain: seed.Keychain,
parameters: CardanoAddressParametersType, parameters: CardanoAddressParametersType,
@ -258,36 +361,14 @@ def derive_address_bytes(
def _derive_shelley_address( def _derive_shelley_address(
keychain: seed.Keychain, keychain: seed.Keychain, parameters: CardanoAddressParametersType, network_id: int
parameters: CardanoAddressParametersType,
network_id: int,
) -> bytes: ) -> bytes:
if parameters.address_type == CardanoAddressType.BASE: header = _create_address_header(parameters.address_type, network_id)
address = _derive_base_address(
keychain,
parameters.address_n,
parameters.address_n_staking,
parameters.staking_key_hash,
network_id,
)
elif parameters.address_type == CardanoAddressType.POINTER:
# ensured by validate_address_parameters
assert parameters.certificate_pointer is not None
address = _derive_pointer_address(
keychain,
parameters.address_n,
parameters.certificate_pointer,
network_id,
)
elif parameters.address_type == CardanoAddressType.ENTERPRISE:
address = _derive_enterprise_address(keychain, parameters.address_n, network_id)
elif parameters.address_type == CardanoAddressType.REWARD:
address = _derive_reward_address(keychain, parameters.address_n, network_id)
else:
raise INVALID_ADDRESS_PARAMETERS
return address payment_part = _get_address_payment_part(keychain, parameters)
staking_part = _get_address_staking_part(keychain, parameters)
return header + payment_part + staking_part
def _create_address_header(address_type: CardanoAddressType, network_id: int) -> bytes: def _create_address_header(address_type: CardanoAddressType, network_id: int) -> bytes:
@ -295,33 +376,30 @@ def _create_address_header(address_type: CardanoAddressType, network_id: int) ->
return header.to_bytes(1, "little") return header.to_bytes(1, "little")
def _derive_base_address( def _get_address_payment_part(
keychain: seed.Keychain, keychain: seed.Keychain, parameters: CardanoAddressParametersType
path: list[int],
staking_path: list[int],
staking_key_hash: bytes | None,
network_id: int,
) -> bytes: ) -> bytes:
header = _create_address_header(CardanoAddressType.BASE, network_id) if parameters.address_n:
spending_key_hash = get_public_key_hash(keychain, path) return get_public_key_hash(keychain, parameters.address_n)
elif parameters.script_payment_hash:
if staking_key_hash is None: return parameters.script_payment_hash
staking_key_hash = get_public_key_hash(keychain, staking_path) else:
return bytes()
return header + spending_key_hash + staking_key_hash
def _derive_pointer_address( def _get_address_staking_part(
keychain: seed.Keychain, keychain: seed.Keychain, parameters: CardanoAddressParametersType
path: list[int],
pointer: CardanoBlockchainPointerType,
network_id: int,
) -> bytes: ) -> bytes:
header = _create_address_header(CardanoAddressType.POINTER, network_id) if parameters.staking_key_hash:
spending_key_hash = get_public_key_hash(keychain, path) return parameters.staking_key_hash
encoded_pointer = _encode_certificate_pointer(pointer) elif parameters.address_n_staking:
return get_public_key_hash(keychain, parameters.address_n_staking)
return header + spending_key_hash + encoded_pointer elif parameters.script_staking_hash:
return parameters.script_staking_hash
elif parameters.certificate_pointer:
return _encode_certificate_pointer(parameters.certificate_pointer)
else:
return bytes()
def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes: def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes:
@ -330,36 +408,3 @@ def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes:
certificate_index_encoded = variable_length_encode(pointer.certificate_index) certificate_index_encoded = variable_length_encode(pointer.certificate_index)
return bytes(block_index_encoded + tx_index_encoded + certificate_index_encoded) return bytes(block_index_encoded + tx_index_encoded + certificate_index_encoded)
def _derive_enterprise_address(
keychain: seed.Keychain,
path: list[int],
network_id: int,
) -> bytes:
header = _create_address_header(CardanoAddressType.ENTERPRISE, network_id)
spending_key_hash = get_public_key_hash(keychain, path)
return header + spending_key_hash
def _derive_reward_address(
keychain: seed.Keychain,
path: list[int],
network_id: int,
) -> bytes:
staking_key_hash = get_public_key_hash(keychain, path)
return pack_reward_address_bytes(staking_key_hash, network_id)
def pack_reward_address_bytes(
staking_key_hash: bytes,
network_id: int,
) -> bytes:
"""
Helper function to transform raw staking key hash into reward address
"""
header = _create_address_header(CardanoAddressType.REWARD, network_id)
return header + staking_key_hash

@ -1,19 +1,10 @@
from trezor import log, wire from trezor import log, wire
from trezor.messages import CardanoAddress from trezor.messages import CardanoAddress
from trezor.ui.layouts import show_address
from apps.common import paths
from . import seed from . import seed
from .address import derive_human_readable_address, validate_address_parameters from .address import derive_human_readable_address, validate_address_parameters
from .helpers import protocol_magics, staking_use_cases from .helpers.credential import Credential, should_show_address_credentials
from .helpers.paths import SCHEMA_PAYMENT, SCHEMA_STAKING from .layout import show_cardano_address, show_credentials
from .helpers.utils import to_account_path
from .layout import (
ADDRESS_TYPE_NAMES,
show_warning_address_foreign_staking_key,
show_warning_address_pointer,
)
from .sign_tx import validate_network_info from .sign_tx import validate_network_info
if False: if False:
@ -29,15 +20,6 @@ async def get_address(
) -> CardanoAddress: ) -> CardanoAddress:
address_parameters = msg.address_parameters address_parameters = msg.address_parameters
await paths.validate_path(
ctx,
keychain,
address_parameters.address_n,
# path must match the PAYMENT or STAKING schema
SCHEMA_PAYMENT.match(address_parameters.address_n)
or SCHEMA_STAKING.match(address_parameters.address_n),
)
validate_network_info(msg.network_id, msg.protocol_magic) validate_network_info(msg.network_id, msg.protocol_magic)
validate_address_parameters(address_parameters) validate_address_parameters(address_parameters)
@ -51,51 +33,22 @@ async def get_address(
raise wire.ProcessError("Deriving address failed") raise wire.ProcessError("Deriving address failed")
if msg.show_display: if msg.show_display:
await _display_address( await _display_address(ctx, address_parameters, address, msg.protocol_magic)
ctx, keychain, address_parameters, address, msg.protocol_magic
)
return CardanoAddress(address=address) return CardanoAddress(address=address)
async def _display_address( async def _display_address(
ctx: wire.Context, ctx: wire.Context,
keychain: seed.Keychain,
address_parameters: CardanoAddressParametersType, address_parameters: CardanoAddressParametersType,
address: str, address: str,
protocol_magic: int, protocol_magic: int,
) -> None: ) -> None:
await _show_staking_warnings(ctx, keychain, address_parameters) if should_show_address_credentials(address_parameters):
await show_credentials(
network_name = None
if not protocol_magics.is_mainnet(protocol_magic):
network_name = protocol_magics.to_ui_string(protocol_magic)
address_n = paths.address_n_to_str(address_parameters.address_n)
await show_address(
ctx,
address=address,
title="%s address" % ADDRESS_TYPE_NAMES[address_parameters.address_type],
network=network_name,
address_extra=address_n,
title_qr=address_n,
)
async def _show_staking_warnings(
ctx: wire.Context,
keychain: seed.Keychain,
address_parameters: CardanoAddressParametersType,
) -> None:
staking_type = staking_use_cases.get(keychain, address_parameters)
if staking_type == staking_use_cases.MISMATCH:
await show_warning_address_foreign_staking_key(
ctx, ctx,
to_account_path(address_parameters.address_n), Credential.payment_credential(address_parameters),
to_account_path(address_parameters.address_n_staking), Credential.stake_credential(address_parameters),
address_parameters.staking_key_hash,
) )
elif staking_type == staking_use_cases.POINTER_ADDRESS:
# ensured in _derive_shelley_address: await show_cardano_address(ctx, address_parameters, address, protocol_magic)
assert address_parameters.certificate_pointer is not None
await show_warning_address_pointer(ctx, address_parameters.certificate_pointer)

@ -0,0 +1,220 @@
from trezor.enums import CardanoAddressType
from ...common.paths import address_n_to_str
from .paths import CHAIN_STAKING_KEY, SCHEMA_PAYMENT, SCHEMA_STAKING
from .utils import format_key_hash, format_script_hash, to_account_path
if False:
from trezor.messages import (
CardanoBlockchainPointerType,
CardanoAddressParametersType,
)
from trezor.ui.layouts import PropertyType
class Credential:
"""
Serves mainly as a wrapper object for credentials (so that they don't have to be
passed into functions separately) which also determines all properties that should be shown
as warnings.
Also contains functions which simplify displaying the credential.
"""
type_name: str
address_type: CardanoAddressType
path: list[int]
key_hash: bytes | None
script_hash: bytes | None
pointer: CardanoBlockchainPointerType | None
is_reward: bool = False
is_no_staking: bool = False
is_mismatch: bool = False
is_unusual_path: bool = False
is_other_warning: bool = False
def __init__(
self,
type_name: str,
address_type: CardanoAddressType,
path: list[int],
key_hash: bytes | None,
script_hash: bytes | None,
pointer: CardanoBlockchainPointerType | None,
):
self.type_name = type_name
self.address_type = address_type
self.path = path
self.key_hash = key_hash
self.script_hash = script_hash
self.pointer = pointer
@classmethod
def payment_credential(
cls, address_params: CardanoAddressParametersType
) -> "Credential":
address_type = address_params.address_type
credential = cls(
"payment",
address_type,
address_params.address_n,
None,
address_params.script_payment_hash,
None,
)
if address_type in (
CardanoAddressType.BASE,
CardanoAddressType.BASE_KEY_SCRIPT,
CardanoAddressType.POINTER,
CardanoAddressType.ENTERPRISE,
CardanoAddressType.BYRON,
):
if not SCHEMA_PAYMENT.match(address_params.address_n):
credential.is_unusual_path = True
elif address_type in (
CardanoAddressType.BASE_SCRIPT_KEY,
CardanoAddressType.BASE_SCRIPT_SCRIPT,
CardanoAddressType.POINTER_SCRIPT,
CardanoAddressType.ENTERPRISE_SCRIPT,
):
credential.is_other_warning = True
elif address_type in (
CardanoAddressType.REWARD,
CardanoAddressType.REWARD_SCRIPT,
):
credential.is_reward = True
else:
raise RuntimeError # we didn't cover all address types
return credential
@classmethod
def stake_credential(
cls, address_params: CardanoAddressParametersType
) -> "Credential":
address_type = address_params.address_type
credential = cls(
"stake",
address_type,
address_params.address_n_staking,
address_params.staking_key_hash,
address_params.script_staking_hash,
address_params.certificate_pointer,
)
if address_type == CardanoAddressType.BASE:
if address_params.staking_key_hash:
credential.is_other_warning = True
else:
if not SCHEMA_STAKING.match(address_params.address_n_staking):
credential.is_unusual_path = True
if not _do_base_address_credentials_match(
address_params.address_n,
address_params.address_n_staking,
):
credential.is_mismatch = True
elif address_type == CardanoAddressType.BASE_SCRIPT_KEY:
if address_params.address_n_staking and not SCHEMA_STAKING.match(
address_params.address_n_staking
):
credential.is_unusual_path = True
elif address_type in (
CardanoAddressType.POINTER,
CardanoAddressType.POINTER_SCRIPT,
):
credential.is_other_warning = True
elif address_type == CardanoAddressType.REWARD:
if not SCHEMA_STAKING.match(address_params.address_n_staking):
credential.is_unusual_path = True
elif address_type in (
CardanoAddressType.BASE_KEY_SCRIPT,
CardanoAddressType.BASE_SCRIPT_SCRIPT,
CardanoAddressType.REWARD_SCRIPT,
):
credential.is_other_warning = True
elif address_type in (
CardanoAddressType.ENTERPRISE,
CardanoAddressType.ENTERPRISE_SCRIPT,
CardanoAddressType.BYRON,
):
credential.is_no_staking = True
else:
raise RuntimeError # we didn't cover all address types
return credential
def should_warn(self) -> bool:
return any(
(
self.is_reward,
self.is_no_staking,
self.is_mismatch,
self.is_unusual_path,
self.is_other_warning,
)
)
def is_set(self) -> bool:
return any((self.path, self.key_hash, self.script_hash, self.pointer))
def get_title(self) -> str:
if self.path:
return "path"
elif self.key_hash:
return "key hash"
elif self.script_hash:
return "script"
elif self.pointer:
return "pointer"
else:
return ""
def format(self) -> list[PropertyType]:
if self.path:
return [(None, address_n_to_str(self.path))]
elif self.key_hash:
return [(None, format_key_hash(self.key_hash, False))]
elif self.script_hash:
return [(None, format_script_hash(self.script_hash))]
elif self.pointer:
return [
("Block: %s" % self.pointer.block_index, None),
("Transaction: %s" % self.pointer.tx_index, None),
("Certificate: %s" % self.pointer.certificate_index, None),
]
else:
return []
def should_show_address_credentials(
address_parameters: CardanoAddressParametersType,
) -> bool:
return not (
address_parameters.address_type == CardanoAddressType.BASE
and SCHEMA_PAYMENT.match(address_parameters.address_n)
and _do_base_address_credentials_match(
address_parameters.address_n,
address_parameters.address_n_staking,
)
)
def _do_base_address_credentials_match(
address_n: list[int],
address_n_staking: list[int],
) -> bool:
return address_n_staking == _path_to_staking_path(address_n)
def _path_to_staking_path(path: list[int]) -> list[int]:
return to_account_path(path) + [CHAIN_STAKING_KEY, 0]

@ -19,8 +19,9 @@ SCHEMA_STAKING = PathSchema.parse("m/[1852]'/coin_type'/account'/2/0", SLIP44_ID
SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse("m/[1852]'/coin_type'/[0-%s]'/2/0" % (HARDENED - 1), SLIP44_ID) SCHEMA_STAKING_ANY_ACCOUNT = PathSchema.parse("m/[1852]'/coin_type'/[0-%s]'/2/0" % (HARDENED - 1), SLIP44_ID)
# fmt: on # fmt: on
ACCOUNT_PATH_LENGTH = const(3)
ACCOUNT_PATH_INDEX = const(2) ACCOUNT_PATH_INDEX = const(2)
ACCOUNT_PATH_LENGTH = const(3)
CHAIN_STAKING_KEY = const(2)
CHANGE_OUTPUT_PATH_NAME = "Change output path" CHANGE_OUTPUT_PATH_NAME = "Change output path"
CHANGE_OUTPUT_STAKING_PATH_NAME = "Change output staking path" CHANGE_OUTPUT_STAKING_PATH_NAME = "Change output staking path"

@ -1,54 +0,0 @@
from trezor.enums import CardanoAddressType
from ..address import get_public_key_hash
from ..seed import is_shelley_path
from .utils import to_account_path
if False:
from trezor.messages import CardanoAddressParametersType
from ..seed import Keychain
"""
Used as a helper when deciding what warnings we should
display to the user during get_address and sign_tx depending
on the type of address and its parameters.
"""
NO_STAKING = 0
MATCH = 1
MISMATCH = 2
POINTER_ADDRESS = 3
def get(keychain: Keychain, address_parameters: CardanoAddressParametersType) -> int:
address_type = address_parameters.address_type
if address_type == CardanoAddressType.BASE:
if not is_shelley_path(address_parameters.address_n):
return MISMATCH
spending_account_staking_path = _path_to_staking_path(
address_parameters.address_n
)
if address_parameters.address_n_staking:
if address_parameters.address_n_staking != spending_account_staking_path:
return MISMATCH
else:
staking_key_hash = get_public_key_hash(
keychain, spending_account_staking_path
)
if address_parameters.staking_key_hash != staking_key_hash:
return MISMATCH
return MATCH
elif address_type == CardanoAddressType.POINTER:
return POINTER_ADDRESS
elif address_type == CardanoAddressType.REWARD:
return MATCH
else:
return NO_STAKING
def _path_to_staking_path(path: list[int]) -> list[int]:
return to_account_path(path) + [2, 0]

@ -6,6 +6,7 @@ from trezor.enums import (
CardanoNativeScriptHashDisplayFormat, CardanoNativeScriptHashDisplayFormat,
CardanoNativeScriptType, CardanoNativeScriptType,
) )
from trezor.messages import CardanoAddressParametersType
from trezor.strings import format_amount from trezor.strings import format_amount
from trezor.ui.layouts import ( from trezor.ui.layouts import (
confirm_blob, confirm_blob,
@ -13,16 +14,13 @@ from trezor.ui.layouts import (
confirm_output, confirm_output,
confirm_path_warning, confirm_path_warning,
confirm_properties, confirm_properties,
show_address,
) )
from apps.common.paths import address_n_to_str from apps.common.paths import address_n_to_str
from . import seed from . import seed
from .address import ( from .address import derive_human_readable_address
encode_human_readable_address,
get_public_key_hash,
pack_reward_address_bytes,
)
from .helpers import protocol_magics from .helpers import protocol_magics
from .helpers.utils import ( from .helpers.utils import (
format_account_number, format_account_number,
@ -37,7 +35,6 @@ from .helpers.utils import (
if False: if False:
from trezor import wire from trezor import wire
from trezor.messages import ( from trezor.messages import (
CardanoBlockchainPointerType,
CardanoNativeScript, CardanoNativeScript,
CardanoTxCertificate, CardanoTxCertificate,
CardanoTxWithdrawal, CardanoTxWithdrawal,
@ -48,14 +45,21 @@ if False:
) )
from trezor.ui.layouts import PropertyType from trezor.ui.layouts import PropertyType
from .helpers.credential import Credential
ADDRESS_TYPE_NAMES = { ADDRESS_TYPE_NAMES = {
CardanoAddressType.BYRON: "Legacy", CardanoAddressType.BYRON: "Legacy",
CardanoAddressType.BASE: "Base", CardanoAddressType.BASE: "Base",
CardanoAddressType.BASE_SCRIPT_KEY: "Base",
CardanoAddressType.BASE_KEY_SCRIPT: "Base",
CardanoAddressType.BASE_SCRIPT_SCRIPT: "Base",
CardanoAddressType.POINTER: "Pointer", CardanoAddressType.POINTER: "Pointer",
CardanoAddressType.POINTER_SCRIPT: "Pointer",
CardanoAddressType.ENTERPRISE: "Enterprise", CardanoAddressType.ENTERPRISE: "Enterprise",
CardanoAddressType.ENTERPRISE_SCRIPT: "Enterprise",
CardanoAddressType.REWARD: "Reward", CardanoAddressType.REWARD: "Reward",
CardanoAddressType.REWARD_SCRIPT: "Reward",
} }
SCRIPT_TYPE_NAMES = { SCRIPT_TYPE_NAMES = {
@ -184,13 +188,15 @@ async def confirm_sending(
ctx: wire.Context, ctx: wire.Context,
ada_amount: int, ada_amount: int,
to: str, to: str,
is_change_output: bool,
) -> None: ) -> None:
subtitle = "Change amount:" if is_change_output else "Confirm sending:"
await confirm_output( await confirm_output(
ctx, ctx,
to, to,
format_coin_amount(ada_amount), format_coin_amount(ada_amount),
title="Confirm transaction", title="Confirm transaction",
subtitle="Confirm sending:", subtitle=subtitle,
font_amount=ui.BOLD, font_amount=ui.BOLD,
width_paginated=17, width_paginated=17,
to_str="\nto\n", to_str="\nto\n",
@ -220,103 +226,91 @@ async def confirm_sending_token(
) )
async def show_warning_tx_output_contains_tokens(ctx: wire.Context) -> None: async def show_credentials(
await confirm_metadata( ctx: wire.Context,
ctx, payment_credential: Credential,
"confirm_tokens", stake_credential: Credential,
title="Confirm transaction", is_change_output: bool = False,
content="The following\ntransaction output\ncontains tokens.",
larger_vspace=True,
br_code=ButtonRequestType.Other,
)
async def show_warning_path(ctx: wire.Context, path: list[int], title: str) -> None:
await confirm_path_warning(ctx, address_n_to_str(path), path_type=title)
async def show_warning_tx_no_staking_info(
ctx: wire.Context, address_type: CardanoAddressType, amount: int
) -> None: ) -> None:
atype = ADDRESS_TYPE_NAMES[address_type].lower() await _show_credential(ctx, payment_credential, is_change_output)
content = "Change %s address has no stake rights.\nChange amount:\n{}" % atype await _show_credential(ctx, stake_credential, is_change_output)
await confirm_metadata(
ctx,
"warning_staking",
title="Confirm transaction",
content=content,
param=format_coin_amount(amount),
hide_continue=True,
br_code=ButtonRequestType.Other,
)
async def show_warning_tx_pointer_address( async def _show_credential(
ctx: wire.Context, ctx: wire.Context,
pointer: CardanoBlockchainPointerType, credential: Credential,
amount: int, is_change_output: bool = False,
) -> None: ) -> None:
await confirm_properties( if is_change_output:
ctx, title = "Confirm transaction"
"warning_pointer", else:
title="Confirm transaction", title = "%s address" % ADDRESS_TYPE_NAMES[credential.address_type]
props=[
("Change address has a\npointer with staking\nrights.\n\n\n", None),
(
"Pointer:",
"%s, %s, %s"
% (
pointer.block_index,
pointer.tx_index,
pointer.certificate_index,
),
),
("Change amount:", format_coin_amount(amount)),
],
br_code=ButtonRequestType.Other,
)
props: list[PropertyType] = []
async def show_warning_tx_different_staking_account( # Credential can be empty in case of enterprise address stake credential
ctx: wire.Context, # and reward address payment credential. In that case we don't want to
staking_account_path: list[int], # show some of the "props".
amount: int, if credential.is_set():
) -> None: if is_change_output:
await confirm_properties( address_usage = "Change address"
ctx, else:
"warning_differentstaking", address_usage = "Address"
title="Confirm transaction",
props=[ credential_title = credential.get_title()
props.append(
( (
"Change address staking rights do not match the current account.\n\n", "%s %s credential is a %s:"
% (address_usage, credential.type_name, credential_title),
None, None,
), )
)
props.extend(credential.format())
if credential.is_unusual_path:
props.append((None, "Path is unusual."))
if credential.is_mismatch:
props.append((None, "Credential doesn't match payment credential."))
if credential.is_reward:
props.append(("Address is a reward address.", None))
if credential.is_no_staking:
props.append(
( (
"Staking account %s:" % format_account_number(staking_account_path), "%s address - no staking rewards."
address_n_to_str(staking_account_path), % ADDRESS_TYPE_NAMES[credential.address_type],
), None,
("Change amount:", format_coin_amount(amount)), )
], )
if credential.should_warn():
icon = ui.ICON_WRONG
icon_color = ui.RED
else:
icon = ui.ICON_SEND
icon_color = ui.GREEN
await confirm_properties(
ctx,
"confirm_credential",
title=title,
props=props,
icon=icon,
icon_color=icon_color,
br_code=ButtonRequestType.Other, br_code=ButtonRequestType.Other,
) )
async def show_warning_tx_staking_key_hash( async def show_warning_path(ctx: wire.Context, path: list[int], title: str) -> None:
ctx: wire.Context, await confirm_path_warning(ctx, address_n_to_str(path), path_type=title)
staking_key_hash: bytes,
amount: int,
) -> None:
props = [
("Change address staking rights do not match the current account.\n\n", None),
("Staking key hash:", staking_key_hash),
("Change amount:", format_coin_amount(amount)),
]
await confirm_properties(
async def show_warning_tx_output_contains_tokens(ctx: wire.Context) -> None:
await confirm_metadata(
ctx, ctx,
"confirm_different_stakingrights", "confirm_tokens",
title="Confirm transaction", title="Confirm transaction",
props=props, content="The following\ntransaction output\ncontains tokens.",
larger_vspace=True,
br_code=ButtonRequestType.Other, br_code=ButtonRequestType.Other,
) )
@ -414,6 +408,7 @@ async def confirm_stake_pool_owner(
ctx: wire.Context, ctx: wire.Context,
keychain: seed.Keychain, keychain: seed.Keychain,
owner: CardanoPoolOwner, owner: CardanoPoolOwner,
protocol_magic: int,
network_id: int, network_id: int,
) -> None: ) -> None:
props: list[tuple[str, str | None]] = [] props: list[tuple[str, str | None]] = []
@ -421,11 +416,14 @@ async def confirm_stake_pool_owner(
props.append(("Pool owner:", address_n_to_str(owner.staking_key_path))) props.append(("Pool owner:", address_n_to_str(owner.staking_key_path)))
props.append( props.append(
( (
encode_human_readable_address( derive_human_readable_address(
pack_reward_address_bytes( keychain,
get_public_key_hash(keychain, owner.staking_key_path), CardanoAddressParametersType(
network_id, address_type=CardanoAddressType.REWARD,
) address_n=owner.staking_key_path,
),
protocol_magic,
network_id,
), ),
None, None,
) )
@ -435,8 +433,14 @@ async def confirm_stake_pool_owner(
props.append( props.append(
( (
"Pool owner:", "Pool owner:",
encode_human_readable_address( derive_human_readable_address(
pack_reward_address_bytes(owner.staking_key_hash, network_id) keychain,
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
staking_key_hash=owner.staking_key_hash,
),
protocol_magic,
network_id,
), ),
) )
) )
@ -605,20 +609,39 @@ async def show_warning_tx_network_unverifiable(ctx: wire.Context) -> None:
) )
async def show_warning_address_pointer( async def show_cardano_address(
ctx: wire.Context, pointer: CardanoBlockchainPointerType ctx: wire.Context,
address_parameters: CardanoAddressParametersType,
address: str,
protocol_magic: int,
) -> None: ) -> None:
content = "Pointer address:\nBlock: %s\nTransaction: %s\nCertificate: %s" % ( network_name = None
pointer.block_index, if not protocol_magics.is_mainnet(protocol_magic):
pointer.tx_index, network_name = protocol_magics.to_ui_string(protocol_magic)
pointer.certificate_index,
) title = "%s address" % ADDRESS_TYPE_NAMES[address_parameters.address_type]
await confirm_metadata( address_extra = None
title_qr = title
if address_parameters.address_type in (
CardanoAddressType.BYRON,
CardanoAddressType.BASE,
CardanoAddressType.BASE_KEY_SCRIPT,
CardanoAddressType.POINTER,
CardanoAddressType.ENTERPRISE,
CardanoAddressType.REWARD,
):
if address_parameters.address_n:
address_extra = address_n_to_str(address_parameters.address_n)
title_qr = address_n_to_str(address_parameters.address_n)
elif address_parameters.address_n_staking:
address_extra = address_n_to_str(address_parameters.address_n_staking)
title_qr = address_n_to_str(address_parameters.address_n_staking)
await show_address(
ctx, ctx,
"warning_pointer", address=address,
title="Warning", title=title,
icon=ui.ICON_WRONG, network=network_name,
icon_color=ui.RED, address_extra=address_extra,
content=content, title_qr=title_qr,
br_code=ButtonRequestType.Other,
) )

@ -64,9 +64,9 @@ from .helpers import (
LOVELACE_MAX_SUPPLY, LOVELACE_MAX_SUPPLY,
network_ids, network_ids,
protocol_magics, protocol_magics,
staking_use_cases,
) )
from .helpers.account_path_check import AccountPathChecker 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.hash_builder_collection import HashBuilderDict, HashBuilderList
from .helpers.paths import ( from .helpers.paths import (
CERTIFICATE_PATH_NAME, CERTIFICATE_PATH_NAME,
@ -89,13 +89,10 @@ from .layout import (
confirm_stake_pool_registration_final, confirm_stake_pool_registration_final,
confirm_transaction, confirm_transaction,
confirm_withdrawal, confirm_withdrawal,
show_credentials,
show_warning_path, show_warning_path,
show_warning_tx_different_staking_account,
show_warning_tx_network_unverifiable, show_warning_tx_network_unverifiable,
show_warning_tx_no_staking_info,
show_warning_tx_output_contains_tokens, show_warning_tx_output_contains_tokens,
show_warning_tx_pointer_address,
show_warning_tx_staking_key_hash,
) )
from .seed import is_byron_path from .seed import is_byron_path
@ -302,8 +299,16 @@ async def _process_outputs(
total_amount = 0 total_amount = 0
for _ in range(outputs_count): for _ in range(outputs_count):
output: CardanoTxOutput = await ctx.call(CardanoTxItemAck(), CardanoTxOutput) output: CardanoTxOutput = await ctx.call(CardanoTxItemAck(), CardanoTxOutput)
_validate_output(output, protocol_magic, network_id, account_path_checker) _validate_output(
if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: output,
signing_mode,
protocol_magic,
network_id,
account_path_checker,
)
should_show_output = _should_show_output(output, signing_mode)
if should_show_output:
await _show_output( await _show_output(
ctx, ctx,
keychain, keychain,
@ -335,7 +340,7 @@ async def _process_outputs(
ctx, ctx,
asset_groups_dict, asset_groups_dict,
output.asset_groups_count, output.asset_groups_count,
_should_show_tokens(output, signing_mode), should_show_output,
) )
total_amount += output.amount total_amount += output.amount
@ -634,6 +639,7 @@ def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTxInit) -> No
def _validate_output( def _validate_output(
output: CardanoTxOutput, output: CardanoTxOutput,
signing_mode: CardanoTxSigningMode,
protocol_magic: int, protocol_magic: int,
network_id: int, network_id: int,
account_path_checker: AccountPathChecker, account_path_checker: AccountPathChecker,
@ -641,8 +647,9 @@ def _validate_output(
if output.address_parameters and output.address is not None: if output.address_parameters and output.address is not None:
raise INVALID_OUTPUT raise INVALID_OUTPUT
if output.address_parameters: if address_parameters := output.address_parameters:
validate_address_parameters(output.address_parameters) validate_address_parameters(address_parameters)
_fail_if_strict_and_unusual(address_parameters)
elif output.address is not None: elif output.address is not None:
validate_output_address(output.address, protocol_magic, network_id) validate_output_address(output.address, protocol_magic, network_id)
else: else:
@ -651,6 +658,23 @@ def _validate_output(
account_path_checker.add_output(output) account_path_checker.add_output(output)
def _should_show_output(
output: CardanoTxOutput,
signing_mode: CardanoTxSigningMode,
) -> bool:
if signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER:
# In a pool registration transaction, there are no inputs belonging to the user
# and no spending witnesses. It is thus safe to not show the outputs.
return False
if output.address_parameters: # is change output
if not should_show_address_credentials(output.address_parameters):
# we don't need to display simple address outputs
return False
return True
async def _show_output( async def _show_output(
ctx: wire.Context, ctx: wire.Context,
keychain: seed.Keychain, keychain: seed.Keychain,
@ -659,33 +683,30 @@ async def _show_output(
protocol_magic: int, protocol_magic: int,
network_id: int, network_id: int,
) -> None: ) -> None:
if output.address_parameters: if output.asset_groups_count > 0:
await _fail_or_warn_if_invalid_path( await show_warning_tx_output_contains_tokens(ctx)
ctx,
SCHEMA_PAYMENT,
output.address_parameters.address_n,
CHANGE_OUTPUT_PATH_NAME,
)
await _show_change_output_staking_warnings( is_change_output: bool
ctx, keychain, output.address_parameters, output.amount if address_parameters := output.address_parameters:
) is_change_output = True
if _should_hide_output(output.address_parameters.address_n): await show_credentials(
return ctx,
Credential.payment_credential(address_parameters),
Credential.stake_credential(address_parameters),
is_change_output=True,
)
address = derive_human_readable_address( address = derive_human_readable_address(
keychain, output.address_parameters, protocol_magic, network_id keychain, address_parameters, protocol_magic, network_id
) )
else: else:
is_change_output = False
assert output.address is not None # _validate_output assert output.address is not None # _validate_output
address = output.address address = output.address
if output.asset_groups_count > 0: await confirm_sending(ctx, output.amount, address, is_change_output)
await show_warning_tx_output_contains_tokens(ctx)
if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
await confirm_sending(ctx, output.amount, address)
def _validate_asset_group( def _validate_asset_group(
@ -777,7 +798,11 @@ async def _show_stake_pool_registration_certificate(
async def _show_pool_owner( async def _show_pool_owner(
ctx: wire.Context, keychain: seed.Keychain, owner: CardanoPoolOwner, network_id: int ctx: wire.Context,
keychain: seed.Keychain,
owner: CardanoPoolOwner,
protocol_magic: int,
network_id: int,
) -> None: ) -> None:
if owner.staking_key_path: if owner.staking_key_path:
await _fail_or_warn_if_invalid_path( await _fail_or_warn_if_invalid_path(
@ -787,7 +812,7 @@ async def _show_pool_owner(
POOL_OWNER_STAKING_PATH_NAME, POOL_OWNER_STAKING_PATH_NAME,
) )
await confirm_stake_pool_owner(ctx, keychain, owner, network_id) await confirm_stake_pool_owner(ctx, keychain, owner, protocol_magic, network_id)
def _validate_witness_request( def _validate_witness_request(
@ -824,78 +849,11 @@ async def _show_witness(
ctx: wire.Context, ctx: wire.Context,
witness_path: list[int], witness_path: list[int],
) -> None: ) -> None:
if not SCHEMA_PAYMENT.match(witness_path) and not SCHEMA_STAKING.match( is_payment = SCHEMA_PAYMENT.match(witness_path)
witness_path is_staking = SCHEMA_STAKING.match(witness_path)
):
await _fail_or_warn_path(
ctx,
witness_path,
WITNESS_PATH_NAME,
)
async def _show_change_output_staking_warnings(
ctx: wire.Context,
keychain: seed.Keychain,
address_parameters: CardanoAddressParametersType,
amount: int,
) -> None:
address_type = address_parameters.address_type
if (
address_type == CardanoAddressType.BASE
and not address_parameters.staking_key_hash
):
await _fail_or_warn_if_invalid_path(
ctx,
SCHEMA_STAKING,
address_parameters.address_n_staking,
CHANGE_OUTPUT_STAKING_PATH_NAME,
)
staking_use_case = staking_use_cases.get(keychain, address_parameters)
if staking_use_case == staking_use_cases.NO_STAKING:
await show_warning_tx_no_staking_info(ctx, address_type, amount)
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(
ctx,
address_parameters.certificate_pointer,
amount,
)
elif staking_use_case == staking_use_cases.MISMATCH:
if address_parameters.address_n_staking:
await show_warning_tx_different_staking_account(
ctx,
to_account_path(address_parameters.address_n_staking),
amount,
)
else:
# ensured in _validate_base_address_staking_info:
assert address_parameters.staking_key_hash
await show_warning_tx_staking_key_hash(
ctx,
address_parameters.staking_key_hash,
amount,
)
def _should_hide_output(path: list[int]) -> bool:
"""Return whether the output address is from a safe path, so it could be hidden."""
return SCHEMA_PAYMENT.match(path)
if not is_payment and not is_staking:
def _should_show_tokens( await _fail_or_warn_path(ctx, witness_path, WITNESS_PATH_NAME)
output: CardanoTxOutput, signing_mode: CardanoTxSigningMode
) -> bool:
if signing_mode != CardanoTxSigningMode.ORDINARY_TRANSACTION:
return False
if output.address_parameters:
return not _should_hide_output(output.address_parameters.address_n)
return True
def _is_network_id_verifiable(msg: CardanoSignTxInit) -> bool: def _is_network_id_verifiable(msg: CardanoSignTxInit) -> bool:
@ -925,3 +883,16 @@ async def _fail_or_warn_path(
raise wire.DataError("Invalid %s" % path_name.lower()) raise wire.DataError("Invalid %s" % path_name.lower())
else: else:
await show_warning_path(ctx, path, path_name) await show_warning_path(ctx, path, path_name)
def _fail_if_strict_and_unusual(
address_parameters: CardanoAddressParametersType,
) -> None:
if not safety_checks.is_strict():
return
if Credential.payment_credential(address_parameters).is_unusual_path:
raise wire.DataError("Invalid %s" % CHANGE_OUTPUT_PATH_NAME.lower())
if Credential.stake_credential(address_parameters).is_unusual_path:
raise wire.DataError("Invalid %s" % CHANGE_OUTPUT_STAKING_PATH_NAME.lower())

@ -1123,6 +1123,8 @@ if TYPE_CHECKING:
address_n_staking: "list[int]" address_n_staking: "list[int]"
staking_key_hash: "bytes | None" staking_key_hash: "bytes | None"
certificate_pointer: "CardanoBlockchainPointerType | None" certificate_pointer: "CardanoBlockchainPointerType | None"
script_payment_hash: "bytes | None"
script_staking_hash: "bytes | None"
def __init__( def __init__(
self, self,
@ -1132,6 +1134,8 @@ if TYPE_CHECKING:
address_n_staking: "list[int] | None" = None, address_n_staking: "list[int] | None" = None,
staking_key_hash: "bytes | None" = None, staking_key_hash: "bytes | None" = None,
certificate_pointer: "CardanoBlockchainPointerType | None" = None, certificate_pointer: "CardanoBlockchainPointerType | None" = None,
script_payment_hash: "bytes | None" = None,
script_staking_hash: "bytes | None" = None,
) -> None: ) -> None:
pass pass

@ -285,142 +285,154 @@ class TestCardanoAddress(unittest.TestCase):
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
self.assertEqual(hexlify(n.chain_code()), chain) self.assertEqual(hexlify(n.chain_code()), chain)
def test_base_address(self): def test_testnet_byron_address(self):
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice" mnemonic = "all all all all all all all all all all all all"
passphrase = "" passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase) node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
keychain = Keychain(node) keychain = Keychain(node)
test_vectors = [ addresses = [
# network id, account, expected result "2657WMsDfac5F3zbgs9BwNWx3dhGAJERkAL93gPa68NJ2i8mbCHm2pLUHWSj8Mfea",
# data generated with code under test "2657WMsDfac6ezKWszxLFqJjSUgpg9NgxKc1koqi24sVpRaPhiwMaExk4useKn5HA",
(network_ids.MAINNET, 4, "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r"), "2657WMsDfac7hr1ioJGr6g7r6JRx4r1My8Rj91tcPTeVjJDpfBYKURrPG2zVLx2Sq",
(network_ids.TESTNET, 4, "addr_test1qr4sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlswzkqcu"),
] ]
for network_id, account, expected_address in test_vectors: for i, expected in enumerate(addresses):
# 44'/1815'/0'/0/i'
address_parameters = CardanoAddressParametersType( address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.BASE, address_type=CardanoAddressType.BYRON,
address_n=[1852 | HARDENED, 1815 | HARDENED, account | HARDENED, 0, 0], address_n=[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, account | HARDENED, 2, 0]
) )
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id) address = derive_human_readable_address(keychain, address_parameters, protocol_magics.TESTNET, 0)
self.assertEqual(expected, address)
self.assertEqual(actual_address, expected_address)
def test_base_address_with_staking_key_hash(self): def test_derive_address(self):
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice" mnemonic = "all all all all all all all all all all all all"
passphrase = "" passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase) node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
keychain = Keychain(node) keychain = Keychain(node)
test_vectors = [ address_parameters = {
# network id, account, staking key hash, expected result "BASE": CardanoAddressParametersType(
# own staking key hash address_type=CardanoAddressType.BASE,
# data generated with code under test address_n=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 0, 0],
(network_ids.MAINNET, 4, unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff"), "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r"), address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 2, 0]
(network_ids.TESTNET, 4, unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff"), "addr_test1qr4sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlswzkqcu"), ),
# staking key hash not owned - derived with "all all..." mnenomnic, data generated with code under test "BASE_OWN_STAKING_KEY_HASH": CardanoAddressParametersType(
(network_ids.MAINNET, 4, unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"), "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsxrrvc2"), address_type=CardanoAddressType.BASE,
(network_ids.MAINNET, 0, unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"), "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzersj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms6xjnst"), address_n=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 0, 0],
(network_ids.TESTNET, 4, unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"), "addr_test1qr4sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms947v54"), staking_key_hash=unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff")
] ),
"BASE_OWN_STAKING_KEY_HASH": CardanoAddressParametersType(
for network_id, account, staking_key_hash, expected_address in test_vectors: address_type=CardanoAddressType.BASE,
address_parameters = CardanoAddressParametersType( address_n=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 0, 0],
staking_key_hash=unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff")
),
# staking key hash not owned - derived with "all all..." mnenomnic
"BASE_FOREIGN_STAKING_KEY_HASH_ACCOUNT_4": CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 0, 0],
staking_key_hash=unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277")
),
# staking key hash not owned - derived with "all all..." mnenomnic
"BASE_FOREIGN_STAKING_KEY_HASH_ACCOUNT_0": CardanoAddressParametersType(
address_type=CardanoAddressType.BASE, address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, account | HARDENED, 0, 0],
staking_key_hash=staking_key_hash,
)
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id)
self.assertEqual(actual_address, expected_address)
def test_enterprise_address(self):
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice"
passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
keychain = Keychain(node)
test_vectors = [
# network id, expected result
(network_ids.MAINNET, "addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8"),
(network_ids.TESTNET, "addr_test1vz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspjrlsz")
]
for network_id, expected_address in test_vectors:
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
) staking_key_hash=unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277")
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id) ),
"BASE_SCRIPT_KEY_SCRIPT_HASH":CardanoAddressParametersType(
self.assertEqual(actual_address, expected_address) address_type=CardanoAddressType.BASE_SCRIPT_KEY,
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
def test_pointer_address(self): script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice" ),
passphrase = "" "BASE_KEY_SCRIPT_HASH":CardanoAddressParametersType(
node = bip32.from_mnemonic_cardano(mnemonic, passphrase) address_type=CardanoAddressType.BASE_KEY_SCRIPT,
keychain = Keychain(node) address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
test_vectors = [ ),
# network id, pointer, expected result "BASE_SCRIPT_SCRIPT_HASHES": CardanoAddressParametersType(
(network_ids.MAINNET, CardanoBlockchainPointerType(block_index=1, tx_index=2, certificate_index=3), "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspqgpse33frd"), address_type=CardanoAddressType.BASE_SCRIPT_SCRIPT,
(network_ids.TESTNET, CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42), "addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5ph3wczvf2pfz4ly") script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
] script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
),
for network_id, pointer, expected_address in test_vectors: "POINTER1": CardanoAddressParametersType(
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER, address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=pointer, certificate_pointer=CardanoBlockchainPointerType(block_index=1, tx_index=2, certificate_index=3),
) ),
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id) "POINTER2": CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
self.assertEqual(actual_address, expected_address) address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42),
def test_reward_address(self): ),
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice" "POINTER_SCRIPT_HASH": CardanoAddressParametersType(
passphrase = "" address_type=CardanoAddressType.POINTER_SCRIPT,
node = bip32.from_mnemonic_cardano(mnemonic, passphrase) certificate_pointer=CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42),
keychain = Keychain(node) script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
),
"ENTERPRISE": CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
"ENTERPRISE_SCRIPT_HASH": CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE_SCRIPT,
script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
),
"REWARD": CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
"REWARD_SCRIPT_HASH": CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD_SCRIPT,
script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
),
}
test_vectors = [ test_vectors = [
# network id, expected result # base address
(network_ids.MAINNET, "stake1uyevw2xnsc0pvn9t9r9c7qryfqfeerchgrlm3ea2nefr9hqxdekzz"), (network_ids.MAINNET, CardanoAddressType.BASE, address_parameters["BASE"], "addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wnrqua2vw243tmjfjt0h5wsru6appuz8c0pfd75ur7myyeqsx9990"),
(network_ids.TESTNET, "stake_test1uqevw2xnsc0pvn9t9r9c7qryfqfeerchgrlm3ea2nefr9hqp8n5xl") (network_ids.TESTNET, CardanoAddressType.BASE, address_parameters["BASE"], "addr_test1qrv42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wnrqua2vw243tmjfjt0h5wsru6appuz8c0pfd75ur7myyeqnsc9fs"),
# base address with staking key hash
(network_ids.MAINNET, CardanoAddressType.BASE, address_parameters["BASE_OWN_STAKING_KEY_HASH"], "addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsydc62k"),
(network_ids.TESTNET, CardanoAddressType.BASE, address_parameters["BASE_OWN_STAKING_KEY_HASH"], "addr_test1qrv42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hls8m96xf"),
(network_ids.MAINNET, CardanoAddressType.BASE, address_parameters["BASE_FOREIGN_STAKING_KEY_HASH_ACCOUNT_4"], "addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms06skxl"),
(network_ids.MAINNET, CardanoAddressType.BASE, address_parameters["BASE_FOREIGN_STAKING_KEY_HASH_ACCOUNT_0"], "addr1qxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsl3s9zt"),
(network_ids.TESTNET, CardanoAddressType.BASE, address_parameters["BASE_FOREIGN_STAKING_KEY_HASH_ACCOUNT_4"], "addr_test1qrv42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsvvdk2q"),
# base_script_key address
(network_ids.MAINNET, CardanoAddressType.BASE_SCRIPT_KEY, address_parameters["BASE_SCRIPT_KEY_SCRIPT_HASH"], "addr1zyx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsf42dkl"),
(network_ids.TESTNET, CardanoAddressType.BASE_SCRIPT_KEY, address_parameters["BASE_SCRIPT_KEY_SCRIPT_HASH"], "addr_test1zqx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms2rhd6q"),
# base_key_script address
(network_ids.MAINNET, CardanoAddressType.BASE_KEY_SCRIPT, address_parameters["BASE_KEY_SCRIPT_HASH"], "addr1yxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5s8vnrtt"),
(network_ids.TESTNET, CardanoAddressType.BASE_KEY_SCRIPT, address_parameters["BASE_KEY_SCRIPT_HASH"], "addr_test1yzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5sy6wr85"),
# base_script_script address
(network_ids.MAINNET, CardanoAddressType.BASE_SCRIPT_SCRIPT, address_parameters["BASE_SCRIPT_SCRIPT_HASHES"], "addr1xyx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5s3gftll"),
(network_ids.TESTNET, CardanoAddressType.BASE_SCRIPT_SCRIPT, address_parameters["BASE_SCRIPT_SCRIPT_HASHES"], "addr_test1xqx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5d004u0fv0r3a4ld7f8yg8rmxnk5dsxf542ghcc42ngw5sj75tnq"),
# pointer address
(network_ids.MAINNET, CardanoAddressType.POINTER, address_parameters["POINTER1"], "addr1gxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92spqgpsl97q83"),
(network_ids.TESTNET, CardanoAddressType.POINTER, address_parameters["POINTER2"], "addr_test1gzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925ph3wczvf2ag2x9t"),
# pointer_script address
(network_ids.MAINNET, CardanoAddressType.POINTER_SCRIPT, address_parameters["POINTER_SCRIPT_HASH"], "addr12yx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5ph3wczvf2zmd4yp"),
(network_ids.TESTNET, CardanoAddressType.POINTER_SCRIPT, address_parameters["POINTER_SCRIPT_HASH"], "addr_test12qx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3l5ph3wczvf2d4sugn"),
# enterprise address
(network_ids.MAINNET, CardanoAddressType.ENTERPRISE, address_parameters["ENTERPRISE"], "addr1vxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92su77c6m"),
(network_ids.TESTNET, CardanoAddressType.ENTERPRISE, address_parameters["ENTERPRISE"], "addr_test1vzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92s8k2y47"),
# enterprise_script address
(network_ids.MAINNET, CardanoAddressType.ENTERPRISE_SCRIPT, address_parameters["ENTERPRISE_SCRIPT_HASH"], "addr1wyx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsqee7sp"),
(network_ids.TESTNET, CardanoAddressType.ENTERPRISE_SCRIPT, address_parameters["ENTERPRISE_SCRIPT_HASH"], "addr_test1wqx44jlk580mpjrjfesd7v2fsuc4ejlh3wmvp7dk702k3lsm3dzly"),
# reward address
(network_ids.MAINNET, CardanoAddressType.REWARD, address_parameters["REWARD"], "stake1uyfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yacalmqha"),
(network_ids.TESTNET, CardanoAddressType.REWARD, address_parameters["REWARD"], "stake_test1uqfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yac643znq"),
# reward_script address
(network_ids.MAINNET, CardanoAddressType.REWARD_SCRIPT, address_parameters["REWARD_SCRIPT_HASH"], "stake17xxhh6785k83c76lklynjyr3anfm2xcry624ytuv24f582gt5mad4"),
(network_ids.TESTNET, CardanoAddressType.REWARD_SCRIPT, address_parameters["REWARD_SCRIPT_HASH"], "stake_test17zxhh6785k83c76lklynjyr3anfm2xcry624ytuv24f582gv73lfg"),
] ]
for network_id, expected_address in test_vectors:
address_parameters = CardanoAddressParametersType( for network_id, address_type, address_parameters, expected_address in test_vectors:
address_type=CardanoAddressType.REWARD, validate_address_parameters(address_parameters)
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
)
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id) actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id)
self.assertEqual(actual_address, expected_address) self.assertEqual(actual_address, expected_address)
def test_testnet_byron_address(self):
mnemonic = "all all all all all all all all all all all all"
passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
keychain = Keychain(node)
addresses = [
"2657WMsDfac5F3zbgs9BwNWx3dhGAJERkAL93gPa68NJ2i8mbCHm2pLUHWSj8Mfea",
"2657WMsDfac6ezKWszxLFqJjSUgpg9NgxKc1koqi24sVpRaPhiwMaExk4useKn5HA",
"2657WMsDfac7hr1ioJGr6g7r6JRx4r1My8Rj91tcPTeVjJDpfBYKURrPG2zVLx2Sq",
]
for i, expected in enumerate(addresses):
# 44'/1815'/0'/0/i'
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.BYRON,
address_n=[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i],
)
address = derive_human_readable_address(keychain, address_parameters, protocol_magics.TESTNET, 0)
self.assertEqual(expected, address)
def test_validate_address_parameters(self): def test_validate_address_parameters(self):
test_vectors = [ test_vectors = [
# base address - both address_n_staking and staking_key_hash are None # base address - both address_n_staking and staking_key_hash are None
@ -451,22 +463,80 @@ class TestCardanoAddress(unittest.TestCase):
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_key_hash=None, staking_key_hash=None,
), ),
# base_script_key address - script_payment_hash is None
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_KEY,
script_payment_hash=None,
staking_key_hash=unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8d"),
),
# base_script_key address - address_n_staking is not a staking path
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_KEY,
script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
# base_key_script address - script_staking_hash is None
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_KEY_SCRIPT,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
script_staking_hash=None,
),
# base_script_script address - script_payment_hash is None
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_SCRIPT,
script_payment_hash=None,
script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
),
# base_script_script address - script_staking and script_staking_hash are None
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_SCRIPT,
script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
script_staking_hash=None,
),
# pointer address - pointer is None # pointer address - pointer is None
CardanoAddressParametersType( CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER, address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=None, certificate_pointer=None,
), ),
# pointer_script address - pointer is None
CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER_SCRIPT,
script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
certificate_pointer=None,
),
# pointer_script address - script_payment_script is None
CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER_SCRIPT,
script_payment_hash=None,
certificate_pointer=CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42),
),
# enterprise_script address - script_payment_hash is None
CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE_SCRIPT,
script_payment_hash=None,
),
# reward address - non staking path # reward address - non staking path
CardanoAddressParametersType( CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD, address_type=CardanoAddressType.REWARD,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0] address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]
), ),
# reward_script address - script_staking_hash is None
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD_SCRIPT,
script_staking_hash=None,
),
# Shelley addresses with Byron namespace # Shelley addresses with Byron namespace
CardanoAddressParametersType( CardanoAddressParametersType(
address_type=CardanoAddressType.BASE, address_type=CardanoAddressType.BASE,
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0] address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_key_hash=unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff"),
),
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_KEY_SCRIPT,
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
), ),
CardanoAddressParametersType( CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER, address_type=CardanoAddressType.POINTER,

@ -0,0 +1,267 @@
from common import *
from apps.cardano.helpers.credential import Credential
from apps.common.paths import HARDENED
from trezor.enums import CardanoAddressType
from trezor.messages import CardanoAddressParametersType, CardanoBlockchainPointerType
CERTIFICATE_POINTER = CardanoBlockchainPointerType(
block_index=24157,
tx_index=177,
certificate_index=42,
)
def _create_flags(
is_reward: bool = False,
is_no_staking: bool = False,
is_mismatch: bool = False,
is_unusual_path: bool = False,
is_other_warning: bool = False,
) -> tuple[bool, ...]:
return (is_reward, is_no_staking, is_mismatch, is_unusual_path, is_other_warning)
ADDRESS_PARAMETERS_CASES = [
# base
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
_create_flags(),
_create_flags(),
),
# base mismatch
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 1 | HARDENED, 2, 0],
),
_create_flags(),
_create_flags(is_mismatch=True),
),
# base payment unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
_create_flags(is_unusual_path=True),
_create_flags(is_mismatch=True),
),
# base staking unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 2, 0],
),
_create_flags(is_unusual_path=True),
_create_flags(is_unusual_path=True),
),
# base both unusual and mismatch
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 102 | HARDENED, 2, 0],
),
_create_flags(is_unusual_path=True),
_create_flags(is_mismatch=True, is_unusual_path=True),
),
# base staking key hash
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_key_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
),
_create_flags(),
_create_flags(is_other_warning=True),
),
# base key script
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_KEY_SCRIPT,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
),
_create_flags(),
_create_flags(is_other_warning=True),
),
# base key script unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_KEY_SCRIPT,
address_n=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
staking_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
),
_create_flags(is_unusual_path=True),
_create_flags(is_other_warning=True),
),
# base script key
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_KEY,
payment_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
_create_flags(is_other_warning=True),
_create_flags(),
),
# base script key unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_KEY,
payment_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 2, 0],
),
_create_flags(is_other_warning=True),
_create_flags(is_unusual_path=True),
),
# base script script
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_SCRIPT,
payment_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
staking_script_hash="2bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
),
_create_flags(is_other_warning=True),
_create_flags(is_other_warning=True),
),
# pointer
(
CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=CERTIFICATE_POINTER,
),
_create_flags(),
_create_flags(is_other_warning=True),
),
# pointer unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
certificate_pointer=CERTIFICATE_POINTER,
),
_create_flags(is_unusual_path=True),
_create_flags(is_other_warning=True),
),
# pointer script
(
CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER_SCRIPT,
payment_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
certificate_pointer=CERTIFICATE_POINTER,
),
_create_flags(is_other_warning=True),
_create_flags(is_other_warning=True),
),
# enterprise
(
CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
_create_flags(),
_create_flags(is_no_staking=True),
),
# enterprise unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
),
_create_flags(is_unusual_path=True),
_create_flags(is_no_staking=True),
),
# enterprise script
(
CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE_SCRIPT,
payment_script_hash="1bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
),
_create_flags(is_other_warning=True),
_create_flags(is_no_staking=True),
),
# reward
(
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
_create_flags(is_reward=True),
_create_flags(),
),
# reward unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 2, 0],
),
_create_flags(is_reward=True),
_create_flags(is_unusual_path=True),
),
# reward script
(
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD_SCRIPT,
staking_script_hash="2bc428e4720732ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
),
_create_flags(is_reward=True),
_create_flags(is_other_warning=True),
),
# byron
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BYRON,
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
_create_flags(),
_create_flags(is_no_staking=True),
),
# byron unusual
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BYRON,
address_n=[44 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
),
_create_flags(is_unusual_path=True),
_create_flags(is_no_staking=True),
),
]
def _get_flags(credential: Credential) -> tuple[bool, ...]:
return (
credential.is_reward,
credential.is_no_staking,
credential.is_mismatch,
credential.is_unusual_path,
credential.is_other_warning,
)
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestCardanoCredential(unittest.TestCase):
def test_credential_flags(self):
for (
address_parameters,
expected_payment_flags,
expected_stake_flags,
) in ADDRESS_PARAMETERS_CASES:
payment_credential = Credential.payment_credential(address_parameters)
stake_credential = Credential.stake_credential(address_parameters)
self.assertEqual(_get_flags(payment_credential), expected_payment_flags)
self.assertEqual(_get_flags(stake_credential), expected_stake_flags)
if __name__ == "__main__":
unittest.main()

@ -1,42 +0,0 @@
from common import *
from apps.common.paths import HARDENED
if not utils.BITCOIN_ONLY:
from apps.cardano.sign_tx import _should_hide_output
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestCardanoSignTransaction(unittest.TestCase):
def test_should_show_outputs(self):
outputs_to_hide = [
# byron path
[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
# shelley path
[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
# path account is 2
[1852 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0],
# path index is 2
[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 2],
]
outputs_to_show = [
# path is not complete
[1852 | HARDENED, 1815 | HARDENED],
# path is not complete
[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED],
# staking output path
[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0,],
# max safe account number exceeded
[1852 | HARDENED, 1815 | HARDENED, 101 | HARDENED, 0, 0],
# output address too large
[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 1000001],
]
for output_path in outputs_to_hide:
self.assertTrue(_should_hide_output(output_path))
for output_path in outputs_to_show:
self.assertFalse(_should_hide_output(output_path))
if __name__ == "__main__":
unittest.main()

@ -1,100 +0,0 @@
from ubinascii import unhexlify
from common import *
from apps.common.paths import HARDENED
from trezor.crypto import bip32
from trezor.enums import CardanoAddressType
from trezor.messages import CardanoAddressParametersType
from trezor.messages import CardanoBlockchainPointerType
if not utils.BITCOIN_ONLY:
from apps.cardano.helpers import staking_use_cases
from apps.cardano.seed import Keychain
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestCardanoStakingUseCases(unittest.TestCase):
def test_get(self):
mnemonic = (
"test walk nut penalty hip pave soap entry language right filter choice"
)
passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
keychain = Keychain(node)
expected_staking_use_cases = [
# address parameters, expected staking use case
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
staking_use_cases.MATCH,
),
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 2, 0],
),
staking_use_cases.MISMATCH,
),
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_key_hash=unhexlify("32c728d3861e164cab28cb8f006448139c8f1740ffb8e7aa9e5232dc"),
),
staking_use_cases.MATCH,
),
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_key_hash=unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"),
),
staking_use_cases.MISMATCH,
),
(
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=3
),
),
staking_use_cases.POINTER_ADDRESS,
),
(
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
),
staking_use_cases.MATCH,
),
(
CardanoAddressParametersType(
address_type=CardanoAddressType.ENTERPRISE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
staking_use_cases.NO_STAKING,
),
(
CardanoAddressParametersType(
address_type=CardanoAddressType.BYRON,
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
staking_use_cases.NO_STAKING,
),
]
for address_parameters, expected_staking_use_case in expected_staking_use_cases:
actual_staking_use_case = staking_use_cases.get(keychain, address_parameters)
self.assertEqual(actual_staking_use_case, expected_staking_use_case)
if __name__ == "__main__":
unittest.main()

@ -55,14 +55,6 @@ INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields"
INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY = "The output's token_bundle entry is invalid" INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY = "The output's token_bundle entry is invalid"
ADDRESS_TYPES = (
messages.CardanoAddressType.BYRON,
messages.CardanoAddressType.BASE,
messages.CardanoAddressType.POINTER,
messages.CardanoAddressType.ENTERPRISE,
messages.CardanoAddressType.REWARD,
)
InputWithPath = Tuple[messages.CardanoTxInput, List[int]] InputWithPath = Tuple[messages.CardanoTxInput, List[int]]
AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]] AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]]
OutputWithAssetGroups = Tuple[messages.CardanoTxOutput, List[AssetGroupWithTokens]] OutputWithAssetGroups = Tuple[messages.CardanoTxOutput, List[AssetGroupWithTokens]]
@ -102,13 +94,15 @@ def create_address_parameters(
block_index: int = None, block_index: int = None,
tx_index: int = None, tx_index: int = None,
certificate_index: int = None, certificate_index: int = None,
script_payment_hash: bytes = None,
script_staking_hash: bytes = None,
) -> messages.CardanoAddressParametersType: ) -> messages.CardanoAddressParametersType:
certificate_pointer = None certificate_pointer = None
if address_type not in ADDRESS_TYPES: if address_type in (
raise ValueError("Unknown address type") messages.CardanoAddressType.POINTER,
messages.CardanoAddressType.POINTER_SCRIPT,
if address_type == messages.CardanoAddressType.POINTER: ):
certificate_pointer = _create_certificate_pointer( certificate_pointer = _create_certificate_pointer(
block_index, tx_index, certificate_index block_index, tx_index, certificate_index
) )
@ -119,6 +113,8 @@ def create_address_parameters(
address_n_staking=address_n_staking, address_n_staking=address_n_staking,
staking_key_hash=staking_key_hash, staking_key_hash=staking_key_hash,
certificate_pointer=certificate_pointer, certificate_pointer=certificate_pointer,
script_payment_hash=script_payment_hash,
script_staking_hash=script_staking_hash,
) )
@ -220,21 +216,31 @@ def _parse_tokens(tokens) -> List[messages.CardanoToken]:
def _parse_address_parameters( def _parse_address_parameters(
address_parameters, address_parameters,
) -> messages.CardanoAddressParametersType: ) -> messages.CardanoAddressParametersType:
if "path" not in address_parameters: if "addressType" not in address_parameters:
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE) raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
staking_key_hash_bytes = None path = tools.parse_path(address_parameters.get("path"))
if "stakingKeyHash" in address_parameters: staking_path = tools.parse_path(address_parameters.get("stakingPath"))
staking_key_hash_bytes = bytes.fromhex(address_parameters.get("stakingKeyHash")) staking_key_hash_bytes = parse_optional_bytes(
address_parameters.get("stakingKeyHash")
)
script_payment_hash = parse_optional_bytes(
address_parameters.get("scriptPaymentHash")
)
script_staking_hash = parse_optional_bytes(
address_parameters.get("scriptStakingHash")
)
return create_address_parameters( return create_address_parameters(
int(address_parameters["addressType"]), int(address_parameters["addressType"]),
tools.parse_path(address_parameters["path"]), path,
tools.parse_path(address_parameters.get("stakingPath")), staking_path,
staking_key_hash_bytes, staking_key_hash_bytes,
address_parameters.get("blockIndex"), address_parameters.get("blockIndex"),
address_parameters.get("txIndex"), address_parameters.get("txIndex"),
address_parameters.get("certificateIndex"), address_parameters.get("certificateIndex"),
script_payment_hash,
script_staking_hash,
) )
@ -248,7 +254,7 @@ def parse_native_script(native_script) -> messages.CardanoNativeScript:
for sub_script in native_script.get("scripts", ()) for sub_script in native_script.get("scripts", ())
] ]
key_hash = _parse_optional_bytes(native_script.get("key_hash")) key_hash = parse_optional_bytes(native_script.get("key_hash"))
key_path = tools.parse_path(native_script.get("key_path")) key_path = tools.parse_path(native_script.get("key_path"))
required_signatures_count = parse_optional_int( required_signatures_count = parse_optional_int(
native_script.get("required_signatures_count") native_script.get("required_signatures_count")

@ -23,14 +23,6 @@ from . import ChoiceType, with_client
PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0" PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
ADDRESS_TYPES = {
"byron": messages.CardanoAddressType.BYRON,
"base": messages.CardanoAddressType.BASE,
"pointer": messages.CardanoAddressType.POINTER,
"enterprise": messages.CardanoAddressType.ENTERPRISE,
"reward": messages.CardanoAddressType.REWARD,
}
@click.group(name="cardano") @click.group(name="cardano")
def cli(): def cli():
@ -115,14 +107,21 @@ def sign_tx(client, file, signing_mode, protocol_magic, network_id, testnet):
@cli.command() @cli.command()
@click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-n", "--address", type=str, default=None, help=PATH_HELP)
@click.option("-d", "--show-display", is_flag=True) @click.option("-d", "--show-display", is_flag=True)
@click.option("-t", "--address-type", type=ChoiceType(ADDRESS_TYPES), default="base") @click.option(
"-t",
"--address-type",
type=ChoiceType({m.name: m for m in messages.CardanoAddressType}),
default="BASE",
)
@click.option("-s", "--staking-address", type=str, default=None) @click.option("-s", "--staking-address", type=str, default=None)
@click.option("-h", "--staking-key-hash", type=str, default=None) @click.option("-h", "--staking-key-hash", type=str, default=None)
@click.option("-b", "--block_index", type=int, default=None) @click.option("-b", "--block_index", type=int, default=None)
@click.option("-x", "--tx_index", type=int, default=None) @click.option("-x", "--tx_index", type=int, default=None)
@click.option("-c", "--certificate_index", type=int, default=None) @click.option("-c", "--certificate_index", type=int, default=None)
@click.option("--script-payment-hash", type=str, default=None)
@click.option("--script-staking-hash", type=str, default=None)
@click.option( @click.option(
"-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"] "-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"]
) )
@ -138,6 +137,8 @@ def get_address(
block_index, block_index,
tx_index, tx_index,
certificate_index, certificate_index,
script_payment_hash,
script_staking_hash,
protocol_magic, protocol_magic,
network_id, network_id,
show_display, show_display,
@ -161,9 +162,9 @@ def get_address(
protocol_magic = cardano.PROTOCOL_MAGICS["testnet"] protocol_magic = cardano.PROTOCOL_MAGICS["testnet"]
network_id = cardano.NETWORK_IDS["testnet"] network_id = cardano.NETWORK_IDS["testnet"]
staking_key_hash_bytes = None staking_key_hash_bytes = cardano.parse_optional_bytes(staking_key_hash)
if staking_key_hash: script_payment_hash_bytes = cardano.parse_optional_bytes(script_payment_hash)
staking_key_hash_bytes = bytes.fromhex(staking_key_hash) script_staking_hash_bytes = cardano.parse_optional_bytes(script_staking_hash)
address_parameters = cardano.create_address_parameters( address_parameters = cardano.create_address_parameters(
address_type, address_type,
@ -173,6 +174,8 @@ def get_address(
block_index, block_index,
tx_index, tx_index,
certificate_index, certificate_index,
script_payment_hash_bytes,
script_staking_hash_bytes,
) )
return cardano.get_address( return cardano.get_address(

@ -1962,6 +1962,8 @@ class CardanoAddressParametersType(protobuf.MessageType):
3: protobuf.Field("address_n_staking", "uint32", repeated=True, required=False), 3: protobuf.Field("address_n_staking", "uint32", repeated=True, required=False),
4: protobuf.Field("staking_key_hash", "bytes", repeated=False, required=False), 4: protobuf.Field("staking_key_hash", "bytes", repeated=False, required=False),
5: protobuf.Field("certificate_pointer", "CardanoBlockchainPointerType", repeated=False, required=False), 5: protobuf.Field("certificate_pointer", "CardanoBlockchainPointerType", repeated=False, required=False),
6: protobuf.Field("script_payment_hash", "bytes", repeated=False, required=False),
7: protobuf.Field("script_staking_hash", "bytes", repeated=False, required=False),
} }
def __init__( def __init__(
@ -1972,12 +1974,16 @@ class CardanoAddressParametersType(protobuf.MessageType):
address_n_staking: Optional[List["int"]] = None, address_n_staking: Optional[List["int"]] = None,
staking_key_hash: Optional["bytes"] = None, staking_key_hash: Optional["bytes"] = None,
certificate_pointer: Optional["CardanoBlockchainPointerType"] = None, certificate_pointer: Optional["CardanoBlockchainPointerType"] = None,
script_payment_hash: Optional["bytes"] = None,
script_staking_hash: Optional["bytes"] = None,
) -> None: ) -> None:
self.address_n = address_n if address_n is not None else [] self.address_n = address_n if address_n is not None else []
self.address_n_staking = address_n_staking if address_n_staking is not None else [] self.address_n_staking = address_n_staking if address_n_staking is not None else []
self.address_type = address_type self.address_type = address_type
self.staking_key_hash = staking_key_hash self.staking_key_hash = staking_key_hash
self.certificate_pointer = certificate_pointer self.certificate_pointer = certificate_pointer
self.script_payment_hash = script_payment_hash
self.script_staking_hash = script_staking_hash
class CardanoGetAddress(protobuf.MessageType): class CardanoGetAddress(protobuf.MessageType):

@ -16,7 +16,12 @@
import pytest import pytest
from trezorlib.cardano import create_address_parameters, get_address, get_public_key from trezorlib.cardano import (
create_address_parameters,
get_address,
get_public_key,
parse_optional_bytes,
)
from trezorlib.messages import CardanoAddressType from trezorlib.messages import CardanoAddressType
from trezorlib.tools import parse_path from trezorlib.tools import parse_path
@ -34,6 +39,7 @@ pytestmark = [
"cardano/get_address_byron.slip39.json", "cardano/get_address_byron.slip39.json",
"cardano/get_base_address.json", "cardano/get_base_address.json",
"cardano/get_base_address_with_staking_key_hash.json", "cardano/get_base_address_with_staking_key_hash.json",
"cardano/get_base_address_with_script_hashes.json",
"cardano/get_enterprise_address.json", "cardano/get_enterprise_address.json",
"cardano/get_pointer_address.json", "cardano/get_pointer_address.json",
"cardano/get_reward_address.json", "cardano/get_reward_address.json",
@ -45,16 +51,22 @@ def test_cardano_get_address(client, parameters, result):
address_type=getattr( address_type=getattr(
CardanoAddressType, parameters["address_type"].upper() CardanoAddressType, parameters["address_type"].upper()
), ),
address_n=parse_path(parameters["path"]), address_n=parse_path(parameters.get("path"))
if "path" in parameters
else None,
address_n_staking=parse_path(parameters.get("staking_path")) address_n_staking=parse_path(parameters.get("staking_path"))
if "staking_path" in parameters if "staking_path" in parameters
else None, else None,
staking_key_hash=bytes.fromhex(parameters.get("staking_key_hash")) staking_key_hash=parse_optional_bytes(parameters.get("staking_key_hash")),
if "staking_key_hash" in parameters
else None,
block_index=parameters.get("block_index"), block_index=parameters.get("block_index"),
tx_index=parameters.get("tx_index"), tx_index=parameters.get("tx_index"),
certificate_index=parameters.get("certificate_index"), certificate_index=parameters.get("certificate_index"),
script_payment_hash=parse_optional_bytes(
parameters.get("script_payment_hash")
),
script_staking_hash=parse_optional_bytes(
parameters.get("script_staking_hash")
),
), ),
protocol_magic=parameters["protocol_magic"], protocol_magic=parameters["protocol_magic"],
network_id=parameters["network_id"], network_id=parameters["network_id"],

Loading…
Cancel
Save