1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-17 21:22:10 +00:00

feat(cardano): add support for script addresses derivation

This commit is contained in:
gabrielkerekes 2021-07-23 12:14:29 +02:00 committed by matejcik
parent dd9652cd07
commit 21281d7cf4
22 changed files with 1284 additions and 745 deletions

View File

@ -128,6 +128,8 @@ message CardanoAddressParametersType {
// can be sent directly e.g. if it doesn't belong to
// the same account as address_n
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;
}
/**

View File

@ -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"
}
}
]
}

View File

@ -25,6 +25,28 @@
"result": {
"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"
}
}
]
}

View File

@ -31,6 +31,34 @@
"result": {
"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"
}
}
]
}

View File

@ -6,8 +6,8 @@
"tests": [
{
"parameters": {
"path": "m/1852'/1815'/0'/2/0",
"address_type": "reward",
"staking_path": "m/1852'/1815'/0'/2/0",
"network_id": 1,
"protocol_magic": 764824073
},
@ -17,14 +17,36 @@
},
{
"parameters": {
"path": "m/1852'/1815'/0'/2/0",
"address_type": "reward",
"staking_path": "m/1852'/1815'/0'/2/0",
"network_id": 0,
"protocol_magic": 42
},
"result": {
"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"
}
}
]
}

View File

@ -462,6 +462,8 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.helpers.account_path_check
apps.cardano.helpers.bech32
import apps.cardano.helpers.bech32
apps.cardano.helpers.credential
import apps.cardano.helpers.credential
apps.cardano.helpers.hash_builder_collection
import apps.cardano.helpers.hash_builder_collection
apps.cardano.helpers.network_ids
@ -470,8 +472,6 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.helpers.paths
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
import apps.cardano.helpers.utils
apps.cardano.layout

View File

@ -1,4 +1,4 @@
from trezor.crypto import base58, hashlib
from trezor.crypto import base58
from trezor.enums import CardanoAddressType
from .byron_address import derive_byron_address, validate_byron_address
@ -7,17 +7,20 @@ from .helpers import (
INVALID_ADDRESS,
INVALID_ADDRESS_PARAMETERS,
NETWORK_MISMATCH,
SCRIPT_HASH_SIZE,
bech32,
network_ids,
)
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
if False:
from typing import Any
from trezor.messages import (
CardanoBlockchainPointerType,
CardanoAddressParametersType,
CardanoBlockchainPointerType,
)
from . import seed
@ -33,6 +36,7 @@ ADDRESS_TYPES_SHELLEY = (
CardanoAddressType.REWARD,
CardanoAddressType.REWARD_SCRIPT,
)
MIN_ADDRESS_BYTES_LENGTH = 29
MAX_ADDRESS_BYTES_LENGTH = 65
@ -43,20 +47,56 @@ def validate_address_parameters(parameters: CardanoAddressParametersType) -> Non
if parameters.address_type == CardanoAddressType.BYRON:
if not is_byron_path(parameters.address_n):
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
if parameters.address_type == CardanoAddressType.BASE:
_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.REWARD:
if not SCHEMA_STAKING_ANY_ACCOUNT.match(parameters.address_n):
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):
raise INVALID_ADDRESS_PARAMETERS
elif parameters.address_type == CardanoAddressType.ENTERPRISE_SCRIPT:
_validate_script_hash(parameters.script_payment_hash)
elif parameters.address_type == CardanoAddressType.REWARD:
if not is_shelley_path(parameters.address_n_staking):
raise INVALID_ADDRESS_PARAMETERS
if not SCHEMA_STAKING_ANY_ACCOUNT.match(parameters.address_n_staking):
raise INVALID_ADDRESS_PARAMETERS
elif parameters.address_type == CardanoAddressType.REWARD_SCRIPT:
_validate_script_hash(parameters.script_staking_hash)
else:
raise INVALID_ADDRESS_PARAMETERS
@ -64,23 +104,86 @@ def validate_address_parameters(parameters: CardanoAddressParametersType) -> Non
def _validate_address_parameters_structure(
parameters: CardanoAddressParametersType,
) -> None:
address_n = parameters.address_n
address_n_staking = parameters.address_n_staking
staking_key_hash = parameters.staking_key_hash
certificate_pointer = parameters.certificate_pointer
script_payment_hash = parameters.script_payment_hash
script_staking_hash = parameters.script_staking_hash
fields_to_be_empty: tuple = ()
if parameters.address_type in (
CardanoAddressType.BYRON,
CardanoAddressType.REWARD,
CardanoAddressType.ENTERPRISE,
fields_to_be_empty: dict[CardanoAddressType, tuple[Any, ...]] = {
CardanoAddressType.BASE: (
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
@ -101,6 +204,11 @@ def _validate_base_address_staking_info(
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(
address: str, protocol_magic: int, network_id: int
) -> int:
@ -189,7 +297,7 @@ def _get_bech32_hrp_for_address(
# Byron address uses base58 encoding
raise ValueError
if address_type == CardanoAddressType.REWARD:
if address_type in (CardanoAddressType.REWARD, CardanoAddressType.REWARD_SCRIPT):
if network_ids.is_mainnet(network_id):
return bech32.HRP_REWARD_ADDRESS
else:
@ -210,11 +318,6 @@ def _get_address_network_id(address: bytes) -> int:
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(
keychain: seed.Keychain,
parameters: CardanoAddressParametersType,
@ -258,36 +361,14 @@ def derive_address_bytes(
def _derive_shelley_address(
keychain: seed.Keychain,
parameters: CardanoAddressParametersType,
network_id: int,
keychain: seed.Keychain, parameters: CardanoAddressParametersType, network_id: int
) -> bytes:
if parameters.address_type == CardanoAddressType.BASE:
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
header = _create_address_header(parameters.address_type, network_id)
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
payment_part = _get_address_payment_part(keychain, parameters)
staking_part = _get_address_staking_part(keychain, parameters)
return address
return header + payment_part + staking_part
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")
def _derive_base_address(
keychain: seed.Keychain,
path: list[int],
staking_path: list[int],
staking_key_hash: bytes | None,
network_id: int,
def _get_address_payment_part(
keychain: seed.Keychain, parameters: CardanoAddressParametersType
) -> bytes:
header = _create_address_header(CardanoAddressType.BASE, network_id)
spending_key_hash = get_public_key_hash(keychain, path)
if staking_key_hash is None:
staking_key_hash = get_public_key_hash(keychain, staking_path)
return header + spending_key_hash + staking_key_hash
if parameters.address_n:
return get_public_key_hash(keychain, parameters.address_n)
elif parameters.script_payment_hash:
return parameters.script_payment_hash
else:
return bytes()
def _derive_pointer_address(
keychain: seed.Keychain,
path: list[int],
pointer: CardanoBlockchainPointerType,
network_id: int,
def _get_address_staking_part(
keychain: seed.Keychain, parameters: CardanoAddressParametersType
) -> bytes:
header = _create_address_header(CardanoAddressType.POINTER, network_id)
spending_key_hash = get_public_key_hash(keychain, path)
encoded_pointer = _encode_certificate_pointer(pointer)
return header + spending_key_hash + encoded_pointer
if parameters.staking_key_hash:
return parameters.staking_key_hash
elif parameters.address_n_staking:
return get_public_key_hash(keychain, parameters.address_n_staking)
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:
@ -330,36 +408,3 @@ def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes:
certificate_index_encoded = variable_length_encode(pointer.certificate_index)
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

View File

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

View File

@ -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]

View File

@ -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)
# fmt: on
ACCOUNT_PATH_LENGTH = const(3)
ACCOUNT_PATH_INDEX = const(2)
ACCOUNT_PATH_LENGTH = const(3)
CHAIN_STAKING_KEY = const(2)
CHANGE_OUTPUT_PATH_NAME = "Change output path"
CHANGE_OUTPUT_STAKING_PATH_NAME = "Change output staking path"

View File

@ -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]

View File

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

View File

@ -64,9 +64,9 @@ from .helpers import (
LOVELACE_MAX_SUPPLY,
network_ids,
protocol_magics,
staking_use_cases,
)
from .helpers.account_path_check import AccountPathChecker
from .helpers.credential import Credential, should_show_address_credentials
from .helpers.hash_builder_collection import HashBuilderDict, HashBuilderList
from .helpers.paths import (
CERTIFICATE_PATH_NAME,
@ -89,13 +89,10 @@ from .layout import (
confirm_stake_pool_registration_final,
confirm_transaction,
confirm_withdrawal,
show_credentials,
show_warning_path,
show_warning_tx_different_staking_account,
show_warning_tx_network_unverifiable,
show_warning_tx_no_staking_info,
show_warning_tx_output_contains_tokens,
show_warning_tx_pointer_address,
show_warning_tx_staking_key_hash,
)
from .seed import is_byron_path
@ -302,8 +299,16 @@ async def _process_outputs(
total_amount = 0
for _ in range(outputs_count):
output: CardanoTxOutput = await ctx.call(CardanoTxItemAck(), CardanoTxOutput)
_validate_output(output, protocol_magic, network_id, account_path_checker)
if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
_validate_output(
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(
ctx,
keychain,
@ -335,7 +340,7 @@ async def _process_outputs(
ctx,
asset_groups_dict,
output.asset_groups_count,
_should_show_tokens(output, signing_mode),
should_show_output,
)
total_amount += output.amount
@ -634,6 +639,7 @@ def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTxInit) -> No
def _validate_output(
output: CardanoTxOutput,
signing_mode: CardanoTxSigningMode,
protocol_magic: int,
network_id: int,
account_path_checker: AccountPathChecker,
@ -641,8 +647,9 @@ def _validate_output(
if output.address_parameters and output.address is not None:
raise INVALID_OUTPUT
if output.address_parameters:
validate_address_parameters(output.address_parameters)
if address_parameters := output.address_parameters:
validate_address_parameters(address_parameters)
_fail_if_strict_and_unusual(address_parameters)
elif output.address is not None:
validate_output_address(output.address, protocol_magic, network_id)
else:
@ -651,6 +658,23 @@ def _validate_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(
ctx: wire.Context,
keychain: seed.Keychain,
@ -659,33 +683,30 @@ async def _show_output(
protocol_magic: int,
network_id: int,
) -> None:
if output.address_parameters:
await _fail_or_warn_if_invalid_path(
ctx,
SCHEMA_PAYMENT,
output.address_parameters.address_n,
CHANGE_OUTPUT_PATH_NAME,
)
await _show_change_output_staking_warnings(
ctx, keychain, output.address_parameters, output.amount
)
if _should_hide_output(output.address_parameters.address_n):
return
address = derive_human_readable_address(
keychain, output.address_parameters, protocol_magic, network_id
)
else:
assert output.address is not None # _validate_output
address = output.address
if output.asset_groups_count > 0:
await show_warning_tx_output_contains_tokens(ctx)
if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
await confirm_sending(ctx, output.amount, address)
is_change_output: bool
if address_parameters := output.address_parameters:
is_change_output = True
await show_credentials(
ctx,
Credential.payment_credential(address_parameters),
Credential.stake_credential(address_parameters),
is_change_output=True,
)
address = derive_human_readable_address(
keychain, address_parameters, protocol_magic, network_id
)
else:
is_change_output = False
assert output.address is not None # _validate_output
address = output.address
await confirm_sending(ctx, output.amount, address, is_change_output)
def _validate_asset_group(
@ -777,7 +798,11 @@ async def _show_stake_pool_registration_certificate(
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:
if owner.staking_key_path:
await _fail_or_warn_if_invalid_path(
@ -787,7 +812,7 @@ async def _show_pool_owner(
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(
@ -824,78 +849,11 @@ async def _show_witness(
ctx: wire.Context,
witness_path: list[int],
) -> None:
if not SCHEMA_PAYMENT.match(witness_path) and not SCHEMA_STAKING.match(
witness_path
):
await _fail_or_warn_path(
ctx,
witness_path,
WITNESS_PATH_NAME,
)
is_payment = SCHEMA_PAYMENT.match(witness_path)
is_staking = SCHEMA_STAKING.match(witness_path)
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)
def _should_show_tokens(
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
if not is_payment and not is_staking:
await _fail_or_warn_path(ctx, witness_path, WITNESS_PATH_NAME)
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())
else:
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())

View File

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

View File

@ -285,121 +285,6 @@ class TestCardanoAddress(unittest.TestCase):
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
self.assertEqual(hexlify(n.chain_code()), chain)
def test_base_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, account, expected result
# data generated with code under test
(network_ids.MAINNET, 4, "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r"),
(network_ids.TESTNET, 4, "addr_test1qr4sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlswzkqcu"),
]
for network_id, account, expected_address in test_vectors:
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, account | HARDENED, 0, 0],
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)
self.assertEqual(actual_address, expected_address)
def test_base_address_with_staking_key_hash(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, account, staking key hash, expected result
# own staking key hash
# data generated with code under test
(network_ids.MAINNET, 4, unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff"), "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r"),
(network_ids.TESTNET, 4, unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff"), "addr_test1qr4sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlswzkqcu"),
# staking key hash not owned - derived with "all all..." mnenomnic, data generated with code under test
(network_ids.MAINNET, 4, unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"), "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsxrrvc2"),
(network_ids.MAINNET, 0, unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"), "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzersj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms6xjnst"),
(network_ids.TESTNET, 4, unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"), "addr_test1qr4sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms947v54"),
]
for network_id, account, staking_key_hash, expected_address in test_vectors:
address_parameters = CardanoAddressParametersType(
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],
)
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id)
self.assertEqual(actual_address, expected_address)
def test_pointer_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, pointer, expected result
(network_ids.MAINNET, CardanoBlockchainPointerType(block_index=1, tx_index=2, certificate_index=3), "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspqgpse33frd"),
(network_ids.TESTNET, CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42), "addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5ph3wczvf2pfz4ly")
]
for network_id, pointer, expected_address in test_vectors:
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=pointer,
)
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id)
self.assertEqual(actual_address, expected_address)
def test_reward_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, "stake1uyevw2xnsc0pvn9t9r9c7qryfqfeerchgrlm3ea2nefr9hqxdekzz"),
(network_ids.TESTNET, "stake_test1uqevw2xnsc0pvn9t9r9c7qryfqfeerchgrlm3ea2nefr9hqp8n5xl")
]
for network_id, expected_address in test_vectors:
address_parameters = CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
)
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id)
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 = ""
@ -421,6 +306,133 @@ class TestCardanoAddress(unittest.TestCase):
address = derive_human_readable_address(keychain, address_parameters, protocol_magics.TESTNET, 0)
self.assertEqual(expected, address)
def test_derive_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)
address_parameters = {
"BASE": CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 0, 0],
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 2, 0]
),
"BASE_OWN_STAKING_KEY_HASH": CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
address_n=[1852 | HARDENED, 1815 | HARDENED, 4 | HARDENED, 0, 0],
staking_key_hash=unhexlify("1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff")
),
"BASE_OWN_STAKING_KEY_HASH": CardanoAddressParametersType(
address_type=CardanoAddressType.BASE,
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_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
staking_key_hash=unhexlify("122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277")
),
"BASE_SCRIPT_KEY_SCRIPT_HASH":CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_KEY,
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0],
script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
),
"BASE_KEY_SCRIPT_HASH":CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_KEY_SCRIPT,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
),
"BASE_SCRIPT_SCRIPT_HASHES": CardanoAddressParametersType(
address_type=CardanoAddressType.BASE_SCRIPT_SCRIPT,
script_payment_hash=unhexlify("0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe"),
script_staking_hash=unhexlify("8d7bebc7a58f1c7b5fb7c9391071ecd3b51b032695522f8c555343a9"),
),
"POINTER1": 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),
),
"POINTER2": CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
certificate_pointer=CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42),
),
"POINTER_SCRIPT_HASH": CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER_SCRIPT,
certificate_pointer=CardanoBlockchainPointerType(block_index=24157, tx_index=177, certificate_index=42),
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 = [
# base address
(network_ids.MAINNET, CardanoAddressType.BASE, address_parameters["BASE"], "addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wnrqua2vw243tmjfjt0h5wsru6appuz8c0pfd75ur7myyeqsx9990"),
(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, address_type, address_parameters, expected_address in test_vectors:
validate_address_parameters(address_parameters)
actual_address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_id)
self.assertEqual(actual_address, expected_address)
def test_validate_address_parameters(self):
test_vectors = [
# 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],
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
CardanoAddressParametersType(
address_type=CardanoAddressType.POINTER,
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
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
CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD,
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
CardanoAddressParametersType(
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(
address_type=CardanoAddressType.POINTER,

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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"
ADDRESS_TYPES = (
messages.CardanoAddressType.BYRON,
messages.CardanoAddressType.BASE,
messages.CardanoAddressType.POINTER,
messages.CardanoAddressType.ENTERPRISE,
messages.CardanoAddressType.REWARD,
)
InputWithPath = Tuple[messages.CardanoTxInput, List[int]]
AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]]
OutputWithAssetGroups = Tuple[messages.CardanoTxOutput, List[AssetGroupWithTokens]]
@ -102,13 +94,15 @@ def create_address_parameters(
block_index: int = None,
tx_index: int = None,
certificate_index: int = None,
script_payment_hash: bytes = None,
script_staking_hash: bytes = None,
) -> messages.CardanoAddressParametersType:
certificate_pointer = None
if address_type not in ADDRESS_TYPES:
raise ValueError("Unknown address type")
if address_type == messages.CardanoAddressType.POINTER:
if address_type in (
messages.CardanoAddressType.POINTER,
messages.CardanoAddressType.POINTER_SCRIPT,
):
certificate_pointer = _create_certificate_pointer(
block_index, tx_index, certificate_index
)
@ -119,6 +113,8 @@ def create_address_parameters(
address_n_staking=address_n_staking,
staking_key_hash=staking_key_hash,
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(
address_parameters,
) -> messages.CardanoAddressParametersType:
if "path" not in address_parameters:
if "addressType" not in address_parameters:
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
staking_key_hash_bytes = None
if "stakingKeyHash" in address_parameters:
staking_key_hash_bytes = bytes.fromhex(address_parameters.get("stakingKeyHash"))
path = tools.parse_path(address_parameters.get("path"))
staking_path = tools.parse_path(address_parameters.get("stakingPath"))
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(
int(address_parameters["addressType"]),
tools.parse_path(address_parameters["path"]),
tools.parse_path(address_parameters.get("stakingPath")),
path,
staking_path,
staking_key_hash_bytes,
address_parameters.get("blockIndex"),
address_parameters.get("txIndex"),
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", ())
]
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"))
required_signatures_count = parse_optional_int(
native_script.get("required_signatures_count")

View File

@ -23,14 +23,6 @@ from . import ChoiceType, with_client
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")
def cli():
@ -115,14 +107,21 @@ def sign_tx(client, file, signing_mode, protocol_magic, network_id, testnet):
@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("-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("-h", "--staking-key-hash", type=str, default=None)
@click.option("-b", "--block_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("--script-payment-hash", type=str, default=None)
@click.option("--script-staking-hash", type=str, default=None)
@click.option(
"-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"]
)
@ -138,6 +137,8 @@ def get_address(
block_index,
tx_index,
certificate_index,
script_payment_hash,
script_staking_hash,
protocol_magic,
network_id,
show_display,
@ -161,9 +162,9 @@ def get_address(
protocol_magic = cardano.PROTOCOL_MAGICS["testnet"]
network_id = cardano.NETWORK_IDS["testnet"]
staking_key_hash_bytes = None
if staking_key_hash:
staking_key_hash_bytes = bytes.fromhex(staking_key_hash)
staking_key_hash_bytes = cardano.parse_optional_bytes(staking_key_hash)
script_payment_hash_bytes = cardano.parse_optional_bytes(script_payment_hash)
script_staking_hash_bytes = cardano.parse_optional_bytes(script_staking_hash)
address_parameters = cardano.create_address_parameters(
address_type,
@ -173,6 +174,8 @@ def get_address(
block_index,
tx_index,
certificate_index,
script_payment_hash_bytes,
script_staking_hash_bytes,
)
return cardano.get_address(

View File

@ -1962,6 +1962,8 @@ class CardanoAddressParametersType(protobuf.MessageType):
3: protobuf.Field("address_n_staking", "uint32", repeated=True, required=False),
4: protobuf.Field("staking_key_hash", "bytes", 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__(
@ -1972,12 +1974,16 @@ class CardanoAddressParametersType(protobuf.MessageType):
address_n_staking: Optional[List["int"]] = None,
staking_key_hash: Optional["bytes"] = None,
certificate_pointer: Optional["CardanoBlockchainPointerType"] = None,
script_payment_hash: Optional["bytes"] = None,
script_staking_hash: Optional["bytes"] = None,
) -> None:
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_type = address_type
self.staking_key_hash = staking_key_hash
self.certificate_pointer = certificate_pointer
self.script_payment_hash = script_payment_hash
self.script_staking_hash = script_staking_hash
class CardanoGetAddress(protobuf.MessageType):

View File

@ -16,7 +16,12 @@
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.tools import parse_path
@ -34,6 +39,7 @@ pytestmark = [
"cardano/get_address_byron.slip39.json",
"cardano/get_base_address.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_pointer_address.json",
"cardano/get_reward_address.json",
@ -45,16 +51,22 @@ def test_cardano_get_address(client, parameters, result):
address_type=getattr(
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"))
if "staking_path" in parameters
else None,
staking_key_hash=bytes.fromhex(parameters.get("staking_key_hash"))
if "staking_key_hash" in parameters
else None,
staking_key_hash=parse_optional_bytes(parameters.get("staking_key_hash")),
block_index=parameters.get("block_index"),
tx_index=parameters.get("tx_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"],
network_id=parameters["network_id"],