mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 23:08:14 +00:00
Cardano shelley update 2/3 (#1112)
This commit is contained in:
parent
e1615e60ec
commit
d2c1624602
@ -7,6 +7,46 @@ option java_outer_classname = "TrezorMessageCardano";
|
||||
|
||||
import "messages-common.proto";
|
||||
|
||||
/**
|
||||
* Values correspond to address header values given by the spec.
|
||||
*/
|
||||
enum CardanoAddressType {
|
||||
BASE = 0;
|
||||
POINTER = 4;
|
||||
ENTERPRISE = 6;
|
||||
BYRON = 8;
|
||||
REWARD = 14;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing cardano PointerAddress pointer,
|
||||
* which points to a staking key registration certificate.
|
||||
* @embed
|
||||
*/
|
||||
message CardanoBlockchainPointerType {
|
||||
optional uint32 block_index = 1;
|
||||
optional uint32 tx_index = 2;
|
||||
optional uint32 certificate_index = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure to represent address parameters so they can be
|
||||
* reused in CardanoGetAddress and CardanoTxOutputType.
|
||||
* NetworkId isn't a part of the parameters, because in a transaction
|
||||
* this will be included separately in the transaction itself, so it
|
||||
* shouldn't be duplicated here.
|
||||
* @embed
|
||||
*/
|
||||
message CardanoAddressParametersType {
|
||||
optional CardanoAddressType address_type = 1; // one of the CardanoAddressType-s
|
||||
repeated uint32 address_n = 2; // BIP-32-style path to derive the spending key from master node
|
||||
repeated uint32 address_n_staking = 3; // BIP-32-style path to derive staking key from master node
|
||||
optional bytes staking_key_hash = 4; // staking key can be derived from address_n_staking, or
|
||||
// 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device for Cardano address
|
||||
* @start
|
||||
@ -14,9 +54,11 @@ import "messages-common.proto";
|
||||
* @next Failure
|
||||
*/
|
||||
message CardanoGetAddress {
|
||||
repeated uint32 address_n = 1; // BIP-32-style path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally prompt for confirmation on trezor display
|
||||
optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets
|
||||
// repeated uint32 address_n = 1; // moved to address_parameters
|
||||
optional bool show_display = 2; // optionally prompt for confirmation on trezor display
|
||||
optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets
|
||||
optional uint32 network_id = 4; // network id - mainnet or testnet
|
||||
optional CardanoAddressParametersType address_parameters = 5; // parameters used to derive the address
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,6 +102,7 @@ message CardanoSignTx {
|
||||
optional uint32 protocol_magic = 5; // network's protocol magic
|
||||
optional uint64 fee = 6; // transaction fee - added in shelley
|
||||
optional uint64 ttl = 7; // transaction ttl - added in shelley
|
||||
optional uint32 network_id = 8; // network id - mainnet or testnet
|
||||
/**
|
||||
* Structure representing cardano transaction input
|
||||
*/
|
||||
@ -74,9 +117,10 @@ message CardanoSignTx {
|
||||
* Structure representing cardano transaction output
|
||||
*/
|
||||
message CardanoTxOutputType {
|
||||
optional string address = 1; // target coin address in Base58 encoding
|
||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address"
|
||||
optional uint64 amount = 3; // amount to spend
|
||||
optional string address = 1; // target coin address in bech32 or base58
|
||||
// repeated uint32 address_n = 2; // moved to address_parameters
|
||||
optional uint64 amount = 3; // amount to spend
|
||||
optional CardanoAddressParametersType address_parameters = 4; // parameters used to derive the address
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
from trezor import wire
|
||||
from trezor.messages import MessageType
|
||||
|
||||
from apps.common import HARDENED
|
||||
|
||||
CURVE = "ed25519"
|
||||
SEED_NAMESPACE = [HARDENED | 44, HARDENED | 1815]
|
||||
|
||||
|
||||
def boot() -> None:
|
||||
|
@ -1,145 +1,352 @@
|
||||
from trezor import log, wire
|
||||
from trezor.crypto import base58, crc, hashlib
|
||||
from micropython import const
|
||||
|
||||
from apps.common import HARDENED, cbor
|
||||
from trezor import wire
|
||||
from trezor.crypto import base58, hashlib
|
||||
from trezor.messages import CardanoAddressParametersType, CardanoAddressType
|
||||
|
||||
from apps.common import HARDENED
|
||||
from apps.common.seed import remove_ed25519_prefix
|
||||
|
||||
from . import protocol_magics
|
||||
from .byron_address import derive_byron_address, validate_output_byron_address
|
||||
from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, bech32, network_ids, purposes
|
||||
from .helpers.utils import variable_length_encode
|
||||
from .seed import is_byron_path, is_shelley_path
|
||||
|
||||
if False:
|
||||
from typing import Tuple
|
||||
from trezor.crypto import bip32
|
||||
from typing import List
|
||||
from trezor.messages import CardanoBlockchainPointerType
|
||||
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
|
||||
from . import seed
|
||||
|
||||
PROTOCOL_MAGIC_KEY = 2
|
||||
INVALID_ADDRESS = wire.ProcessError("Invalid address")
|
||||
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
|
||||
ADDRESS_TYPES_SHELLEY = (
|
||||
CardanoAddressType.BASE,
|
||||
CardanoAddressType.POINTER,
|
||||
CardanoAddressType.ENTERPRISE,
|
||||
CardanoAddressType.REWARD,
|
||||
)
|
||||
|
||||
HEADER_LENGTH = 1
|
||||
HASH_LENGTH = 28
|
||||
MIN_POINTER_SIZE = 0
|
||||
MAX_POINTER_SIZE = 12
|
||||
|
||||
ADDRESS_BYTES_MIN_LENGTHS = {
|
||||
CardanoAddressType.BASE: HEADER_LENGTH + HASH_LENGTH + HASH_LENGTH,
|
||||
CardanoAddressType.POINTER: HEADER_LENGTH + HASH_LENGTH + MIN_POINTER_SIZE,
|
||||
CardanoAddressType.ENTERPRISE: HEADER_LENGTH + HASH_LENGTH,
|
||||
CardanoAddressType.REWARD: HEADER_LENGTH + HASH_LENGTH,
|
||||
}
|
||||
|
||||
ADDRESS_BYTES_MAX_LENGTHS = {
|
||||
CardanoAddressType.BASE: ADDRESS_BYTES_MIN_LENGTHS[CardanoAddressType.BASE],
|
||||
CardanoAddressType.POINTER: HEADER_LENGTH + HASH_LENGTH + MAX_POINTER_SIZE,
|
||||
CardanoAddressType.ENTERPRISE: ADDRESS_BYTES_MIN_LENGTHS[
|
||||
CardanoAddressType.ENTERPRISE
|
||||
],
|
||||
CardanoAddressType.REWARD: ADDRESS_BYTES_MIN_LENGTHS[CardanoAddressType.REWARD],
|
||||
}
|
||||
|
||||
|
||||
def _encode_address_raw(address_data_encoded: bytes) -> str:
|
||||
return base58.encode(
|
||||
cbor.encode(
|
||||
[cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def derive_address_and_node(
|
||||
keychain: seed.Keychain, path: list, protocol_magic: int
|
||||
) -> Tuple[str, bip32.HDNode]:
|
||||
node = keychain.derive(path)
|
||||
|
||||
address_attributes = get_address_attributes(protocol_magic)
|
||||
|
||||
address_root = _get_address_root(node, address_attributes)
|
||||
address_type = 0
|
||||
address_data = [address_root, address_attributes, address_type]
|
||||
address_data_encoded = cbor.encode(address_data)
|
||||
|
||||
return (_encode_address_raw(address_data_encoded), node)
|
||||
|
||||
|
||||
def get_address_attributes(protocol_magic: int) -> dict:
|
||||
# protocol magic is included in Byron addresses only on testnets
|
||||
if protocol_magic == protocol_magics.MAINNET:
|
||||
address_attributes = {}
|
||||
else:
|
||||
address_attributes = {PROTOCOL_MAGIC_KEY: cbor.encode(protocol_magic)}
|
||||
|
||||
return address_attributes
|
||||
|
||||
|
||||
def validate_output_address(address: str, protocol_magic: int) -> None:
|
||||
address_data_encoded = _decode_address_raw(address)
|
||||
_validate_address_data_protocol_magic(address_data_encoded, protocol_magic)
|
||||
|
||||
|
||||
def _decode_address_raw(address: str) -> bytes:
|
||||
try:
|
||||
address_hex = base58.decode(address)
|
||||
address_unpacked = cbor.decode(address_hex)
|
||||
except ValueError as e:
|
||||
if __debug__:
|
||||
log.exception(__name__, e)
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
if not isinstance(address_unpacked, list) or len(address_unpacked) != 2:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
address_data_encoded = address_unpacked[0]
|
||||
if not isinstance(address_data_encoded, bytes):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
address_crc = address_unpacked[1]
|
||||
if not isinstance(address_crc, int):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
if address_crc != crc.crc32(address_data_encoded):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
return address_data_encoded
|
||||
|
||||
|
||||
def _validate_address_data_protocol_magic(
|
||||
address_data_encoded: bytes, protocol_magic: int
|
||||
) -> None:
|
||||
def validate_full_path(path: List[int]) -> bool:
|
||||
"""
|
||||
Determines whether the correct protocol magic (or none)
|
||||
is included in the address. Addresses on mainnet don't
|
||||
contain protocol magic, but addresses on the testnet do.
|
||||
"""
|
||||
address_data = cbor.decode(address_data_encoded)
|
||||
if not isinstance(address_data, list) or len(address_data) < 2:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
attributes = address_data[1]
|
||||
if protocol_magic == protocol_magics.MAINNET:
|
||||
if PROTOCOL_MAGIC_KEY in attributes:
|
||||
raise NETWORK_MISMATCH
|
||||
else: # testnet
|
||||
if len(attributes) == 0 or PROTOCOL_MAGIC_KEY not in attributes:
|
||||
raise NETWORK_MISMATCH
|
||||
|
||||
protocol_magic_cbor = attributes[PROTOCOL_MAGIC_KEY]
|
||||
address_protocol_magic = cbor.decode(protocol_magic_cbor)
|
||||
|
||||
if not isinstance(address_protocol_magic, int):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
if address_protocol_magic != protocol_magic:
|
||||
raise NETWORK_MISMATCH
|
||||
|
||||
|
||||
def validate_full_path(path: list) -> bool:
|
||||
"""
|
||||
Validates derivation path to fit 44'/1815'/a'/{0,1}/i,
|
||||
Validates derivation path to fit {44', 1852'}/1815'/a'/{0,1,2}/i,
|
||||
where `a` is an account number and i an address index.
|
||||
The max value for `a` is 20, 1 000 000 for `i`.
|
||||
The derivation scheme v1 allowed a'/0/i only,
|
||||
but in v2 it can be a'/1/i as well.
|
||||
"""
|
||||
if len(path) != 5:
|
||||
return False
|
||||
if path[0] != 44 | HARDENED:
|
||||
if path[0] not in (purposes.BYRON, purposes.SHELLEY):
|
||||
return False
|
||||
if path[1] != 1815 | HARDENED:
|
||||
return False
|
||||
if path[2] < HARDENED or path[2] > 20 | HARDENED:
|
||||
return False
|
||||
if path[3] != 0 and path[3] != 1:
|
||||
if path[3] not in (0, 1, 2):
|
||||
return False
|
||||
if path[4] > 1000000:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _address_hash(data: list) -> bytes:
|
||||
cbor_data = cbor.encode(data)
|
||||
sha_data_hash = hashlib.sha3_256(cbor_data).digest()
|
||||
res = hashlib.blake2b(data=sha_data_hash, outlen=28).digest()
|
||||
return res
|
||||
def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None:
|
||||
if address is None or len(address) == 0:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
address_bytes = get_address_bytes_unsafe(address)
|
||||
address_type = _get_address_type(address_bytes)
|
||||
|
||||
if address_type == CardanoAddressType.BYRON:
|
||||
validate_output_byron_address(address_bytes, protocol_magic)
|
||||
elif address_type in ADDRESS_TYPES_SHELLEY:
|
||||
_validate_output_shelley_address(address, address_bytes, network_id)
|
||||
else:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
|
||||
def _get_address_root(node: bip32.HDNode, address_attributes: dict) -> bytes:
|
||||
extpubkey = remove_ed25519_prefix(node.public_key()) + node.chain_code()
|
||||
return _address_hash([0, [0, extpubkey], address_attributes])
|
||||
def get_address_bytes_unsafe(address: str) -> bytes:
|
||||
try:
|
||||
address_bytes = bech32.decode_unsafe(address)
|
||||
except ValueError:
|
||||
try:
|
||||
address_bytes = base58.decode(address)
|
||||
except ValueError:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
return address_bytes
|
||||
|
||||
|
||||
def _get_address_type(address: bytes) -> int:
|
||||
return address[0] >> 4
|
||||
|
||||
|
||||
def _validate_output_shelley_address(
|
||||
address_str: str, address_bytes: bytes, network_id: int
|
||||
) -> None:
|
||||
address_type = _get_address_type(address_bytes)
|
||||
# reward address cannot be an output address
|
||||
if address_type == CardanoAddressType.REWARD:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
_validate_address_size(address_bytes, address_type)
|
||||
_validate_output_address_bech32_hrp(address_str, address_type, network_id)
|
||||
_validate_address_network_id(address_bytes, network_id)
|
||||
|
||||
|
||||
def _validate_address_size(
|
||||
address_bytes: bytes, address_type: EnumTypeCardanoAddressType
|
||||
) -> None:
|
||||
if not (
|
||||
ADDRESS_BYTES_MIN_LENGTHS[address_type]
|
||||
<= len(address_bytes)
|
||||
<= ADDRESS_BYTES_MAX_LENGTHS[address_type]
|
||||
):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
|
||||
def _validate_output_address_bech32_hrp(
|
||||
address_str: str, address_type: EnumTypeCardanoAddressType, network_id: int
|
||||
) -> None:
|
||||
valid_hrp = _get_bech32_hrp_for_address(address_type, network_id)
|
||||
bech32_hrp = bech32.get_hrp(address_str)
|
||||
|
||||
if valid_hrp != bech32_hrp:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
|
||||
def _get_bech32_hrp_for_address(
|
||||
address_type: EnumTypeCardanoAddressType, network_id: int
|
||||
) -> str:
|
||||
if address_type == CardanoAddressType.BYRON:
|
||||
# Byron address uses base58 encoding
|
||||
raise ValueError
|
||||
|
||||
if address_type == CardanoAddressType.REWARD:
|
||||
if network_ids.is_mainnet(network_id):
|
||||
return bech32.HRP_REWARD_ADDRESS
|
||||
else:
|
||||
return bech32.HRP_TESTNET_REWARD_ADDRESS
|
||||
else:
|
||||
if network_ids.is_mainnet(network_id):
|
||||
return bech32.HRP_ADDRESS
|
||||
else:
|
||||
return bech32.HRP_TESTNET_ADDRESS
|
||||
|
||||
|
||||
def _validate_address_network_id(address: bytes, network_id: int) -> None:
|
||||
if _get_address_network_id(address) != network_id:
|
||||
raise NETWORK_MISMATCH
|
||||
|
||||
|
||||
def _get_address_network_id(address: bytes) -> int:
|
||||
return address[0] & 0x0F
|
||||
|
||||
|
||||
def get_public_key_hash(keychain: seed.Keychain, path: List[int]) -> bytes:
|
||||
node = keychain.derive(path)
|
||||
public_key = remove_ed25519_prefix(node.public_key())
|
||||
return hashlib.blake2b(data=public_key, outlen=28).digest()
|
||||
|
||||
|
||||
def derive_human_readable_address(
|
||||
keychain: seed.Keychain,
|
||||
parameters: CardanoAddressParametersType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> str:
|
||||
address = derive_address_bytes(keychain, parameters, protocol_magic, network_id)
|
||||
|
||||
address_type = _get_address_type(address)
|
||||
if address_type == CardanoAddressType.BYRON:
|
||||
return base58.encode(address)
|
||||
elif address_type in ADDRESS_TYPES_SHELLEY:
|
||||
hrp = _get_bech32_hrp_for_address(_get_address_type(address), network_id)
|
||||
return bech32.encode(hrp, address)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def derive_address_bytes(
|
||||
keychain: seed.Keychain,
|
||||
parameters: CardanoAddressParametersType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> bytes:
|
||||
is_byron_address = parameters.address_type == CardanoAddressType.BYRON
|
||||
|
||||
if is_byron_address:
|
||||
address = _derive_byron_address(keychain, parameters.address_n, protocol_magic)
|
||||
else:
|
||||
address = _derive_shelley_address(keychain, parameters, network_id)
|
||||
|
||||
return address
|
||||
|
||||
|
||||
def _derive_byron_address(
|
||||
keychain: seed.Keychain, path: List[int], protocol_magic: int
|
||||
) -> bytes:
|
||||
if not is_byron_path(path):
|
||||
raise wire.DataError("Invalid path for byron address!")
|
||||
|
||||
address = derive_byron_address(keychain, path, protocol_magic)
|
||||
return address
|
||||
|
||||
|
||||
def _derive_shelley_address(
|
||||
keychain: seed.Keychain, parameters: CardanoAddressParametersType, network_id: int,
|
||||
) -> bytes:
|
||||
if not is_shelley_path(parameters.address_n):
|
||||
raise wire.DataError("Invalid path for shelley address!")
|
||||
|
||||
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.ENTERPRISE:
|
||||
address = _derive_enterprise_address(keychain, parameters.address_n, network_id)
|
||||
elif parameters.address_type == CardanoAddressType.POINTER:
|
||||
address = _derive_pointer_address(
|
||||
keychain, parameters.address_n, parameters.certificate_pointer, network_id,
|
||||
)
|
||||
elif parameters.address_type == CardanoAddressType.REWARD:
|
||||
address = _derive_reward_address(keychain, parameters.address_n, network_id)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
return address
|
||||
|
||||
|
||||
def _create_address_header(
|
||||
address_type: EnumTypeCardanoAddressType, network_id: int
|
||||
) -> bytes:
|
||||
header = address_type << 4 | network_id
|
||||
return header.to_bytes(1, "little")
|
||||
|
||||
|
||||
def _derive_base_address(
|
||||
keychain: seed.Keychain,
|
||||
path: List[int],
|
||||
staking_path: List[int],
|
||||
staking_key_hash: bytes,
|
||||
network_id: int,
|
||||
) -> bytes:
|
||||
header = _create_address_header(CardanoAddressType.BASE, network_id)
|
||||
spending_key_hash = get_public_key_hash(keychain, path)
|
||||
|
||||
_validate_base_address_staking_info(staking_path, staking_key_hash)
|
||||
|
||||
if staking_key_hash is None:
|
||||
staking_key_hash = get_public_key_hash(keychain, staking_path)
|
||||
|
||||
return header + spending_key_hash + staking_key_hash
|
||||
|
||||
|
||||
def _validate_base_address_staking_info(
|
||||
staking_path: List[int], staking_key_hash: bytes,
|
||||
) -> None:
|
||||
if (staking_key_hash is None) == (not staking_path):
|
||||
raise wire.DataError(
|
||||
"Base address needs either a staking path or a staking key hash!"
|
||||
)
|
||||
|
||||
if staking_key_hash is None and not _is_staking_path(staking_path):
|
||||
raise wire.DataError("Invalid staking path!")
|
||||
|
||||
|
||||
def _is_staking_path(path: List[int]) -> bool:
|
||||
"""
|
||||
Validates path to match 1852'/1815'/a'/2/0. Path must
|
||||
be a valid Cardano path. It must have a Shelley purpose
|
||||
(Byron paths are not valid staking paths), it must have
|
||||
2 as chain type and currently there is only one staking
|
||||
path for each account so a 0 is required for address index.
|
||||
"""
|
||||
if not validate_full_path(path):
|
||||
return False
|
||||
|
||||
if path[0] != purposes.SHELLEY:
|
||||
return False
|
||||
if path[3] != 2:
|
||||
return False
|
||||
if path[4] != 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _derive_pointer_address(
|
||||
keychain: seed.Keychain,
|
||||
path: List[int],
|
||||
pointer: CardanoBlockchainPointerType,
|
||||
network_id: int,
|
||||
) -> 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
|
||||
|
||||
|
||||
def _encode_certificate_pointer(pointer: CardanoBlockchainPointerType) -> bytes:
|
||||
if (
|
||||
pointer is None
|
||||
or pointer.block_index is None
|
||||
or pointer.tx_index is None
|
||||
or pointer.certificate_index is None
|
||||
):
|
||||
raise wire.DataError("Invalid pointer!")
|
||||
|
||||
block_index_encoded = variable_length_encode(pointer.block_index)
|
||||
tx_index_encoded = variable_length_encode(pointer.tx_index)
|
||||
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:
|
||||
if not _is_staking_path(path):
|
||||
raise wire.DataError("Invalid path for reward address!")
|
||||
|
||||
header = _create_address_header(CardanoAddressType.REWARD, network_id)
|
||||
staking_key_hash = get_public_key_hash(keychain, path)
|
||||
|
||||
return header + staking_key_hash
|
||||
|
||||
|
||||
def to_account_path(path: List[int]) -> List[int]:
|
||||
ACCOUNT_PATH_LENGTH = const(3)
|
||||
return path[:ACCOUNT_PATH_LENGTH]
|
||||
|
124
core/src/apps/cardano/byron_address.py
Normal file
124
core/src/apps/cardano/byron_address.py
Normal file
@ -0,0 +1,124 @@
|
||||
from trezor import log
|
||||
from trezor.crypto import crc, hashlib
|
||||
|
||||
from apps.common import cbor
|
||||
from apps.common.seed import remove_ed25519_prefix
|
||||
|
||||
from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, protocol_magics
|
||||
|
||||
if False:
|
||||
from trezor.crypto import bip32
|
||||
from . import seed
|
||||
|
||||
PROTOCOL_MAGIC_KEY = 2
|
||||
|
||||
|
||||
"""
|
||||
This is the legacy implementation of Byron addresses. Byron
|
||||
addresses should however remain supported in Shelley with
|
||||
exactly the same implementation, thus it is kept here
|
||||
with base58 encoding and all the nuances of Byron addresses.
|
||||
"""
|
||||
|
||||
|
||||
def _encode_address_raw(address_data_encoded) -> bytes:
|
||||
return cbor.encode(
|
||||
[cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)]
|
||||
)
|
||||
|
||||
|
||||
def derive_byron_address(
|
||||
keychain: seed.Keychain, path: list, protocol_magic: int
|
||||
) -> bytes:
|
||||
node = keychain.derive(path)
|
||||
|
||||
address_attributes = get_address_attributes(protocol_magic)
|
||||
|
||||
address_root = _get_address_root(node, address_attributes)
|
||||
address_type = 0
|
||||
address_data = [address_root, address_attributes, address_type]
|
||||
address_data_encoded = cbor.encode(address_data)
|
||||
|
||||
return _encode_address_raw(address_data_encoded)
|
||||
|
||||
|
||||
def get_address_attributes(protocol_magic: int) -> dict:
|
||||
# protocol magic is included in Byron addresses only on testnets
|
||||
if protocol_magics.is_mainnet(protocol_magic):
|
||||
address_attributes = {}
|
||||
else:
|
||||
address_attributes = {PROTOCOL_MAGIC_KEY: cbor.encode(protocol_magic)}
|
||||
|
||||
return address_attributes
|
||||
|
||||
|
||||
def validate_output_byron_address(address: bytes, protocol_magic: int) -> None:
|
||||
address_data_encoded = _decode_address_raw(address)
|
||||
_validate_address_data_protocol_magic(address_data_encoded, protocol_magic)
|
||||
|
||||
|
||||
def _decode_address_raw(address: bytes) -> bytes:
|
||||
try:
|
||||
address_unpacked = cbor.decode(address)
|
||||
except ValueError as e:
|
||||
if __debug__:
|
||||
log.exception(__name__, e)
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
if not isinstance(address_unpacked, list) or len(address_unpacked) != 2:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
address_data_encoded = address_unpacked[0]
|
||||
if not isinstance(address_data_encoded, bytes):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
address_crc = address_unpacked[1]
|
||||
if not isinstance(address_crc, int):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
if address_crc != crc.crc32(address_data_encoded):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
return address_data_encoded
|
||||
|
||||
|
||||
def _validate_address_data_protocol_magic(
|
||||
address_data_encoded: bytes, protocol_magic: int
|
||||
) -> None:
|
||||
"""
|
||||
Determines whether the correct protocol magic (or none)
|
||||
is included in the address. Addresses on mainnet don't
|
||||
contain protocol magic, but addresses on the testnet do.
|
||||
"""
|
||||
address_data = cbor.decode(address_data_encoded)
|
||||
if not isinstance(address_data, list) or len(address_data) < 2:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
attributes = address_data[1]
|
||||
if protocol_magics.is_mainnet(protocol_magic):
|
||||
if PROTOCOL_MAGIC_KEY in attributes:
|
||||
raise NETWORK_MISMATCH
|
||||
else: # testnet
|
||||
if len(attributes) == 0 or PROTOCOL_MAGIC_KEY not in attributes:
|
||||
raise NETWORK_MISMATCH
|
||||
|
||||
protocol_magic_cbor = attributes[PROTOCOL_MAGIC_KEY]
|
||||
address_protocol_magic = cbor.decode(protocol_magic_cbor)
|
||||
|
||||
if not isinstance(address_protocol_magic, int):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
if address_protocol_magic != protocol_magic:
|
||||
raise NETWORK_MISMATCH
|
||||
|
||||
|
||||
def _address_hash(data: list) -> bytes:
|
||||
cbor_data = cbor.encode(data)
|
||||
sha_data_hash = hashlib.sha3_256(cbor_data).digest()
|
||||
res = hashlib.blake2b(data=sha_data_hash, outlen=28).digest()
|
||||
return res
|
||||
|
||||
|
||||
def _get_address_root(node: bip32.HDNode, address_attributes: dict) -> bytes:
|
||||
extpubkey = remove_ed25519_prefix(node.public_key()) + node.chain_code()
|
||||
return _address_hash([0, [0, extpubkey], address_attributes])
|
@ -2,30 +2,88 @@ from trezor import log, wire
|
||||
from trezor.messages.CardanoAddress import CardanoAddress
|
||||
|
||||
from apps.common import paths
|
||||
from apps.common.layout import address_n_to_str, show_address, show_qr
|
||||
from apps.common.layout import address_n_to_str, show_qr
|
||||
|
||||
from . import CURVE, seed
|
||||
from .address import derive_address_and_node, validate_full_path
|
||||
from .address import derive_human_readable_address, to_account_path, validate_full_path
|
||||
from .helpers import protocol_magics, staking_use_cases
|
||||
from .layout import (
|
||||
show_address,
|
||||
show_warning_address_foreign_staking_key,
|
||||
show_warning_address_pointer,
|
||||
)
|
||||
|
||||
if False:
|
||||
from trezor.messages import CardanoAddressParametersType, CardanoGetAddress
|
||||
|
||||
|
||||
@seed.with_keychain
|
||||
async def get_address(ctx, msg, keychain: seed.Keychain):
|
||||
await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE)
|
||||
async def get_address(
|
||||
ctx: wire.Context, msg: CardanoGetAddress, keychain: seed.Keychain
|
||||
) -> CardanoAddress:
|
||||
address_parameters = msg.address_parameters
|
||||
|
||||
await paths.validate_path(
|
||||
ctx, validate_full_path, keychain, address_parameters.address_n, CURVE
|
||||
)
|
||||
|
||||
try:
|
||||
address, _ = derive_address_and_node(
|
||||
keychain, msg.address_n, msg.protocol_magic
|
||||
address = derive_human_readable_address(
|
||||
keychain, address_parameters, msg.protocol_magic, msg.network_id
|
||||
)
|
||||
except ValueError as e:
|
||||
if __debug__:
|
||||
log.exception(__name__, e)
|
||||
raise wire.ProcessError("Deriving address failed")
|
||||
|
||||
if msg.show_display:
|
||||
desc = address_n_to_str(msg.address_n)
|
||||
while True:
|
||||
if await show_address(ctx, address, desc=desc):
|
||||
break
|
||||
if await show_qr(ctx, address, desc=desc):
|
||||
break
|
||||
await _display_address(
|
||||
ctx, keychain, 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 = None
|
||||
if not protocol_magics.is_mainnet(protocol_magic):
|
||||
network = protocol_magic
|
||||
|
||||
while True:
|
||||
if await show_address(
|
||||
ctx,
|
||||
address,
|
||||
address_parameters.address_type,
|
||||
address_parameters.address_n,
|
||||
network=network,
|
||||
):
|
||||
break
|
||||
if await show_qr(
|
||||
ctx, address, desc=address_n_to_str(address_parameters.address_n)
|
||||
):
|
||||
break
|
||||
|
||||
|
||||
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:
|
||||
await show_warning_address_pointer(ctx, address_parameters.certificate_pointer)
|
||||
|
4
core/src/apps/cardano/helpers/__init__.py
Normal file
4
core/src/apps/cardano/helpers/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from trezor import wire
|
||||
|
||||
INVALID_ADDRESS = wire.ProcessError("Invalid address")
|
||||
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
|
34
core/src/apps/cardano/helpers/bech32.py
Normal file
34
core/src/apps/cardano/helpers/bech32.py
Normal file
@ -0,0 +1,34 @@
|
||||
from trezor.crypto import bech32
|
||||
|
||||
HRP_SEPARATOR = "1"
|
||||
|
||||
HRP_ADDRESS = "addr"
|
||||
HRP_TESTNET_ADDRESS = "addr_test"
|
||||
HRP_REWARD_ADDRESS = "stake"
|
||||
HRP_TESTNET_REWARD_ADDRESS = "stake_test"
|
||||
|
||||
|
||||
def encode(hrp: str, data: bytes) -> str:
|
||||
converted_bits = bech32.convertbits(data, 8, 5)
|
||||
return bech32.bech32_encode(hrp, converted_bits)
|
||||
|
||||
|
||||
def decode_unsafe(bech: str) -> bytes:
|
||||
hrp = get_hrp(bech)
|
||||
return decode(hrp, bech)
|
||||
|
||||
|
||||
def get_hrp(bech: str):
|
||||
return bech.rsplit(HRP_SEPARATOR, 1)[0]
|
||||
|
||||
|
||||
def decode(hrp: str, bech: str) -> bytes:
|
||||
decoded_hrp, data = bech32.bech32_decode(bech, 130)
|
||||
if decoded_hrp != hrp:
|
||||
raise ValueError
|
||||
|
||||
decoded = bech32.convertbits(data, 5, 8, False)
|
||||
if decoded is None:
|
||||
raise ValueError
|
||||
|
||||
return bytes(decoded)
|
12
core/src/apps/cardano/helpers/network_ids.py
Normal file
12
core/src/apps/cardano/helpers/network_ids.py
Normal file
@ -0,0 +1,12 @@
|
||||
MAINNET = 1
|
||||
TESTNET = 0
|
||||
|
||||
|
||||
def is_mainnet(network_id: int) -> bool:
|
||||
"""
|
||||
In the future there might be 15 mainnet IDs and
|
||||
still only one testnet ID. Therefore it is safer
|
||||
to check that it is not a testnet id. Also, if
|
||||
the mainnet id was to change, this would still work.
|
||||
"""
|
||||
return network_id != TESTNET
|
@ -7,5 +7,9 @@ NAMES = {
|
||||
}
|
||||
|
||||
|
||||
def is_mainnet(protocol_magic: int) -> bool:
|
||||
return protocol_magic == MAINNET
|
||||
|
||||
|
||||
def to_ui_string(value: int) -> str:
|
||||
return NAMES.get(value, "Unknown")
|
4
core/src/apps/cardano/helpers/purposes.py
Normal file
4
core/src/apps/cardano/helpers/purposes.py
Normal file
@ -0,0 +1,4 @@
|
||||
from apps.common import HARDENED
|
||||
|
||||
BYRON = 44 | HARDENED
|
||||
SHELLEY = 1852 | HARDENED
|
6
core/src/apps/cardano/helpers/seed_namespaces.py
Normal file
6
core/src/apps/cardano/helpers/seed_namespaces.py
Normal file
@ -0,0 +1,6 @@
|
||||
from apps.common import HARDENED
|
||||
|
||||
from . import purposes
|
||||
|
||||
BYRON = [purposes.BYRON, 1815 | HARDENED]
|
||||
SHELLEY = [purposes.SHELLEY, 1815 | HARDENED]
|
58
core/src/apps/cardano/helpers/staking_use_cases.py
Normal file
58
core/src/apps/cardano/helpers/staking_use_cases.py
Normal file
@ -0,0 +1,58 @@
|
||||
from trezor.messages import CardanoAddressType
|
||||
|
||||
from ..address import get_public_key_hash, to_account_path, validate_full_path
|
||||
from ..seed import is_shelley_path
|
||||
|
||||
if False:
|
||||
from typing import List
|
||||
from trezor.messages import CardanoAddressParametersType
|
||||
from . import seed
|
||||
|
||||
|
||||
"""
|
||||
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: seed.Keychain, address_parameters: CardanoAddressParametersType
|
||||
) -> int:
|
||||
address_type = address_parameters.address_type
|
||||
if address_type == CardanoAddressType.BASE:
|
||||
if not validate_full_path(address_parameters.address_n):
|
||||
return MISMATCH
|
||||
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]
|
20
core/src/apps/cardano/helpers/utils.py
Normal file
20
core/src/apps/cardano/helpers/utils.py
Normal file
@ -0,0 +1,20 @@
|
||||
def variable_length_encode(number: int) -> bytes:
|
||||
"""
|
||||
Used for pointer encoding in pointer address.
|
||||
Encoding description can be found here:
|
||||
https://en.wikipedia.org/wiki/Variable-length_quantity
|
||||
"""
|
||||
if number < 0:
|
||||
raise ValueError("Negative numbers not supported. Number supplied: %s" % number)
|
||||
|
||||
encoded = []
|
||||
|
||||
bit_length = len(bin(number)[2:])
|
||||
encoded.append(number & 127)
|
||||
|
||||
while bit_length > 7:
|
||||
number >>= 7
|
||||
bit_length -= 7
|
||||
encoded.insert(0, (number & 127) + 128)
|
||||
|
||||
return bytes(encoded)
|
224
core/src/apps/cardano/layout.py
Normal file
224
core/src/apps/cardano/layout.py
Normal file
@ -0,0 +1,224 @@
|
||||
from ubinascii import hexlify
|
||||
|
||||
from trezor import ui
|
||||
from trezor.messages import ButtonRequestType, CardanoAddressType
|
||||
from trezor.strings import format_amount
|
||||
from trezor.ui.button import ButtonDefault
|
||||
from trezor.ui.scroll import Paginated
|
||||
from trezor.ui.text import Text
|
||||
from trezor.utils import chunks
|
||||
|
||||
from apps.common.confirm import confirm, require_confirm, require_hold_to_confirm
|
||||
from apps.common.layout import address_n_to_str, show_warning
|
||||
|
||||
from .helpers import protocol_magics
|
||||
|
||||
if False:
|
||||
from typing import List
|
||||
from trezor import wire
|
||||
from trezor.messages import CardanoBlockchainPointerType
|
||||
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
|
||||
|
||||
|
||||
ADDRESS_TYPE_NAMES = {
|
||||
CardanoAddressType.BYRON: "Legacy",
|
||||
CardanoAddressType.BASE: "Base",
|
||||
CardanoAddressType.POINTER: "Pointer",
|
||||
CardanoAddressType.ENTERPRISE: "Enterprise",
|
||||
CardanoAddressType.REWARD: "Reward",
|
||||
}
|
||||
|
||||
|
||||
def format_coin_amount(amount: int) -> str:
|
||||
return "%s %s" % (format_amount(amount, 6), "ADA")
|
||||
|
||||
|
||||
async def confirm_sending(ctx: wire.Context, amount: int, to: str):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Confirm sending:")
|
||||
t1.bold(format_coin_amount(amount))
|
||||
t1.normal("to")
|
||||
|
||||
to_lines = list(chunks(to, 17))
|
||||
t1.bold(to_lines[0])
|
||||
|
||||
pages = [t1] + _paginate_lines(to_lines, 1, "Confirm transaction", ui.ICON_SEND)
|
||||
|
||||
await require_confirm(ctx, Paginated(pages))
|
||||
|
||||
|
||||
async def show_warning_tx_no_staking_info(
|
||||
ctx: wire.Context, address_type: EnumTypeCardanoAddressType, amount: int
|
||||
):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Change " + ADDRESS_TYPE_NAMES[address_type].lower())
|
||||
t1.normal("address has no stake")
|
||||
t1.normal("rights.")
|
||||
t1.normal("Change amount:")
|
||||
t1.bold(format_coin_amount(amount))
|
||||
|
||||
await require_confirm(ctx, t1)
|
||||
|
||||
|
||||
async def show_warning_tx_pointer_address(
|
||||
ctx: wire.Context, pointer: CardanoBlockchainPointerType, amount: int,
|
||||
):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Change address has a")
|
||||
t1.normal("pointer with staking")
|
||||
t1.normal("rights.")
|
||||
|
||||
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t2.normal("Pointer:")
|
||||
t2.bold(
|
||||
"%s, %s, %s"
|
||||
% (pointer.block_index, pointer.tx_index, pointer.certificate_index)
|
||||
)
|
||||
t2.normal("Change amount:")
|
||||
t2.bold(format_coin_amount(amount))
|
||||
|
||||
await require_confirm(ctx, Paginated([t1, t2]))
|
||||
|
||||
|
||||
async def show_warning_tx_different_staking_account(
|
||||
ctx: wire.Context, staking_account_path: List[int], amount: int,
|
||||
):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Change address staking")
|
||||
t1.normal("rights do not match")
|
||||
t1.normal("the current account.")
|
||||
|
||||
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t2.normal("Staking account:")
|
||||
t2.bold(address_n_to_str(staking_account_path))
|
||||
t2.normal("Change amount:")
|
||||
t2.bold(format_coin_amount(amount))
|
||||
|
||||
await require_confirm(ctx, Paginated([t1, t2]))
|
||||
|
||||
|
||||
async def show_warning_tx_staking_key_hash(
|
||||
ctx: wire.Context, staking_key_hash: bytes, amount: int,
|
||||
):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Change address staking")
|
||||
t1.normal("rights do not match")
|
||||
t1.normal("the current account.")
|
||||
|
||||
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t2.normal("Staking key hash:")
|
||||
t2.mono(*chunks(hexlify(staking_key_hash), 17))
|
||||
|
||||
t3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t3.normal("Change amount:")
|
||||
t3.bold(format_coin_amount(amount))
|
||||
|
||||
await require_confirm(ctx, Paginated([t1, t2, t3]))
|
||||
|
||||
|
||||
async def confirm_transaction(ctx, amount: int, fee: int, protocol_magic: int):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Transaction amount:")
|
||||
t1.bold(format_coin_amount(amount))
|
||||
t1.normal("Transaction fee:")
|
||||
t1.bold(format_coin_amount(fee))
|
||||
|
||||
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t2.normal("Network:")
|
||||
t2.bold(protocol_magics.to_ui_string(protocol_magic))
|
||||
|
||||
await require_hold_to_confirm(ctx, Paginated([t1, t2]))
|
||||
|
||||
|
||||
async def show_address(
|
||||
ctx: wire.Context,
|
||||
address: str,
|
||||
address_type: EnumTypeCardanoAddressType,
|
||||
path: List[int],
|
||||
network: int = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Custom show_address function is needed because cardano addresses don't
|
||||
fit on a single screen.
|
||||
"""
|
||||
path_str = address_n_to_str(path)
|
||||
t1 = Text(path_str, ui.ICON_RECEIVE, ui.GREEN)
|
||||
if network is not None:
|
||||
t1.normal("%s network" % protocol_magics.to_ui_string(network))
|
||||
t1.normal("%s address" % ADDRESS_TYPE_NAMES[address_type])
|
||||
|
||||
address_lines = list(chunks(address, 17))
|
||||
t1.bold(address_lines[0])
|
||||
t1.bold(address_lines[1])
|
||||
t1.bold(address_lines[2])
|
||||
|
||||
pages = [t1] + _paginate_lines(address_lines, 3, path_str, ui.ICON_RECEIVE)
|
||||
|
||||
return await confirm(
|
||||
ctx,
|
||||
Paginated(pages),
|
||||
code=ButtonRequestType.Address,
|
||||
cancel="QR",
|
||||
cancel_style=ButtonDefault,
|
||||
)
|
||||
|
||||
|
||||
def _paginate_lines(
|
||||
lines: List[str], offset: int, desc: str, icon: str, per_page: int = 4
|
||||
) -> List[ui.Component]:
|
||||
pages = []
|
||||
if len(lines) > offset:
|
||||
to_pages = list(chunks(lines[offset:], per_page))
|
||||
for page in to_pages:
|
||||
t = Text(desc, icon, ui.GREEN)
|
||||
for line in page:
|
||||
t.bold(line)
|
||||
pages.append(t)
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
async def show_warning_address_foreign_staking_key(
|
||||
ctx: wire.Context,
|
||||
account_path: List[int],
|
||||
staking_account_path: List[int],
|
||||
staking_key_hash: bytes,
|
||||
) -> None:
|
||||
await show_warning(
|
||||
ctx,
|
||||
(
|
||||
"Stake rights associated",
|
||||
"with this address do",
|
||||
"not match your",
|
||||
"account",
|
||||
address_n_to_str(account_path),
|
||||
),
|
||||
button="Ok",
|
||||
)
|
||||
|
||||
if staking_account_path:
|
||||
staking_key_message = (
|
||||
"Stake account path:",
|
||||
address_n_to_str(staking_account_path),
|
||||
)
|
||||
else:
|
||||
staking_key_message = ("Staking key:", hexlify(staking_key_hash))
|
||||
|
||||
await show_warning(
|
||||
ctx, staking_key_message, button="Ok",
|
||||
)
|
||||
|
||||
|
||||
async def show_warning_address_pointer(
|
||||
ctx: wire.Context, pointer: CardanoBlockchainPointerType
|
||||
) -> None:
|
||||
await show_warning(
|
||||
ctx,
|
||||
(
|
||||
"Pointer address:",
|
||||
"Block: %s" % pointer.block_index,
|
||||
"Transaction: %s" % pointer.tx_index,
|
||||
"Certificate: %s" % pointer.certificate_index,
|
||||
),
|
||||
button="Ok",
|
||||
)
|
@ -1,51 +0,0 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import ui
|
||||
from trezor.strings import format_amount
|
||||
from trezor.ui.scroll import Paginated
|
||||
from trezor.ui.text import Text
|
||||
from trezor.utils import chunks
|
||||
|
||||
from apps.common.confirm import require_confirm, require_hold_to_confirm
|
||||
|
||||
from .. import protocol_magics
|
||||
|
||||
|
||||
def format_coin_amount(amount):
|
||||
return "%s %s" % (format_amount(amount, 6), "ADA")
|
||||
|
||||
|
||||
async def confirm_sending(ctx, amount, to):
|
||||
to_lines = list(chunks(to, 17))
|
||||
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Confirm sending:")
|
||||
t1.bold(format_coin_amount(amount))
|
||||
t1.normal("to:")
|
||||
t1.bold(to_lines[0])
|
||||
|
||||
PER_PAGE = const(4)
|
||||
pages = [t1]
|
||||
if len(to_lines) > 1:
|
||||
to_pages = list(chunks(to_lines[1:], PER_PAGE))
|
||||
for page in to_pages:
|
||||
t = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
for line in page:
|
||||
t.bold(line)
|
||||
pages.append(t)
|
||||
|
||||
await require_confirm(ctx, Paginated(pages))
|
||||
|
||||
|
||||
async def confirm_transaction(ctx, amount, fee, protocol_magic):
|
||||
t1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t1.normal("Total amount:")
|
||||
t1.bold(format_coin_amount(amount))
|
||||
t1.normal("including fee:")
|
||||
t1.bold(format_coin_amount(fee))
|
||||
|
||||
t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
t2.normal("Network:")
|
||||
t2.bold(protocol_magics.to_ui_string(protocol_magic))
|
||||
|
||||
await require_hold_to_confirm(ctx, Paginated([t1, t2]))
|
@ -5,7 +5,7 @@ from trezor.crypto import bip32
|
||||
from apps.common import mnemonic
|
||||
from apps.common.passphrase import get as get_passphrase
|
||||
|
||||
from . import SEED_NAMESPACE
|
||||
from .helpers import seed_namespaces
|
||||
|
||||
if False:
|
||||
from apps.common.paths import Bip32Path
|
||||
@ -13,19 +13,42 @@ if False:
|
||||
|
||||
|
||||
class Keychain:
|
||||
"""Cardano keychain hard-coded to SEED_NAMESPACE."""
|
||||
"""Cardano keychain hard-coded to Byron and Shelley seed namespaces."""
|
||||
|
||||
def __init__(self, root: bip32.HDNode) -> None:
|
||||
self.root = root
|
||||
self.byron_root = self._create_namespace_root(seed_namespaces.BYRON)
|
||||
self.shelley_root = self._create_namespace_root(seed_namespaces.SHELLEY)
|
||||
|
||||
def verify_path(self, path: Bip32Path) -> None:
|
||||
if path[: len(SEED_NAMESPACE)] != SEED_NAMESPACE:
|
||||
if not is_byron_path(path) and not is_shelley_path(path):
|
||||
raise wire.DataError("Forbidden key path")
|
||||
|
||||
def _create_namespace_root(self, namespace: list):
|
||||
new_root = self.root.clone()
|
||||
for i in namespace:
|
||||
new_root.derive_cardano(i)
|
||||
|
||||
return new_root
|
||||
|
||||
def _get_path_root(self, path: list):
|
||||
if is_byron_path(path):
|
||||
return self.byron_root
|
||||
elif is_shelley_path(path):
|
||||
return self.shelley_root
|
||||
else:
|
||||
raise wire.DataError("Forbidden key path")
|
||||
|
||||
def derive(self, node_path: Bip32Path) -> bip32.HDNode:
|
||||
self.verify_path(node_path)
|
||||
path_root = self._get_path_root(node_path)
|
||||
|
||||
# this is true now, so for simplicity we don't branch on path type
|
||||
assert len(seed_namespaces.BYRON) == len(seed_namespaces.SHELLEY)
|
||||
suffix = node_path[len(seed_namespaces.SHELLEY) :]
|
||||
|
||||
# derive child node from the root
|
||||
node = self.root.clone()
|
||||
suffix = node_path[len(SEED_NAMESPACE) :]
|
||||
node = path_root.clone()
|
||||
for i in suffix:
|
||||
node.derive_cardano(i)
|
||||
return node
|
||||
@ -35,6 +58,14 @@ class Keychain:
|
||||
# self.root.__del__()
|
||||
|
||||
|
||||
def is_byron_path(path: Bip32Path):
|
||||
return path[: len(seed_namespaces.BYRON)] == seed_namespaces.BYRON
|
||||
|
||||
|
||||
def is_shelley_path(path: Bip32Path):
|
||||
return path[: len(seed_namespaces.SHELLEY)] == seed_namespaces.SHELLEY
|
||||
|
||||
|
||||
@cache.stored_async(cache.APP_CARDANO_ROOT)
|
||||
async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||
if not device.is_initialized():
|
||||
@ -49,10 +80,6 @@ async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||
seed = mnemonic.get_seed(passphrase)
|
||||
root = bip32.from_seed(seed, "ed25519 cardano seed")
|
||||
|
||||
# derive the namespaced root node
|
||||
for i in SEED_NAMESPACE:
|
||||
root.derive_cardano(i)
|
||||
|
||||
keychain = Keychain(root)
|
||||
return keychain
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
from micropython import const
|
||||
|
||||
from trezor import log, wire
|
||||
from trezor.crypto import base58, hashlib
|
||||
from trezor.crypto import hashlib
|
||||
from trezor.crypto.curve import ed25519
|
||||
from trezor.messages import CardanoAddressParametersType
|
||||
from trezor.messages.CardanoSignedTx import CardanoSignedTx
|
||||
|
||||
from apps.common import cbor
|
||||
@ -11,12 +12,24 @@ from apps.common.seed import remove_ed25519_prefix
|
||||
|
||||
from . import CURVE, seed
|
||||
from .address import (
|
||||
derive_address_and_node,
|
||||
get_address_attributes,
|
||||
derive_address_bytes,
|
||||
derive_human_readable_address,
|
||||
get_address_bytes_unsafe,
|
||||
to_account_path,
|
||||
validate_full_path,
|
||||
validate_output_address,
|
||||
)
|
||||
from .layout import confirm_sending, confirm_transaction
|
||||
from .byron_address import get_address_attributes
|
||||
from .helpers import network_ids, protocol_magics, staking_use_cases
|
||||
from .layout import (
|
||||
confirm_sending,
|
||||
confirm_transaction,
|
||||
show_warning_tx_different_staking_account,
|
||||
show_warning_tx_no_staking_info,
|
||||
show_warning_tx_pointer_address,
|
||||
show_warning_tx_staking_key_hash,
|
||||
)
|
||||
from .seed import is_byron_path, is_shelley_path
|
||||
|
||||
if False:
|
||||
from typing import Dict, List, Tuple
|
||||
@ -41,10 +54,12 @@ async def sign_tx(
|
||||
if msg.fee > LOVELACE_MAX_SUPPLY:
|
||||
raise wire.ProcessError("Fee is out of range!")
|
||||
|
||||
_validate_network_info(msg.network_id, msg.protocol_magic)
|
||||
|
||||
for i in msg.inputs:
|
||||
await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE)
|
||||
|
||||
_validate_outputs(keychain, msg.outputs, msg.protocol_magic)
|
||||
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
|
||||
|
||||
# display the transaction in UI
|
||||
await _show_tx(ctx, keychain, msg)
|
||||
@ -61,18 +76,42 @@ async def sign_tx(
|
||||
return tx
|
||||
|
||||
|
||||
def _validate_network_info(network_id: int, protocol_magic: int) -> None:
|
||||
"""
|
||||
We are only concerned about checking that both network_id and protocol_magic
|
||||
belong to the mainnet or that both belong to a testnet. We don't need to check for
|
||||
consistency between various testnets (at least for now).
|
||||
"""
|
||||
is_mainnet_network_id = network_ids.is_mainnet(network_id)
|
||||
is_mainnet_protocol_magic = protocol_magics.is_mainnet(protocol_magic)
|
||||
|
||||
if is_mainnet_network_id != is_mainnet_protocol_magic:
|
||||
raise wire.ProcessError("Invalid network id/protocol magic combination!")
|
||||
|
||||
|
||||
def _validate_outputs(
|
||||
keychain: seed.Keychain, outputs: List[CardanoTxOutputType], protocol_magic: int
|
||||
keychain: seed.Keychain,
|
||||
outputs: List[CardanoTxOutputType],
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> None:
|
||||
if not outputs:
|
||||
raise wire.ProcessError("Transaction has no outputs!")
|
||||
|
||||
total_amount = 0
|
||||
for output in outputs:
|
||||
total_amount += output.amount
|
||||
if output.address_n:
|
||||
continue
|
||||
if output.address_parameters:
|
||||
# try to derive the address to validate it
|
||||
derive_address_bytes(
|
||||
keychain, output.address_parameters, protocol_magic, network_id
|
||||
)
|
||||
elif output.address is not None:
|
||||
validate_output_address(output.address, protocol_magic)
|
||||
validate_output_address(output.address, protocol_magic, network_id)
|
||||
else:
|
||||
raise wire.ProcessError("Each output must have address or address_n field!")
|
||||
raise wire.ProcessError(
|
||||
"Each output must have an address field or address_parameters!"
|
||||
)
|
||||
|
||||
if total_amount > LOVELACE_MAX_SUPPLY:
|
||||
raise wire.ProcessError("Total transaction amount is out of range!")
|
||||
@ -82,11 +121,7 @@ def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, b
|
||||
tx_body = _build_tx_body(keychain, msg)
|
||||
tx_hash = _hash_tx_body(tx_body)
|
||||
|
||||
witnesses_for_cbor = _build_witnesses(
|
||||
keychain, msg.inputs, tx_hash, msg.protocol_magic
|
||||
)
|
||||
# byron witnesses have the key 2 in Shelley
|
||||
witnesses = {2: witnesses_for_cbor}
|
||||
witnesses = _build_witnesses(keychain, msg.inputs, tx_hash, msg.protocol_magic)
|
||||
|
||||
serialized_tx = cbor.encode([tx_body, witnesses, None])
|
||||
|
||||
@ -95,7 +130,9 @@ def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, b
|
||||
|
||||
def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
|
||||
inputs_for_cbor = _build_inputs(msg.inputs)
|
||||
outputs_for_cbor = _build_outputs(keychain, msg.outputs, msg.protocol_magic)
|
||||
outputs_for_cbor = _build_outputs(
|
||||
keychain, msg.outputs, msg.protocol_magic, msg.network_id
|
||||
)
|
||||
|
||||
tx_body = {
|
||||
0: inputs_for_cbor,
|
||||
@ -112,19 +149,23 @@ def _build_inputs(inputs: List[CardanoTxInputType]) -> List[Tuple[bytes, int]]:
|
||||
|
||||
|
||||
def _build_outputs(
|
||||
keychain: seed.Keychain, outputs: List[CardanoTxOutputType], protocol_magic: int
|
||||
keychain: seed.Keychain,
|
||||
outputs: List[CardanoTxOutputType],
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> List[Tuple[bytes, int]]:
|
||||
result = []
|
||||
for output in outputs:
|
||||
amount = output.amount
|
||||
if output.address_n:
|
||||
address, _ = derive_address_and_node(
|
||||
keychain, output.address_n, protocol_magic
|
||||
if output.address_parameters:
|
||||
address = derive_address_bytes(
|
||||
keychain, output.address_parameters, protocol_magic, network_id
|
||||
)
|
||||
else:
|
||||
address = output.address
|
||||
# output address is validated in _validate_outputs before this happens
|
||||
address = get_address_bytes_unsafe(output.address)
|
||||
|
||||
result.append((base58.decode(address), amount))
|
||||
result.append((address, amount))
|
||||
|
||||
return result
|
||||
|
||||
@ -139,9 +180,53 @@ def _build_witnesses(
|
||||
inputs: List[CardanoTxInputType],
|
||||
tx_body_hash: bytes,
|
||||
protocol_magic: int,
|
||||
) -> List[Tuple[bytes, bytes, bytes, bytes]]:
|
||||
result = []
|
||||
) -> Dict:
|
||||
shelley_witnesses = _build_shelley_witnesses(keychain, inputs, tx_body_hash)
|
||||
byron_witnesses = _build_byron_witnesses(
|
||||
keychain, inputs, tx_body_hash, protocol_magic
|
||||
)
|
||||
|
||||
# use key 0 for shelley witnesses and key 2 for byron witnesses
|
||||
# according to the spec in shelley.cddl in cardano-ledger-specs
|
||||
witnesses = {}
|
||||
if len(shelley_witnesses) > 0:
|
||||
witnesses[0] = shelley_witnesses
|
||||
if len(byron_witnesses) > 0:
|
||||
witnesses[2] = byron_witnesses
|
||||
|
||||
return witnesses
|
||||
|
||||
|
||||
def _build_shelley_witnesses(
|
||||
keychain: seed.Keychain, inputs: List[CardanoTxInputType], tx_body_hash: bytes,
|
||||
) -> List[Tuple[bytes, bytes]]:
|
||||
shelley_witnesses = []
|
||||
for input in inputs:
|
||||
if not is_shelley_path(input.address_n):
|
||||
continue
|
||||
|
||||
node = keychain.derive(input.address_n)
|
||||
|
||||
public_key = remove_ed25519_prefix(node.public_key())
|
||||
signature = ed25519.sign_ext(
|
||||
node.private_key(), node.private_key_ext(), tx_body_hash
|
||||
)
|
||||
shelley_witnesses.append((public_key, signature))
|
||||
|
||||
return shelley_witnesses
|
||||
|
||||
|
||||
def _build_byron_witnesses(
|
||||
keychain: seed.Keychain,
|
||||
inputs: List[CardanoTxInputType],
|
||||
tx_body_hash: bytes,
|
||||
protocol_magic: int,
|
||||
) -> List[Tuple[bytes, bytes, bytes, bytes]]:
|
||||
byron_witnesses = []
|
||||
for input in inputs:
|
||||
if not is_byron_path(input.address_n):
|
||||
continue
|
||||
|
||||
node = keychain.derive(input.address_n)
|
||||
|
||||
public_key = remove_ed25519_prefix(node.public_key())
|
||||
@ -151,31 +236,69 @@ def _build_witnesses(
|
||||
chain_code = node.chain_code()
|
||||
address_attributes = cbor.encode(get_address_attributes(protocol_magic))
|
||||
|
||||
result.append((public_key, signature, chain_code, address_attributes))
|
||||
byron_witnesses.append((public_key, signature, chain_code, address_attributes))
|
||||
|
||||
return result
|
||||
return byron_witnesses
|
||||
|
||||
|
||||
async def _show_tx(
|
||||
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
||||
) -> None:
|
||||
total_amount = await _show_outputs(ctx, keychain, msg)
|
||||
await confirm_transaction(ctx, total_amount, msg.fee, msg.protocol_magic)
|
||||
|
||||
|
||||
async def _show_outputs(
|
||||
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
||||
) -> int:
|
||||
total_amount = 0
|
||||
for output in msg.outputs:
|
||||
if _should_hide_output(output.address_n, msg.inputs):
|
||||
continue
|
||||
|
||||
total_amount += output.amount
|
||||
|
||||
if not output.address:
|
||||
address, _ = derive_address_and_node(
|
||||
keychain, output.address_n, msg.protocol_magic
|
||||
if output.address_parameters:
|
||||
address = derive_human_readable_address(
|
||||
keychain, output.address_parameters, msg.protocol_magic, msg.network_id
|
||||
)
|
||||
|
||||
await _show_change_output_staking_warnings(
|
||||
ctx, keychain, output.address_parameters, address, output.amount
|
||||
)
|
||||
|
||||
if _should_hide_output(output.address_parameters.address_n, msg.inputs):
|
||||
continue
|
||||
else:
|
||||
address = output.address
|
||||
|
||||
total_amount += output.amount
|
||||
|
||||
await confirm_sending(ctx, output.amount, address)
|
||||
|
||||
await confirm_transaction(ctx, total_amount, msg.fee, msg.protocol_magic)
|
||||
return total_amount
|
||||
|
||||
|
||||
async def _show_change_output_staking_warnings(
|
||||
ctx: wire.Context,
|
||||
keychain: seed.Keychain,
|
||||
address_parameters: CardanoAddressParametersType,
|
||||
address: str,
|
||||
amount: int,
|
||||
):
|
||||
address_type = address_parameters.address_type
|
||||
|
||||
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:
|
||||
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:
|
||||
await show_warning_tx_staking_key_hash(
|
||||
ctx, address_parameters.staking_key_hash, amount,
|
||||
)
|
||||
|
||||
|
||||
# addresses from the same account as inputs should be hidden
|
||||
|
@ -61,7 +61,9 @@ def bech32_encode(hrp: str, data: List[int]) -> str:
|
||||
return hrp + "1" + "".join([CHARSET[d] for d in combined])
|
||||
|
||||
|
||||
def bech32_decode(bech: str) -> Tuple[Optional[str], Optional[List[int]]]:
|
||||
def bech32_decode(
|
||||
bech: str, max_bech_len: int = 90
|
||||
) -> Tuple[Optional[str], Optional[List[int]]]:
|
||||
"""Validate a Bech32 string, and determine HRP and data."""
|
||||
if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
|
||||
bech.lower() != bech and bech.upper() != bech
|
||||
@ -69,7 +71,7 @@ def bech32_decode(bech: str) -> Tuple[Optional[str], Optional[List[int]]]:
|
||||
return (None, None)
|
||||
bech = bech.lower()
|
||||
pos = bech.rfind("1")
|
||||
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
|
||||
if pos < 1 or pos + 7 > len(bech) or len(bech) > max_bech_len:
|
||||
return (None, None)
|
||||
if not all(x in CHARSET for x in bech[pos + 1 :]):
|
||||
return (None, None)
|
||||
|
40
core/src/trezor/messages/CardanoAddressParametersType.py
Normal file
40
core/src/trezor/messages/CardanoAddressParametersType.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
from .CardanoBlockchainPointerType import CardanoBlockchainPointerType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeCardanoAddressType = Literal[0, 4, 6, 8, 14]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoAddressParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
address_type: EnumTypeCardanoAddressType = None,
|
||||
address_n: List[int] = None,
|
||||
address_n_staking: List[int] = None,
|
||||
staking_key_hash: bytes = None,
|
||||
certificate_pointer: CardanoBlockchainPointerType = None,
|
||||
) -> None:
|
||||
self.address_type = address_type
|
||||
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.staking_key_hash = staking_key_hash
|
||||
self.certificate_pointer = certificate_pointer
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('address_type', p.EnumType("CardanoAddressType", (0, 4, 6, 8, 14)), 0),
|
||||
2: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('address_n_staking', p.UVarintType, p.FLAG_REPEATED),
|
||||
4: ('staking_key_hash', p.BytesType, 0),
|
||||
5: ('certificate_pointer', CardanoBlockchainPointerType, 0),
|
||||
}
|
10
core/src/trezor/messages/CardanoAddressType.py
Normal file
10
core/src/trezor/messages/CardanoAddressType.py
Normal file
@ -0,0 +1,10 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
if False:
|
||||
from typing_extensions import Literal
|
||||
|
||||
BASE = 0 # type: Literal[0]
|
||||
POINTER = 4 # type: Literal[4]
|
||||
ENTERPRISE = 6 # type: Literal[6]
|
||||
BYRON = 8 # type: Literal[8]
|
||||
REWARD = 14 # type: Literal[14]
|
31
core/src/trezor/messages/CardanoBlockchainPointerType.py
Normal file
31
core/src/trezor/messages/CardanoBlockchainPointerType.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoBlockchainPointerType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
block_index: int = None,
|
||||
tx_index: int = None,
|
||||
certificate_index: int = None,
|
||||
) -> None:
|
||||
self.block_index = block_index
|
||||
self.tx_index = tx_index
|
||||
self.certificate_index = certificate_index
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('block_index', p.UVarintType, 0),
|
||||
2: ('tx_index', p.UVarintType, 0),
|
||||
3: ('certificate_index', p.UVarintType, 0),
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
@ -15,18 +17,21 @@ class CardanoGetAddress(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
address_n: List[int] = None,
|
||||
show_display: bool = None,
|
||||
protocol_magic: int = None,
|
||||
network_id: int = None,
|
||||
address_parameters: CardanoAddressParametersType = None,
|
||||
) -> None:
|
||||
self.address_n = address_n if address_n is not None else []
|
||||
self.show_display = show_display
|
||||
self.protocol_magic = protocol_magic
|
||||
self.network_id = network_id
|
||||
self.address_parameters = address_parameters
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
||||
2: ('show_display', p.BoolType, 0),
|
||||
3: ('protocol_magic', p.UVarintType, 0),
|
||||
4: ('network_id', p.UVarintType, 0),
|
||||
5: ('address_parameters', CardanoAddressParametersType, 0),
|
||||
}
|
||||
|
@ -23,12 +23,14 @@ class CardanoSignTx(p.MessageType):
|
||||
protocol_magic: int = None,
|
||||
fee: int = None,
|
||||
ttl: int = None,
|
||||
network_id: int = None,
|
||||
) -> None:
|
||||
self.inputs = inputs if inputs is not None else []
|
||||
self.outputs = outputs if outputs is not None else []
|
||||
self.protocol_magic = protocol_magic
|
||||
self.fee = fee
|
||||
self.ttl = ttl
|
||||
self.network_id = network_id
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
@ -38,4 +40,5 @@ class CardanoSignTx(p.MessageType):
|
||||
5: ('protocol_magic', p.UVarintType, 0),
|
||||
6: ('fee', p.UVarintType, 0),
|
||||
7: ('ttl', p.UVarintType, 0),
|
||||
8: ('network_id', p.UVarintType, 0),
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
@ -15,17 +17,17 @@ class CardanoTxOutputType(p.MessageType):
|
||||
def __init__(
|
||||
self,
|
||||
address: str = None,
|
||||
address_n: List[int] = None,
|
||||
amount: int = None,
|
||||
address_parameters: CardanoAddressParametersType = None,
|
||||
) -> None:
|
||||
self.address = address
|
||||
self.address_n = address_n if address_n is not None else []
|
||||
self.amount = amount
|
||||
self.address_parameters = address_parameters
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('address', p.UnicodeType, 0),
|
||||
2: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('amount', p.UVarintType, 0),
|
||||
4: ('address_parameters', CardanoAddressParametersType, 0),
|
||||
}
|
||||
|
@ -1,16 +1,22 @@
|
||||
from common import *
|
||||
from apps.common import seed
|
||||
|
||||
from apps.common import HARDENED
|
||||
from trezor import wire
|
||||
from trezor.crypto import bip32, slip39
|
||||
from trezor.messages import CardanoAddressType
|
||||
from trezor.messages.CardanoAddressParametersType import CardanoAddressParametersType
|
||||
from trezor.messages.CardanoBlockchainPointerType import CardanoBlockchainPointerType
|
||||
|
||||
from apps.common import HARDENED, seed
|
||||
|
||||
if not utils.BITCOIN_ONLY:
|
||||
from apps.cardano import protocol_magics
|
||||
from apps.cardano.address import (
|
||||
derive_human_readable_address,
|
||||
validate_full_path,
|
||||
)
|
||||
from apps.cardano.byron_address import (
|
||||
_get_address_root,
|
||||
_address_hash,
|
||||
validate_full_path,
|
||||
derive_address_and_node
|
||||
)
|
||||
from apps.cardano.helpers import network_ids, protocol_magics
|
||||
from apps.cardano.seed import Keychain
|
||||
|
||||
|
||||
@ -20,8 +26,6 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
mnemonic = "all all all all all all all all all all all all"
|
||||
passphrase = ""
|
||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
addresses = [
|
||||
@ -32,7 +36,11 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
|
||||
for i, expected in enumerate(addresses):
|
||||
# 44'/1815'/0'/0/i'
|
||||
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i], protocol_magics.MAINNET)
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BYRON,
|
||||
address_n=[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i],
|
||||
)
|
||||
address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_ids.MAINNET)
|
||||
self.assertEqual(expected, address)
|
||||
|
||||
nodes = [
|
||||
@ -57,7 +65,7 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
]
|
||||
|
||||
for i, (priv, ext, pub, chain) in enumerate(nodes):
|
||||
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i], protocol_magics.MAINNET)
|
||||
n = keychain.derive([0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i])
|
||||
self.assertEqual(hexlify(n.private_key()), priv)
|
||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
||||
@ -67,8 +75,6 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
mnemonic = "all all all all all all all all all all all all"
|
||||
passphrase = ""
|
||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
addresses = [
|
||||
@ -79,7 +85,11 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
|
||||
for i, expected in enumerate(addresses):
|
||||
# 44'/1815'/0'/0/i
|
||||
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
|
||||
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.MAINNET, network_ids.MAINNET)
|
||||
self.assertEqual(address, expected)
|
||||
|
||||
nodes = [
|
||||
@ -104,7 +114,7 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
]
|
||||
|
||||
for i, (priv, ext, pub, chain) in enumerate(nodes):
|
||||
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
|
||||
n = keychain.derive([0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||
self.assertEqual(hexlify(n.private_key()), priv)
|
||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
||||
@ -115,12 +125,14 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
mnemonic = "all all all all all all all all all all all all"
|
||||
passphrase = ""
|
||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
# 44'/1815'
|
||||
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815], protocol_magics.MAINNET)
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BYRON,
|
||||
address_n=[0x80000000 | 44, 0x80000000 | 1815],
|
||||
)
|
||||
address = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_ids.MAINNET)
|
||||
self.assertEqual(address, "Ae2tdPwUPEZ2FGHX3yCKPSbSgyuuTYgMxNq652zKopxT4TuWvEd8Utd92w3")
|
||||
|
||||
priv, ext, pub, chain = (
|
||||
@ -130,7 +142,7 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
b"02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a"
|
||||
)
|
||||
|
||||
_, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815], protocol_magics.MAINNET)
|
||||
n = keychain.derive([0x80000000 | 44, 0x80000000 | 1815])
|
||||
self.assertEqual(hexlify(n.private_key()), priv)
|
||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
||||
@ -198,8 +210,6 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
||||
|
||||
# Check derived nodes and addresses.
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
nodes = [
|
||||
@ -228,7 +238,12 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
|
||||
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
|
||||
# 44'/1815'/0'/0/i
|
||||
a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BYRON,
|
||||
address_n=[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i],
|
||||
)
|
||||
a = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_ids.MAINNET)
|
||||
n = keychain.derive([0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||
self.assertEqual(a, address)
|
||||
self.assertEqual(hexlify(n.private_key()), priv)
|
||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||
@ -262,8 +277,6 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
||||
|
||||
# Check derived nodes and addresses.
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
nodes = [
|
||||
@ -292,19 +305,274 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
|
||||
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
|
||||
# 44'/1815'/0'/0/i
|
||||
a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET)
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BYRON,
|
||||
address_n=[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i],
|
||||
)
|
||||
a = derive_human_readable_address(keychain, address_parameters, protocol_magics.MAINNET, network_ids.MAINNET)
|
||||
n = keychain.derive([0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||
self.assertEqual(a, address)
|
||||
self.assertEqual(hexlify(n.private_key()), priv)
|
||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
||||
self.assertEqual(hexlify(n.chain_code()), chain)
|
||||
|
||||
def test_testnet_address(self):
|
||||
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_base_address_with_invalid_parameters(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)
|
||||
|
||||
# both address_n_staking and staking_key_hash are None
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BASE,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
address_n_staking=None,
|
||||
staking_key_hash=None,
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
# address_n_staking is not a staking path
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BASE,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
address_n_staking=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
staking_key_hash=None,
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
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(1, 2, 3), "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerspqgpse33frd"),
|
||||
(network_ids.TESTNET, CardanoBlockchainPointerType(24157, 177, 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_pointer_address_invalid_pointers(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)
|
||||
|
||||
# pointer is None
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.POINTER,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
certificate_pointer=None,
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
# block index is None
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.POINTER,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
certificate_pointer=CardanoBlockchainPointerType(None, 2, 3),
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
# tx index is None
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.POINTER,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
certificate_pointer=CardanoBlockchainPointerType(1, None, 3),
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
# certificate index is None
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.POINTER,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
certificate_pointer=CardanoBlockchainPointerType(1, 2, None),
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
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_reward_address_with_non_staking_path(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)
|
||||
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.REWARD,
|
||||
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
def test_shelley_address_with_byron_namespace(self):
|
||||
"""
|
||||
It shouldn't be possible to derive Shelley addresses
|
||||
(Base, Pointer, Enterprise, Reward) with a Byron namespace (44')
|
||||
"""
|
||||
mnemonic = "test walk nut penalty hip pave soap entry language right filter choice"
|
||||
passphrase = ""
|
||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||
keychain = Keychain(node)
|
||||
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BASE,
|
||||
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.POINTER,
|
||||
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
certificate_pointer=CardanoBlockchainPointerType(0, 0, 0)
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.ENTERPRISE,
|
||||
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.REWARD,
|
||||
address_n=[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
def test_byron_address_with_shelley_namespace(self):
|
||||
"""
|
||||
It shouldn't be possible to derive Byron addresses
|
||||
with a Shelley namespace (1852')
|
||||
"""
|
||||
mnemonic = "all all all all all all all all all all all all"
|
||||
passphrase = ""
|
||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||
keychain = Keychain(node)
|
||||
|
||||
with self.assertRaises(wire.DataError):
|
||||
address_parameters = CardanoAddressParametersType(
|
||||
address_type=CardanoAddressType.BYRON,
|
||||
address_n=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
|
||||
)
|
||||
derive_human_readable_address(keychain, address_parameters, 0, 0)
|
||||
|
||||
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)
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
addresses = [
|
||||
@ -315,7 +583,11 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
|
||||
for i, expected in enumerate(addresses):
|
||||
# 44'/1815'/0'/0/i'
|
||||
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.TESTNET)
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
28
core/tests/test_apps.cardano.bech32.py
Normal file
28
core/tests/test_apps.cardano.bech32.py
Normal file
@ -0,0 +1,28 @@
|
||||
from common import *
|
||||
|
||||
from apps.cardano.helpers import bech32
|
||||
|
||||
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
|
||||
class TestCardanoBech32(unittest.TestCase):
|
||||
def test_decode_and_encode(self):
|
||||
expected_bechs = [
|
||||
# human readable part, bech32
|
||||
("a", "a12uel5l"),
|
||||
("an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio",
|
||||
"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs"),
|
||||
("abcdef", "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"),
|
||||
("1", "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j"),
|
||||
("split", "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w"),
|
||||
("addr", "addr1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsw8ezsk"),
|
||||
("addr_test", "addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5")
|
||||
]
|
||||
|
||||
for expected_human_readable_part, expected_bech in expected_bechs:
|
||||
decoded = bech32.decode(expected_human_readable_part, expected_bech)
|
||||
actual_bech = bech32.encode(expected_human_readable_part, decoded)
|
||||
|
||||
self.assertEqual(actual_bech, expected_bech)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@ -11,8 +11,6 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
mnemonic = "all all all all all all all all all all all all"
|
||||
passphrase = ""
|
||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
derivation_paths = [
|
||||
@ -20,6 +18,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
[0x80000000 | 44, 0x80000000 | 1815],
|
||||
[0x80000000 | 44, 0x80000000 | 1815, 0, 0, 0],
|
||||
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0],
|
||||
|
||||
[0x80000000 | 1852, 0x80000000 | 1815, 0x80000000, 0, 0x80000000],
|
||||
[0x80000000 | 1852, 0x80000000 | 1815],
|
||||
[0x80000000 | 1852, 0x80000000 | 1815, 0, 0, 0],
|
||||
[0x80000000 | 1852, 0x80000000 | 1815, 0x80000000, 0, 0],
|
||||
]
|
||||
|
||||
public_keys = [
|
||||
@ -27,6 +30,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
b'8c47ebce34234d04fd3dfbac33feaba6133e4e3d77c4b5ab18120ec6878ad4ce',
|
||||
b'17cc0bf978756d0d5c76f931629036a810c61801b78beecb44555773d13e3791',
|
||||
b'b90fb812a2268e9569ff1172e8daed1da3dc7e72c7bded7c5bcb7282039f90d5',
|
||||
|
||||
b'f698a764b23aa6667b1157fc4247c6a1b58c21a3865ac6a47a3590167a9e0211',
|
||||
b'e9c46841be76e3be0289694fd5c7503c04f40e5b036abac200b98a9006cf6647',
|
||||
b'6d225f078ca611f00d86cbfd8ba6c6ac7826721434eae6525686efb878b72370',
|
||||
b'5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1',
|
||||
]
|
||||
|
||||
chain_codes = [
|
||||
@ -34,6 +42,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
b'02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a',
|
||||
b'646ac4a6295326bae6831be05921edfbcb362de48dfd37b12e74c227dfad768d',
|
||||
b'fd8e71c1543de2cdc7f7623130c5f2cceb53549055fa1f5bc88199989e08cce7',
|
||||
|
||||
b'13cfb6de37a568aae56cadac907e6469b121464fe1b70a10c213eaea2cbb6636',
|
||||
b'58f3f46f4a93e7a4431e75b10af7497b747c3053cb7466ed53f4277e78a63c52',
|
||||
b'f72b3c361381db2d88289440268c94c5e7467c9414375e6b63d03026750f3c66',
|
||||
b'f123474e140a2c360b01f0fa66f2f22e2e965a5b07a80358cf75f77abbd66088',
|
||||
]
|
||||
|
||||
xpub_keys = [
|
||||
@ -41,6 +54,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
'8c47ebce34234d04fd3dfbac33feaba6133e4e3d77c4b5ab18120ec6878ad4ce02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a',
|
||||
'17cc0bf978756d0d5c76f931629036a810c61801b78beecb44555773d13e3791646ac4a6295326bae6831be05921edfbcb362de48dfd37b12e74c227dfad768d',
|
||||
'b90fb812a2268e9569ff1172e8daed1da3dc7e72c7bded7c5bcb7282039f90d5fd8e71c1543de2cdc7f7623130c5f2cceb53549055fa1f5bc88199989e08cce7',
|
||||
|
||||
'f698a764b23aa6667b1157fc4247c6a1b58c21a3865ac6a47a3590167a9e021113cfb6de37a568aae56cadac907e6469b121464fe1b70a10c213eaea2cbb6636',
|
||||
'e9c46841be76e3be0289694fd5c7503c04f40e5b036abac200b98a9006cf664758f3f46f4a93e7a4431e75b10af7497b747c3053cb7466ed53f4277e78a63c52',
|
||||
'6d225f078ca611f00d86cbfd8ba6c6ac7826721434eae6525686efb878b72370f72b3c361381db2d88289440268c94c5e7467c9414375e6b63d03026750f3c66',
|
||||
'5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1f123474e140a2c360b01f0fa66f2f22e2e965a5b07a80358cf75f77abbd66088',
|
||||
]
|
||||
|
||||
for index, derivation_path in enumerate(derivation_paths):
|
||||
@ -65,8 +83,6 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
|
||||
node = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
||||
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
# 44'/1815'/0'/0/i
|
||||
@ -116,8 +132,6 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
|
||||
node = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
||||
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain(node)
|
||||
|
||||
# 44'/1815'/0'/0/i
|
||||
|
65
core/tests/test_apps.cardano.keychain.py
Normal file
65
core/tests/test_apps.cardano.keychain.py
Normal file
@ -0,0 +1,65 @@
|
||||
from common import *
|
||||
from trezor import wire
|
||||
from trezor.crypto import bip32, slip39
|
||||
|
||||
from apps.common import HARDENED, seed
|
||||
|
||||
if not utils.BITCOIN_ONLY:
|
||||
from apps.cardano.seed import Keychain
|
||||
from apps.cardano.get_public_key import _get_public_key
|
||||
|
||||
|
||||
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
|
||||
class TestCardanoKeychain(unittest.TestCase):
|
||||
def test_various_paths_at_once(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)
|
||||
|
||||
derivation_paths = [
|
||||
[44 | HARDENED, 1815 | HARDENED, HARDENED, 0, 0],
|
||||
[44 | HARDENED, 1815 | HARDENED, HARDENED, 0, 1],
|
||||
[1852 | HARDENED, 1815 | HARDENED, HARDENED, 0, 0],
|
||||
[1852 | HARDENED, 1815 | HARDENED, HARDENED, 0, 1],
|
||||
[44 | HARDENED, 1815 | HARDENED, HARDENED, 0, 2],
|
||||
[1852 | HARDENED, 1815 | HARDENED, HARDENED, 0, 2]
|
||||
]
|
||||
|
||||
public_keys = [
|
||||
b'badd2852ccda7492364be0f88f2ba0b78c5f2d7179a941f1d19f756112b66afa',
|
||||
b'34377409140c061d76778626d43456880d5471c1cbade8c372cb6a3be9678072',
|
||||
b'73fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d',
|
||||
b'f626ab887eb5f40b502463ccf2ec5a7311676ee9e5d55c492059a366c0b4d4a1',
|
||||
b'408ee7b2d1c84d7899dba07150fae88c5411974f1762cb659dd928db8aac206b',
|
||||
b'86e8a3880767e1ed521a47de1e031d47f33d5a8095be467bffbbd3295e27258e'
|
||||
]
|
||||
|
||||
chain_codes = [
|
||||
b"e1c5d15875d3ed68667978af38fe3fe586511d87a784c0962a333c21e63a865d",
|
||||
b"15c987276326a82defa4cb6762d43442f09e5dcbcc37fa0c58f24ae2dba3d3eb",
|
||||
b"dd75e154da417becec55cdd249327454138f082110297d5e87ab25e15fad150f",
|
||||
b"f7ab126f2884db9059fa09ca83be6b8bd0250426aeb62191bdd9861457b8bc91",
|
||||
b"18d5c9d20c8d23bed068c9ff3a1126b940f0e537f9d94891828a999dda6fafd1",
|
||||
b"580bba4bb0b9c56974e16a6998322a91e857e2fac28674404da993f6197fd29f"
|
||||
]
|
||||
|
||||
xpub_keys = [
|
||||
"badd2852ccda7492364be0f88f2ba0b78c5f2d7179a941f1d19f756112b66afae1c5d15875d3ed68667978af38fe3fe586511d87a784c0962a333c21e63a865d",
|
||||
"34377409140c061d76778626d43456880d5471c1cbade8c372cb6a3be967807215c987276326a82defa4cb6762d43442f09e5dcbcc37fa0c58f24ae2dba3d3eb",
|
||||
"73fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7ddd75e154da417becec55cdd249327454138f082110297d5e87ab25e15fad150f",
|
||||
"f626ab887eb5f40b502463ccf2ec5a7311676ee9e5d55c492059a366c0b4d4a1f7ab126f2884db9059fa09ca83be6b8bd0250426aeb62191bdd9861457b8bc91",
|
||||
"408ee7b2d1c84d7899dba07150fae88c5411974f1762cb659dd928db8aac206b18d5c9d20c8d23bed068c9ff3a1126b940f0e537f9d94891828a999dda6fafd1",
|
||||
"86e8a3880767e1ed521a47de1e031d47f33d5a8095be467bffbbd3295e27258e580bba4bb0b9c56974e16a6998322a91e857e2fac28674404da993f6197fd29f"
|
||||
]
|
||||
|
||||
for index, derivation_path in enumerate(derivation_paths):
|
||||
key = _get_public_key(keychain, derivation_path)
|
||||
|
||||
self.assertEqual(hexlify(key.node.public_key), public_keys[index])
|
||||
self.assertEqual(hexlify(key.node.chain_code), chain_codes[index])
|
||||
self.assertEqual(key.xpub, xpub_keys[index])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
100
core/tests/test_apps.cardano.staking_use_cases.py
Normal file
100
core/tests/test_apps.cardano.staking_use_cases.py
Normal file
@ -0,0 +1,100 @@
|
||||
from ubinascii import unhexlify
|
||||
|
||||
from common import *
|
||||
|
||||
from apps.common import HARDENED
|
||||
from trezor.crypto import bip32
|
||||
from trezor.messages import CardanoAddressType
|
||||
from trezor.messages.CardanoAddressParametersType import CardanoAddressParametersType
|
||||
from trezor.messages.CardanoBlockchainPointerType 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()
|
33
core/tests/test_apps.cardano.utils.py
Normal file
33
core/tests/test_apps.cardano.utils.py
Normal file
@ -0,0 +1,33 @@
|
||||
from common import *
|
||||
|
||||
if not utils.BITCOIN_ONLY:
|
||||
from apps.cardano.helpers.utils import variable_length_encode
|
||||
|
||||
|
||||
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
|
||||
class TestCardanoUtils(unittest.TestCase):
|
||||
def test_variable_length_encode(self):
|
||||
test_vectors = [
|
||||
(0, bytes([0x00])),
|
||||
(42, bytes([0x2A])),
|
||||
(127, bytes([0x7F])),
|
||||
(128, bytes([0x81, 0x00])),
|
||||
(129, bytes([0x81, 0x01])),
|
||||
(255, bytes([0x81, 0x7F])),
|
||||
(256, bytes([0x82, 0x00])),
|
||||
(16383, bytes([0xFF, 0x7F])),
|
||||
(16384, bytes([0x81, 0x80, 0x00])),
|
||||
]
|
||||
|
||||
for number, expected in test_vectors:
|
||||
actual = variable_length_encode(number)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
def test_variable_length_encode_negative_number(self):
|
||||
with self.assertRaises(ValueError):
|
||||
variable_length_encode(-1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -20,19 +20,130 @@ from . import messages, tools
|
||||
from .tools import expect
|
||||
|
||||
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
|
||||
NETWORK_IDS = {"mainnet": 1, "testnet": 0}
|
||||
|
||||
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs")
|
||||
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index")
|
||||
|
||||
INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields"
|
||||
|
||||
ADDRESS_TYPES = (
|
||||
messages.CardanoAddressType.BYRON,
|
||||
messages.CardanoAddressType.BASE,
|
||||
messages.CardanoAddressType.POINTER,
|
||||
messages.CardanoAddressType.ENTERPRISE,
|
||||
messages.CardanoAddressType.REWARD,
|
||||
)
|
||||
|
||||
|
||||
def create_address_parameters(
|
||||
address_type: messages.CardanoAddressType,
|
||||
address_n: List[int],
|
||||
address_n_staking: List[int] = None,
|
||||
staking_key_hash: bytes = None,
|
||||
block_index: int = None,
|
||||
tx_index: int = None,
|
||||
certificate_index: int = None,
|
||||
) -> messages.CardanoAddressParametersType:
|
||||
certificate_pointer = None
|
||||
|
||||
if address_type not in ADDRESS_TYPES:
|
||||
raise ValueError("Unknown address type")
|
||||
|
||||
if address_type == messages.CardanoAddressType.POINTER:
|
||||
certificate_pointer = create_certificate_pointer(
|
||||
block_index, tx_index, certificate_index
|
||||
)
|
||||
|
||||
return messages.CardanoAddressParametersType(
|
||||
address_type=address_type,
|
||||
address_n=address_n,
|
||||
address_n_staking=address_n_staking,
|
||||
staking_key_hash=staking_key_hash,
|
||||
certificate_pointer=certificate_pointer,
|
||||
)
|
||||
|
||||
|
||||
def create_certificate_pointer(
|
||||
block_index: int, tx_index: int, certificate_index: int
|
||||
) -> messages.CardanoBlockchainPointerType:
|
||||
if block_index is None or tx_index is None or certificate_index is None:
|
||||
raise ValueError("Invalid pointer parameters")
|
||||
|
||||
return messages.CardanoBlockchainPointerType(
|
||||
block_index=block_index, tx_index=tx_index, certificate_index=certificate_index
|
||||
)
|
||||
|
||||
|
||||
def create_input(input) -> messages.CardanoTxInputType:
|
||||
if not all(input.get(k) is not None for k in REQUIRED_FIELDS_INPUT):
|
||||
raise ValueError("The input is missing some fields")
|
||||
|
||||
path = input["path"]
|
||||
|
||||
return messages.CardanoTxInputType(
|
||||
address_n=tools.parse_path(path),
|
||||
prev_hash=bytes.fromhex(input["prev_hash"]),
|
||||
prev_index=input["prev_index"],
|
||||
)
|
||||
|
||||
|
||||
def create_output(output) -> messages.CardanoTxOutputType:
|
||||
contains_address = output.get("address") is not None
|
||||
contains_address_type = output.get("addressType") is not None
|
||||
|
||||
if output.get("amount") is None:
|
||||
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
|
||||
if not (contains_address or contains_address_type):
|
||||
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
|
||||
|
||||
if contains_address:
|
||||
return messages.CardanoTxOutputType(
|
||||
address=output["address"], amount=int(output["amount"])
|
||||
)
|
||||
else:
|
||||
return _create_change_output(output)
|
||||
|
||||
|
||||
def _create_change_output(output) -> messages.CardanoTxOutputType:
|
||||
if output.get("path") is None:
|
||||
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
|
||||
|
||||
staking_key_hash_bytes = None
|
||||
if output.get("stakingKeyHash"):
|
||||
staking_key_hash_bytes = bytes.fromhex(output.get("stakingKeyHash"))
|
||||
|
||||
address_parameters = create_address_parameters(
|
||||
int(output["addressType"]),
|
||||
tools.parse_path(output["path"]),
|
||||
tools.parse_path(output.get("stakingPath")),
|
||||
staking_key_hash_bytes,
|
||||
output.get("blockIndex"),
|
||||
output.get("txIndex"),
|
||||
output.get("certificateIndex"),
|
||||
)
|
||||
|
||||
return messages.CardanoTxOutputType(
|
||||
address_parameters=address_parameters, amount=int(output["amount"])
|
||||
)
|
||||
|
||||
|
||||
# ====== Client functions ====== #
|
||||
|
||||
|
||||
@expect(messages.CardanoAddress, field="address")
|
||||
def get_address(
|
||||
client, address_n: List[int], protocol_magic: int, show_display=False
|
||||
client,
|
||||
address_parameters: messages.CardanoAddressParametersType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
show_display=False,
|
||||
) -> messages.CardanoAddress:
|
||||
return client.call(
|
||||
messages.CardanoGetAddress(
|
||||
address_n=address_n,
|
||||
address_parameters=address_parameters,
|
||||
protocol_magic=protocol_magic,
|
||||
network_id=network_id,
|
||||
show_display=show_display,
|
||||
)
|
||||
)
|
||||
@ -51,6 +162,7 @@ def sign_tx(
|
||||
fee: int,
|
||||
ttl: int,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> messages.CardanoSignedTx:
|
||||
response = client.call(
|
||||
messages.CardanoSignTx(
|
||||
@ -59,36 +171,8 @@ def sign_tx(
|
||||
fee=fee,
|
||||
ttl=ttl,
|
||||
protocol_magic=protocol_magic,
|
||||
network_id=network_id,
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def create_input(input) -> messages.CardanoTxInputType:
|
||||
if not all(input.get(k) is not None for k in REQUIRED_FIELDS_INPUT):
|
||||
raise ValueError("The input is missing some fields")
|
||||
|
||||
path = input["path"]
|
||||
|
||||
return messages.CardanoTxInputType(
|
||||
address_n=tools.parse_path(path),
|
||||
prev_hash=bytes.fromhex(input["prev_hash"]),
|
||||
prev_index=input["prev_index"],
|
||||
)
|
||||
|
||||
|
||||
def create_output(output) -> messages.CardanoTxOutputType:
|
||||
if not output.get("amount") or not (output.get("address") or output.get("path")):
|
||||
raise ValueError("The output is missing some fields")
|
||||
|
||||
if output.get("path"):
|
||||
path = output["path"]
|
||||
|
||||
return messages.CardanoTxOutputType(
|
||||
address_n=tools.parse_path(path), amount=int(output["amount"])
|
||||
)
|
||||
|
||||
return messages.CardanoTxOutputType(
|
||||
address=output["address"], amount=int(output["amount"])
|
||||
)
|
||||
|
@ -18,11 +18,19 @@ import json
|
||||
|
||||
import click
|
||||
|
||||
from .. import cardano, tools
|
||||
from . import with_client
|
||||
from .. import cardano, messages, tools
|
||||
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():
|
||||
@ -37,19 +45,27 @@ def cli():
|
||||
required=True,
|
||||
help="Transaction in JSON format",
|
||||
)
|
||||
@click.option("-p", "--protocol-magic", type=int, default=1)
|
||||
@click.option(
|
||||
"-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"]
|
||||
)
|
||||
@click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"])
|
||||
@click.option("-t", "--testnet", is_flag=True)
|
||||
@with_client
|
||||
def sign_tx(client, file, protocol_magic):
|
||||
def sign_tx(client, file, protocol_magic, network_id, testnet):
|
||||
"""Sign Cardano transaction."""
|
||||
transaction = json.load(file)
|
||||
|
||||
if testnet:
|
||||
protocol_magic = cardano.PROTOCOL_MAGICS["testnet"]
|
||||
network_id = cardano.NETWORK_IDS["testnet"]
|
||||
|
||||
inputs = [cardano.create_input(input) for input in transaction["inputs"]]
|
||||
outputs = [cardano.create_output(output) for output in transaction["outputs"]]
|
||||
fee = transaction["fee"]
|
||||
ttl = transaction["ttl"]
|
||||
|
||||
signed_transaction = cardano.sign_tx(
|
||||
client, inputs, outputs, fee, ttl, protocol_magic
|
||||
client, inputs, outputs, fee, ttl, protocol_magic, network_id
|
||||
)
|
||||
|
||||
return {
|
||||
@ -61,12 +77,67 @@ def sign_tx(client, file, protocol_magic):
|
||||
@cli.command()
|
||||
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
||||
@click.option("-d", "--show-display", is_flag=True)
|
||||
@click.option("-p", "--protocol-magic", type=int, default=1)
|
||||
@click.option("-t", "--address-type", type=ChoiceType(ADDRESS_TYPES), 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(
|
||||
"-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"]
|
||||
)
|
||||
@click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"])
|
||||
@click.option("-e", "--testnet", is_flag=True)
|
||||
@with_client
|
||||
def get_address(client, address, show_display, protocol_magic):
|
||||
"""Get Cardano address."""
|
||||
address_n = tools.parse_path(address)
|
||||
return cardano.get_address(client, address_n, protocol_magic, show_display)
|
||||
def get_address(
|
||||
client,
|
||||
address,
|
||||
address_type,
|
||||
staking_address,
|
||||
staking_key_hash,
|
||||
block_index,
|
||||
tx_index,
|
||||
certificate_index,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
show_display,
|
||||
testnet,
|
||||
):
|
||||
"""
|
||||
Get Cardano address.
|
||||
|
||||
All address types require the address, address_type, protocol_magic and
|
||||
network_id parameters.
|
||||
|
||||
When deriving a base address you can choose to include staking info as
|
||||
staking_address or staking_key_hash - one has to be chosen.
|
||||
|
||||
When deriving a pointer address you need to specify the block_index,
|
||||
tx_index and certificate_index parameters.
|
||||
|
||||
Byron, enterprise and reward addresses only require the general parameters.
|
||||
"""
|
||||
if testnet:
|
||||
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)
|
||||
|
||||
address_parameters = cardano.create_address_parameters(
|
||||
address_type,
|
||||
tools.parse_path(address),
|
||||
tools.parse_path(staking_address),
|
||||
staking_key_hash_bytes,
|
||||
block_index,
|
||||
tx_index,
|
||||
certificate_index,
|
||||
)
|
||||
|
||||
return cardano.get_address(
|
||||
client, address_parameters, protocol_magic, network_id, show_display
|
||||
)
|
||||
|
||||
|
||||
@cli.command()
|
||||
|
@ -0,0 +1,40 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
from .CardanoBlockchainPointerType import CardanoBlockchainPointerType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeCardanoAddressType = Literal[0, 4, 6, 8, 14]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoAddressParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
address_type: EnumTypeCardanoAddressType = None,
|
||||
address_n: List[int] = None,
|
||||
address_n_staking: List[int] = None,
|
||||
staking_key_hash: bytes = None,
|
||||
certificate_pointer: CardanoBlockchainPointerType = None,
|
||||
) -> None:
|
||||
self.address_type = address_type
|
||||
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.staking_key_hash = staking_key_hash
|
||||
self.certificate_pointer = certificate_pointer
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('address_type', p.EnumType("CardanoAddressType", (0, 4, 6, 8, 14)), 0),
|
||||
2: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('address_n_staking', p.UVarintType, p.FLAG_REPEATED),
|
||||
4: ('staking_key_hash', p.BytesType, 0),
|
||||
5: ('certificate_pointer', CardanoBlockchainPointerType, 0),
|
||||
}
|
10
python/src/trezorlib/messages/CardanoAddressType.py
Normal file
10
python/src/trezorlib/messages/CardanoAddressType.py
Normal file
@ -0,0 +1,10 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
if False:
|
||||
from typing_extensions import Literal
|
||||
|
||||
BASE = 0 # type: Literal[0]
|
||||
POINTER = 4 # type: Literal[4]
|
||||
ENTERPRISE = 6 # type: Literal[6]
|
||||
BYRON = 8 # type: Literal[8]
|
||||
REWARD = 14 # type: Literal[14]
|
@ -0,0 +1,31 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoBlockchainPointerType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
block_index: int = None,
|
||||
tx_index: int = None,
|
||||
certificate_index: int = None,
|
||||
) -> None:
|
||||
self.block_index = block_index
|
||||
self.tx_index = tx_index
|
||||
self.certificate_index = certificate_index
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('block_index', p.UVarintType, 0),
|
||||
2: ('tx_index', p.UVarintType, 0),
|
||||
3: ('certificate_index', p.UVarintType, 0),
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
@ -15,18 +17,21 @@ class CardanoGetAddress(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
address_n: List[int] = None,
|
||||
show_display: bool = None,
|
||||
protocol_magic: int = None,
|
||||
network_id: int = None,
|
||||
address_parameters: CardanoAddressParametersType = None,
|
||||
) -> None:
|
||||
self.address_n = address_n if address_n is not None else []
|
||||
self.show_display = show_display
|
||||
self.protocol_magic = protocol_magic
|
||||
self.network_id = network_id
|
||||
self.address_parameters = address_parameters
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
||||
2: ('show_display', p.BoolType, 0),
|
||||
3: ('protocol_magic', p.UVarintType, 0),
|
||||
4: ('network_id', p.UVarintType, 0),
|
||||
5: ('address_parameters', CardanoAddressParametersType, 0),
|
||||
}
|
||||
|
@ -23,12 +23,14 @@ class CardanoSignTx(p.MessageType):
|
||||
protocol_magic: int = None,
|
||||
fee: int = None,
|
||||
ttl: int = None,
|
||||
network_id: int = None,
|
||||
) -> None:
|
||||
self.inputs = inputs if inputs is not None else []
|
||||
self.outputs = outputs if outputs is not None else []
|
||||
self.protocol_magic = protocol_magic
|
||||
self.fee = fee
|
||||
self.ttl = ttl
|
||||
self.network_id = network_id
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
@ -38,4 +40,5 @@ class CardanoSignTx(p.MessageType):
|
||||
5: ('protocol_magic', p.UVarintType, 0),
|
||||
6: ('fee', p.UVarintType, 0),
|
||||
7: ('ttl', p.UVarintType, 0),
|
||||
8: ('network_id', p.UVarintType, 0),
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
@ -15,17 +17,17 @@ class CardanoTxOutputType(p.MessageType):
|
||||
def __init__(
|
||||
self,
|
||||
address: str = None,
|
||||
address_n: List[int] = None,
|
||||
amount: int = None,
|
||||
address_parameters: CardanoAddressParametersType = None,
|
||||
) -> None:
|
||||
self.address = address
|
||||
self.address_n = address_n if address_n is not None else []
|
||||
self.amount = amount
|
||||
self.address_parameters = address_parameters
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('address', p.UnicodeType, 0),
|
||||
2: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('amount', p.UVarintType, 0),
|
||||
4: ('address_parameters', CardanoAddressParametersType, 0),
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ from .ButtonAck import ButtonAck
|
||||
from .ButtonRequest import ButtonRequest
|
||||
from .Cancel import Cancel
|
||||
from .CardanoAddress import CardanoAddress
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
from .CardanoBlockchainPointerType import CardanoBlockchainPointerType
|
||||
from .CardanoGetAddress import CardanoGetAddress
|
||||
from .CardanoGetPublicKey import CardanoGetPublicKey
|
||||
from .CardanoPublicKey import CardanoPublicKey
|
||||
@ -277,6 +279,7 @@ from . import BinanceOrderType
|
||||
from . import BinanceTimeInForce
|
||||
from . import ButtonRequestType
|
||||
from . import Capability
|
||||
from . import CardanoAddressType
|
||||
from . import DebugLinkShowTextStyle
|
||||
from . import DebugSwipeDirection
|
||||
from . import FailureType
|
||||
|
@ -16,10 +16,14 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib.cardano import PROTOCOL_MAGICS, get_address
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
from ..common import MNEMONIC12
|
||||
from trezorlib import tools
|
||||
from trezorlib.cardano import (
|
||||
NETWORK_IDS,
|
||||
PROTOCOL_MAGICS,
|
||||
create_address_parameters,
|
||||
get_address,
|
||||
)
|
||||
from trezorlib.messages import CardanoAddressType
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@ -32,38 +36,250 @@ from ..common import MNEMONIC12
|
||||
(
|
||||
"m/44'/1815'/0'/0/0",
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
"Ae2tdPwUPEZLCq3sFv4wVYxwqjMH2nUzBVt1HFr4v87snYrtYq3d3bq2PUQ",
|
||||
"Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRdsswsZ",
|
||||
),
|
||||
(
|
||||
"m/44'/1815'/0'/0/1",
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
"Ae2tdPwUPEZEY6pVJoyuNNdLp7VbMB7U7qfebeJ7XGunk5Z2eHarkcN1bHK",
|
||||
"Ae2tdPwUPEZJb8r1VZxweSwHDTYtqeYqF39rZmVbrNK62JHd4Wd7Ytsc8eG",
|
||||
),
|
||||
(
|
||||
"m/44'/1815'/0'/0/2",
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
"Ae2tdPwUPEZ3gZD1QeUHvAqadAV59Zid6NP9VCR9BG5LLAja9YtBUgr6ttK",
|
||||
"Ae2tdPwUPEZFm6Y7aPZGKMyMAK16yA5pWWKU9g73ncUQNZsAjzjhszenCsq",
|
||||
),
|
||||
# testnet
|
||||
# data generated by code under test
|
||||
(
|
||||
"m/44'/1815'/0'/0/0",
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
"2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
|
||||
"2657WMsDfac5F3zbgs9BwNWx3dhGAJERkAL93gPa68NJ2i8mbCHm2pLUHWSj8Mfea",
|
||||
),
|
||||
(
|
||||
"m/44'/1815'/0'/0/1",
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
"2657WMsDfac61ebUDw53WUX49Dcfya8S8G7iYbhN4nP8JSFuh38T1LuFax1bUnhxA",
|
||||
"2657WMsDfac6ezKWszxLFqJjSUgpg9NgxKc1koqi24sVpRaPhiwMaExk4useKn5HA",
|
||||
),
|
||||
(
|
||||
"m/44'/1815'/0'/0/2",
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
"2657WMsDfac5PMpEsxc1md3pgZKUZRZ11MUK8tjkDHBQG9b3TMBsTQc4PmmumVrcn",
|
||||
"2657WMsDfac7hr1ioJGr6g7r6JRx4r1My8Rj91tcPTeVjJDpfBYKURrPG2zVLx2Sq",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
||||
def test_cardano_get_address(client, path, protocol_magic, expected_address):
|
||||
address = get_address(client, parse_path(path), protocol_magic)
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.BYRON, address_n=tools.parse_path(path),
|
||||
),
|
||||
protocol_magic=protocol_magic,
|
||||
network_id=NETWORK_IDS["mainnet"],
|
||||
)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"path, staking_path, network_id, expected_address",
|
||||
[
|
||||
# data generated with code under test
|
||||
(
|
||||
"m/1852'/1815'/4'/0/0",
|
||||
"m/1852'/1815'/4'/2/0",
|
||||
NETWORK_IDS["mainnet"],
|
||||
"addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wnrqua2vw243tmjfjt0h5wsru6appuz8c0pfd75ur7myyeqsx9990",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/4'/0/0",
|
||||
"m/1852'/1815'/4'/2/0",
|
||||
NETWORK_IDS["testnet"],
|
||||
"addr_test1qrv42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wnrqua2vw243tmjfjt0h5wsru6appuz8c0pfd75ur7myyeqnsc9fs",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cardano_get_base_address(
|
||||
client, path, staking_path, network_id, expected_address
|
||||
):
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.BASE,
|
||||
address_n=tools.parse_path(path),
|
||||
address_n_staking=tools.parse_path(staking_path),
|
||||
),
|
||||
protocol_magic=PROTOCOL_MAGICS["mainnet"],
|
||||
network_id=network_id,
|
||||
)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"path, staking_key_hash, network_id, expected_address",
|
||||
[
|
||||
# data generated with code under test
|
||||
(
|
||||
"m/1852'/1815'/4'/0/0",
|
||||
"1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
|
||||
NETWORK_IDS["mainnet"],
|
||||
"addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsydc62k",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/4'/0/0",
|
||||
"1bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff",
|
||||
NETWORK_IDS["testnet"],
|
||||
"addr_test1qrv42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hls8m96xf",
|
||||
),
|
||||
# staking key hash not owned - derived with "all all..." mnenomnic, data generated with code under test
|
||||
(
|
||||
"m/1852'/1815'/4'/0/0",
|
||||
"122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277",
|
||||
NETWORK_IDS["mainnet"],
|
||||
"addr1q8v42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms06skxl",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/0'/0/0",
|
||||
"122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277",
|
||||
NETWORK_IDS["testnet"],
|
||||
"addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/4'/0/0",
|
||||
"122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277",
|
||||
NETWORK_IDS["testnet"],
|
||||
"addr_test1qrv42wjda8r6mpfj40d36znlgfdcqp7jtj03ah8skh6u8wsj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsvvdk2q",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cardano_get_base_address_with_staking_key_hash(
|
||||
client, path, staking_key_hash, network_id, expected_address
|
||||
):
|
||||
# data form shelley test vectors
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.BASE,
|
||||
address_n=tools.parse_path(path),
|
||||
staking_key_hash=bytes.fromhex(staking_key_hash),
|
||||
),
|
||||
protocol_magic=PROTOCOL_MAGICS["mainnet"],
|
||||
network_id=network_id,
|
||||
)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"path, network_id, expected_address",
|
||||
[
|
||||
# data generated with code under test
|
||||
(
|
||||
"m/1852'/1815'/0'/0/0",
|
||||
NETWORK_IDS["mainnet"],
|
||||
"addr1vxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92su77c6m",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/0'/0/0",
|
||||
NETWORK_IDS["testnet"],
|
||||
"addr_test1vzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92s8k2y47",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cardano_get_enterprise_address(client, path, network_id, expected_address):
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.ENTERPRISE,
|
||||
address_n=tools.parse_path(path),
|
||||
),
|
||||
protocol_magic=PROTOCOL_MAGICS["mainnet"],
|
||||
network_id=network_id,
|
||||
)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"path, block_index, tx_index, certificate_index, network_id, expected_address",
|
||||
[
|
||||
# data generated with code under test
|
||||
(
|
||||
"m/1852'/1815'/0'/0/0",
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
NETWORK_IDS["mainnet"],
|
||||
"addr1gxq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92spqgpsl97q83",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/0'/0/0",
|
||||
24157,
|
||||
177,
|
||||
42,
|
||||
NETWORK_IDS["testnet"],
|
||||
"addr_test1gzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z925ph3wczvf2ag2x9t",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cardano_get_pointer_address(
|
||||
client,
|
||||
path,
|
||||
block_index,
|
||||
tx_index,
|
||||
certificate_index,
|
||||
network_id,
|
||||
expected_address,
|
||||
):
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.POINTER,
|
||||
address_n=tools.parse_path(path),
|
||||
block_index=block_index,
|
||||
tx_index=tx_index,
|
||||
certificate_index=certificate_index,
|
||||
),
|
||||
protocol_magic=PROTOCOL_MAGICS["mainnet"],
|
||||
network_id=network_id,
|
||||
)
|
||||
assert address == expected_address
|
||||
|
||||
|
||||
@pytest.mark.altcoin
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"path, network_id, expected_address",
|
||||
[
|
||||
# data generated with code under test
|
||||
(
|
||||
"m/1852'/1815'/0'/2/0",
|
||||
NETWORK_IDS["mainnet"],
|
||||
"stake1uyfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yacalmqha",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/0'/2/0",
|
||||
NETWORK_IDS["testnet"],
|
||||
"stake_test1uqfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yac643znq",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_cardano_get_reward_address(client, path, network_id, expected_address):
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.REWARD, address_n=tools.parse_path(path),
|
||||
),
|
||||
protocol_magic=PROTOCOL_MAGICS["mainnet"],
|
||||
network_id=network_id,
|
||||
)
|
||||
assert address == expected_address
|
||||
|
@ -16,8 +16,9 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib.cardano import PROTOCOL_MAGICS, get_address
|
||||
from trezorlib.tools import parse_path
|
||||
from trezorlib import tools
|
||||
from trezorlib.cardano import PROTOCOL_MAGICS, create_address_parameters, get_address
|
||||
from trezorlib.messages import CardanoAddressType
|
||||
|
||||
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
||||
|
||||
@ -70,5 +71,13 @@ def test_cardano_get_address(client, path, protocol_magic, expected_address):
|
||||
assert client.features.passphrase_protection is True
|
||||
client.use_passphrase("TREZOR")
|
||||
|
||||
address = get_address(client, parse_path(path), protocol_magic)
|
||||
address = get_address(
|
||||
client,
|
||||
address_parameters=create_address_parameters(
|
||||
address_type=CardanoAddressType.BYRON, address_n=tools.parse_path(path),
|
||||
),
|
||||
protocol_magic=protocol_magic,
|
||||
network_id=0,
|
||||
)
|
||||
assert address == expected_address
|
||||
assert address == expected_address
|
||||
|
@ -37,14 +37,24 @@ from trezorlib.tools import parse_path
|
||||
"70f131bb799fd659c997221ad8cae7dcce4e8da701f8101cf15307fd3a3712a1",
|
||||
),
|
||||
(
|
||||
"m/44'/1815'/2'",
|
||||
"076338cee5ab3dae19f06ccaa80e3d4428cf0e1bdc04243e41bba7be63a90da7",
|
||||
"5dcdf129f6f2d108292e615c4b67a1fc41a64e6a96130f5c981e5e8e046a6cd7",
|
||||
"m/1852'/1815'/0'",
|
||||
"d507c8f866691bd96e131334c355188b1a1d0b2fa0ab11545075aab332d77d9e",
|
||||
"b19657ad13ee581b56b0f8d744d66ca356b93d42fe176b3de007d53e9c4c4e7a",
|
||||
),
|
||||
(
|
||||
"m/44'/1815'/3'",
|
||||
"5f769380dc6fd17a4e0f2d23aa359442a712e5e96d7838ebb91eb020003cccc3",
|
||||
"1197ea234f528987cbac9817ebc31344395b837a3bb7c2332f87e095e70550a5",
|
||||
"m/1852'/1815'/1'",
|
||||
"140791584001446365f169c82241c7c214475000180dab39fa0588fc9c3d6d80",
|
||||
"7f9f812d49816844b52e319857aa75961724ad1a146701679d02d7168622233d",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/2'",
|
||||
"ff6ccc3097ca79fc29fe92a9639c47644746780c63acae10a9e6f03bf5c919dd",
|
||||
"27d985feabf40d83a30aa4645ff008c068187559dd224ba59e26d0d2dc3598ce",
|
||||
),
|
||||
(
|
||||
"m/1852'/1815'/3'",
|
||||
"be81ace1f63f4f0cae74dd274a72d7818f238bc764ab3e0dc0beb1945b756dca",
|
||||
"29034f036a162ac4f9f9f397b2d1f289754bb6633915f26b199e156f81d05c88",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
@ -17,21 +17,73 @@
|
||||
import pytest
|
||||
|
||||
from trezorlib import cardano, messages
|
||||
from trezorlib.cardano import PROTOCOL_MAGICS
|
||||
from trezorlib.cardano import NETWORK_IDS, PROTOCOL_MAGICS
|
||||
from trezorlib.exceptions import TrezorFailure
|
||||
|
||||
SAMPLE_INPUT = {
|
||||
"path": "m/44'/1815'/0'/0/1",
|
||||
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
|
||||
"prev_index": 0,
|
||||
|
||||
class InputAction:
|
||||
"""
|
||||
Test cases don't use the same input flows. These constants are used to define
|
||||
the expected input flows for each test case. Corresponding input actions
|
||||
are then executed on the device to simulate user inputs.
|
||||
"""
|
||||
|
||||
SWIPE = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
SAMPLE_INPUTS = {
|
||||
"byron_input": {
|
||||
"path": "m/44'/1815'/0'/0/1",
|
||||
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
|
||||
"prev_index": 0,
|
||||
},
|
||||
"shelley_input": {
|
||||
"path": "m/1852'/1815'/0'/0/0",
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0,
|
||||
},
|
||||
}
|
||||
|
||||
SAMPLE_OUTPUTS = {
|
||||
"simple_output": {
|
||||
"simple_byron_output": {
|
||||
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"change_output": {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
|
||||
"byron_change_output": {
|
||||
"addressType": 8,
|
||||
"path": "m/44'/1815'/0'/0/1",
|
||||
"amount": "1000000",
|
||||
},
|
||||
"simple_shelley_output": {
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1",
|
||||
},
|
||||
"base_address_change_output": {
|
||||
"addressType": 0,
|
||||
"path": "m/1852'/1815'/0'/0/0",
|
||||
"stakingPath": "m/1852'/1815'/0'/2/0",
|
||||
"amount": "7120787",
|
||||
},
|
||||
"staking_key_hash_output": {
|
||||
"addressType": 0,
|
||||
"path": "m/1852'/1815'/0'/0/0",
|
||||
"stakingKeyHash": "32c728d3861e164cab28cb8f006448139c8f1740ffb8e7aa9e5232dc",
|
||||
"amount": "7120787",
|
||||
},
|
||||
"pointer_address_output": {
|
||||
"addressType": 4,
|
||||
"path": "m/1852'/1815'/0'/0/0",
|
||||
"blockIndex": 1,
|
||||
"txIndex": 2,
|
||||
"certificateIndex": 3,
|
||||
"amount": "7120787",
|
||||
},
|
||||
"enterprise_address_output": {
|
||||
"addressType": 6,
|
||||
"path": "m/1852'/1815'/0'/0/0",
|
||||
"amount": "7120787",
|
||||
},
|
||||
"invalid_address": {
|
||||
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
|
||||
"amount": "3003112",
|
||||
@ -44,14 +96,42 @@ SAMPLE_OUTPUTS = {
|
||||
"address": "Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRZR3Mbm",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"large_simple_output": {
|
||||
"invalid_base_address_too_short": {
|
||||
"address": "addr1q89s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqqj922xhxkn6twlq2wn4q50q352annk3903tj00h45mggqvpjcf",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"invalid_base_address_too_long": {
|
||||
"address": "addr1q89s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqqj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfm5zhnjqfc",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"invalid_pointer_address_too_short": {
|
||||
"address": "addr1g89s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcgrfjd3l",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"invalid_pointer_address_too_long": {
|
||||
"address": "addr1g89s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqysszqgqqysszqgqqysszqgqqzpqv0wa7",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"invalid_enterprise_address_too_short": {
|
||||
"address": "addr1v89s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcg0c7m2w",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"invalid_enterprise_address_too_long": {
|
||||
"address": "addr1v89s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqzp9v4srv",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"large_simple_byron_output": {
|
||||
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
|
||||
"amount": "449999999199999999",
|
||||
},
|
||||
"testnet_output": {
|
||||
"address": "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
|
||||
"address": "2657WMsDfac7BteXkJq5Jzdog4h47fPbkwUM49isuWbYAr2cFRHa3rURP236h9PBe",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"shelley_testnet_output": {
|
||||
"address": "addr_test1vr9s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqqtmut0e",
|
||||
"amount": "1",
|
||||
},
|
||||
}
|
||||
|
||||
VALID_VECTORS = [
|
||||
@ -59,14 +139,18 @@ VALID_VECTORS = [
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_output"]],
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[[InputAction.SWIPE, InputAction.YES], [InputAction.SWIPE, InputAction.YES]],
|
||||
# tx hash
|
||||
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
|
||||
# serialized tx
|
||||
@ -74,37 +158,166 @@ VALID_VECTORS = [
|
||||
),
|
||||
# Mainnet transaction with change
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]],
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"], SAMPLE_OUTPUTS["byron_change_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43",
|
||||
# serialized tx
|
||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840d909b16038c4fd772a177038242e6793be39c735430b03ee924ed18026bd28d06920b5846247945f1204276e4b759aa5ac05a4a73b49ce705ab0e5e54a3a170e582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6",
|
||||
),
|
||||
# Testnet transaction
|
||||
# simple transaction with base address change output
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]],
|
||||
[
|
||||
SAMPLE_OUTPUTS["simple_shelley_output"],
|
||||
SAMPLE_OUTPUTS["base_address_change_output"],
|
||||
],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[[InputAction.SWIPE, InputAction.YES], [InputAction.SWIPE, InputAction.YES]],
|
||||
# tx hash
|
||||
"5dd03fb44cb88061b2a1c246981bb31adfe4f57be69b58badb5ae8f448450932",
|
||||
"16fe72bb198be423677577e6326f1f648ec5fc11263b072006382d8125a6edda",
|
||||
# tx body
|
||||
"83a400818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff018258390180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b42771a006ca79302182a030aa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c158406a78f07836dcf4a303448d2b16b217265a9226be3984a69a04dba5d04f4dbb2a47b5e1cbb345f474c0b9634a2f37b921ab26e6a65d5dfd015dacb4455fb8430af6",
|
||||
),
|
||||
# simple transaction with base address change output with staking key hash
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[
|
||||
SAMPLE_OUTPUTS["simple_shelley_output"],
|
||||
SAMPLE_OUTPUTS["staking_key_hash_output"],
|
||||
],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"d1610bb89bece22ed3158738bc1fbb31c6af0685053e2993361e3380f49afad9",
|
||||
# tx body
|
||||
"83a400818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff018258390180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa32c728d3861e164cab28cb8f006448139c8f1740ffb8e7aa9e5232dc1a006ca79302182a030aa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c15840622f22d03bc9651ddc5eb2f5dc709ac4240a64d2b78c70355dd62106543c407d56e8134c4df7884ba67c8a1b5c706fc021df5c4d0ff37385c30572e73c727d00f6",
|
||||
),
|
||||
# simple transaction with pointer address change output
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[
|
||||
SAMPLE_OUTPUTS["simple_shelley_output"],
|
||||
SAMPLE_OUTPUTS["pointer_address_output"],
|
||||
],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"40535fa8f88515f1da008d3cdf544cf9dbf1675c3cb0adb13b74b9293f1b7096",
|
||||
# tx body
|
||||
"83a400818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff018258204180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa0102031a006ca79302182a030aa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c15840dbbf050cc13d0696b1884113613318a275e6f0f8c7cb3e7828c4f2f3c158b2622a5d65ea247f1eed758a0f6242a52060c319d6f37c8460f5d14be24456cd0b08f6",
|
||||
),
|
||||
# simple transaction with enterprise address change output
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[
|
||||
SAMPLE_OUTPUTS["simple_shelley_output"],
|
||||
SAMPLE_OUTPUTS["enterprise_address_output"],
|
||||
],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"d3570557b197604109481a80aeb66cd2cfabc57f802ad593bacc12eb658e5d72",
|
||||
# tx body
|
||||
"83a400818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018282583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff0182581d6180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa1a006ca79302182a030aa100818258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c15840c5996650c438c4493b2c8a94229621bb9b151b8d61d75fb868c305e917031e9a1654f35023f7dbf5d1839ab9d57b153c7f79c2666af51ecf363780397956e00af6",
|
||||
),
|
||||
# Testnet transaction
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
# network id
|
||||
NETWORK_IDS["testnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[
|
||||
SAMPLE_OUTPUTS["testnet_output"],
|
||||
SAMPLE_OUTPUTS["shelley_testnet_output"],
|
||||
SAMPLE_OUTPUTS["byron_change_output"],
|
||||
],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"47cf79f20c6c62edb4162b3b232a57afc1bd0b57c7fd8389555276408a004776",
|
||||
# serialized tx
|
||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840fc30afdd0d4a6d8581e0f6abe895994d208fd382f2b23ff1553d711477a4fedbd1f68a76e7465c4816d5477f4287f7360acf71fca3b3d5902e4448e48c447106582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6",
|
||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018382582f82d818582583581cc817d85b524e3d073795819a25cdbb84cff6aa2bbb3a081980d248cba10242182a001a0fb6fc611a002dd2e882581d60cb03849e268f989b5a843107bad7fa2908246986a8f3d643f8c184800182582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840cc11adf81cb3c3b75a438325f8577666f5cbb4d5d6b73fa6dbbcf5ab36897df34eecacdb54c3bc3ce7fc594ebb2c7aa4db4700f4290facad9b611a035af8710a582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6",
|
||||
),
|
||||
]
|
||||
|
||||
@ -113,8 +326,10 @@ INVALID_VECTORS = [
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_address"]],
|
||||
# fee
|
||||
@ -126,10 +341,12 @@ INVALID_VECTORS = [
|
||||
),
|
||||
# Output address is invalid CBOR
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_cbor"]],
|
||||
# fee
|
||||
@ -143,8 +360,10 @@ INVALID_VECTORS = [
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_crc"]],
|
||||
# fee
|
||||
@ -154,14 +373,118 @@ INVALID_VECTORS = [
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Fee is too high
|
||||
# Output base address is too short
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_output"]],
|
||||
[SAMPLE_OUTPUTS["invalid_base_address_too_short"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Output base address is too long
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_base_address_too_long"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Output pointer address is too short
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_pointer_address_too_short"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Output pointer address is too long
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_pointer_address_too_long"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Output enterprise address is too short
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_enterprise_address_too_short"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Output enterprise address is too long
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["invalid_enterprise_address_too_long"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Fee is too high
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||
# fee
|
||||
45000000000000001,
|
||||
# ttl
|
||||
@ -171,12 +494,17 @@ INVALID_VECTORS = [
|
||||
),
|
||||
# Output total is too high
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["large_simple_output"], SAMPLE_OUTPUTS["change_output"]],
|
||||
[
|
||||
SAMPLE_OUTPUTS["large_simple_byron_output"],
|
||||
SAMPLE_OUTPUTS["byron_change_output"],
|
||||
],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
@ -188,8 +516,10 @@ INVALID_VECTORS = [
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["testnet_output"]],
|
||||
# fee
|
||||
@ -203,10 +533,12 @@ INVALID_VECTORS = [
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
# network id
|
||||
NETWORK_IDS["testnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_output"]],
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
@ -214,6 +546,74 @@ INVALID_VECTORS = [
|
||||
# error message
|
||||
"Output address network mismatch!",
|
||||
),
|
||||
# Shelley mainnet transaction with testnet output
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["shelley_testnet_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Shelley testnet transaction with mainnet output
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
# network id
|
||||
NETWORK_IDS["testnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_shelley_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid address",
|
||||
),
|
||||
# Testnet protocol magic with mainnet network id
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_shelley_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid network id/protocol magic combination!",
|
||||
),
|
||||
# Mainnet protocol magic with testnet network id
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["testnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUTS["shelley_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# error message
|
||||
"Invalid network id/protocol magic combination!",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -221,32 +621,47 @@ INVALID_VECTORS = [
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"protocol_magic,inputs,outputs,fee,ttl,tx_hash,serialized_tx", VALID_VECTORS
|
||||
"protocol_magic,network_id,inputs,outputs,fee,ttl,input_flow_sequences,tx_hash,serialized_tx",
|
||||
VALID_VECTORS,
|
||||
)
|
||||
def test_cardano_sign_tx(
|
||||
client, protocol_magic, inputs, outputs, fee, ttl, tx_hash, serialized_tx
|
||||
client,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
inputs,
|
||||
outputs,
|
||||
fee,
|
||||
ttl,
|
||||
input_flow_sequences,
|
||||
tx_hash,
|
||||
serialized_tx,
|
||||
):
|
||||
inputs = [cardano.create_input(i) for i in inputs]
|
||||
outputs = [cardano.create_output(o) for o in outputs]
|
||||
|
||||
expected_responses = [
|
||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
||||
messages.CardanoSignedTx(),
|
||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||
for i in range(len(input_flow_sequences))
|
||||
]
|
||||
expected_responses.append(messages.CardanoSignedTx())
|
||||
|
||||
def input_flow():
|
||||
yield
|
||||
client.debug.swipe_up()
|
||||
client.debug.press_yes()
|
||||
yield
|
||||
client.debug.swipe_up()
|
||||
client.debug.press_yes()
|
||||
for sequence in input_flow_sequences:
|
||||
yield
|
||||
for action in sequence:
|
||||
if action == InputAction.SWIPE:
|
||||
client.debug.swipe_up()
|
||||
elif action == InputAction.YES:
|
||||
client.debug.press_yes()
|
||||
else:
|
||||
raise ValueError("Invalid input action")
|
||||
|
||||
with client:
|
||||
client.set_expected_responses(expected_responses)
|
||||
client.set_input_flow(input_flow)
|
||||
response = cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic)
|
||||
response = cardano.sign_tx(
|
||||
client, inputs, outputs, fee, ttl, protocol_magic, network_id
|
||||
)
|
||||
assert response.tx_hash.hex() == tx_hash
|
||||
assert response.serialized_tx.hex() == serialized_tx
|
||||
|
||||
@ -255,10 +670,18 @@ def test_cardano_sign_tx(
|
||||
@pytest.mark.cardano
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.parametrize(
|
||||
"protocol_magic,inputs,outputs,fee,ttl,expected_error_message", INVALID_VECTORS
|
||||
"protocol_magic,network_id,inputs,outputs,fee,ttl,expected_error_message",
|
||||
INVALID_VECTORS,
|
||||
)
|
||||
def test_cardano_sign_tx_validation(
|
||||
client, protocol_magic, inputs, outputs, fee, ttl, expected_error_message
|
||||
client,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
inputs,
|
||||
outputs,
|
||||
fee,
|
||||
ttl,
|
||||
expected_error_message,
|
||||
):
|
||||
inputs = [cardano.create_input(i) for i in inputs]
|
||||
outputs = [cardano.create_output(o) for o in outputs]
|
||||
@ -269,4 +692,6 @@ def test_cardano_sign_tx_validation(
|
||||
client.set_expected_responses(expected_responses)
|
||||
|
||||
with pytest.raises(TrezorFailure, match=expected_error_message):
|
||||
cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic)
|
||||
cardano.sign_tx(
|
||||
client, inputs, outputs, fee, ttl, protocol_magic, network_id
|
||||
)
|
||||
|
@ -17,41 +17,32 @@
|
||||
import pytest
|
||||
|
||||
from trezorlib import cardano, messages
|
||||
from trezorlib.cardano import PROTOCOL_MAGICS
|
||||
from trezorlib.cardano import NETWORK_IDS, PROTOCOL_MAGICS
|
||||
|
||||
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
||||
|
||||
SAMPLE_INPUT = {
|
||||
"path": "m/44'/1815'/0'/0/1",
|
||||
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
|
||||
"prev_index": 0,
|
||||
}
|
||||
|
||||
SAMPLE_OUTPUTS = {
|
||||
"simple_output": {
|
||||
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
|
||||
"amount": "3003112",
|
||||
},
|
||||
"change_output": {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"},
|
||||
"testnet_output": {
|
||||
"address": "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
|
||||
"amount": "3003112",
|
||||
},
|
||||
}
|
||||
from .test_msg_cardano_sign_transaction import (
|
||||
SAMPLE_INPUTS,
|
||||
SAMPLE_OUTPUTS,
|
||||
InputAction,
|
||||
)
|
||||
|
||||
VALID_VECTORS = [
|
||||
# Mainnet transaction without change
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_output"]],
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[[InputAction.SWIPE, InputAction.YES], [InputAction.SWIPE, InputAction.YES]],
|
||||
# tx hash
|
||||
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
|
||||
# serialized tx
|
||||
@ -61,14 +52,22 @@ VALID_VECTORS = [
|
||||
(
|
||||
# protocol magic (mainnet)
|
||||
PROTOCOL_MAGICS["mainnet"],
|
||||
# network id
|
||||
NETWORK_IDS["mainnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]],
|
||||
[SAMPLE_OUTPUTS["simple_byron_output"], SAMPLE_OUTPUTS["byron_change_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae",
|
||||
# serialized tx
|
||||
@ -78,18 +77,26 @@ VALID_VECTORS = [
|
||||
(
|
||||
# protocol magic
|
||||
PROTOCOL_MAGICS["testnet"],
|
||||
# network id
|
||||
NETWORK_IDS["testnet"],
|
||||
# inputs
|
||||
[SAMPLE_INPUT],
|
||||
[SAMPLE_INPUTS["byron_input"]],
|
||||
# outputs
|
||||
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]],
|
||||
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["byron_change_output"]],
|
||||
# fee
|
||||
42,
|
||||
# ttl
|
||||
10,
|
||||
# input flow
|
||||
[
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
[InputAction.YES],
|
||||
[InputAction.SWIPE, InputAction.YES],
|
||||
],
|
||||
# tx hash
|
||||
"ac7ef9e4f51ed4d6b791cee111b240dae2f00c39c5cc1a150631eba8aa955528",
|
||||
"93a2c3cfb67ef1e4bae167b0f443c3370664bdb9171bc9cd41bad98e5cc049b2",
|
||||
# serialized tx
|
||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c709bfb5d9733cbdd72f520cd2c8b9f8f942da5e6cd0b6994e1803b0aa10242182a001aef14e76d1a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840cfd68676454ad8bed8575dcb8ee91824c0f836da4f07a54112088b12c6b89be0c8f729d4e3fb1df0de10f049a66dea372f3e2888cabb6110d538a0e9a06fbb0758206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b45a10242182af6",
|
||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581cc817d85b524e3d073795819a25cdbb84cff6aa2bbb3a081980d248cba10242182a001a0fb6fc611a002dd2e882582f82d818582583581c709bfb5d9733cbdd72f520cd2c8b9f8f942da5e6cd0b6994e1803b0aa10242182a001aef14e76d1a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840552d1d66972598532fa539faa98cdc7889c8dce00577626a62fb22d0e244d9f49732b6ab65593352a7486123077b7e36308c5048cc8ee6dc465e576f065cb70558206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b45a10242182af6",
|
||||
),
|
||||
]
|
||||
|
||||
@ -99,33 +106,48 @@ VALID_VECTORS = [
|
||||
@pytest.mark.skip_t1 # T1 support is not planned
|
||||
@pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True)
|
||||
@pytest.mark.parametrize(
|
||||
"protocol_magic,inputs,outputs,fee,ttl,tx_hash,serialized_tx", VALID_VECTORS
|
||||
"protocol_magic,network_id,inputs,outputs,fee,ttl,input_flow_sequences,tx_hash,serialized_tx",
|
||||
VALID_VECTORS,
|
||||
)
|
||||
def test_cardano_sign_tx(
|
||||
client, protocol_magic, inputs, outputs, fee, ttl, tx_hash, serialized_tx
|
||||
client,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
inputs,
|
||||
outputs,
|
||||
fee,
|
||||
ttl,
|
||||
input_flow_sequences,
|
||||
tx_hash,
|
||||
serialized_tx,
|
||||
):
|
||||
inputs = [cardano.create_input(i) for i in inputs]
|
||||
outputs = [cardano.create_output(o) for o in outputs]
|
||||
|
||||
expected_responses = [
|
||||
messages.PassphraseRequest(),
|
||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
||||
messages.CardanoSignedTx(),
|
||||
expected_responses = [messages.PassphraseRequest()]
|
||||
expected_responses += [
|
||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||
for i in range(len(input_flow_sequences))
|
||||
]
|
||||
expected_responses.append(messages.CardanoSignedTx())
|
||||
|
||||
def input_flow():
|
||||
yield
|
||||
client.debug.swipe_up()
|
||||
client.debug.press_yes()
|
||||
yield
|
||||
client.debug.swipe_up()
|
||||
client.debug.press_yes()
|
||||
for sequence in input_flow_sequences:
|
||||
yield
|
||||
for action in sequence:
|
||||
if action == InputAction.SWIPE:
|
||||
client.debug.swipe_up()
|
||||
elif action == InputAction.YES:
|
||||
client.debug.press_yes()
|
||||
else:
|
||||
raise ValueError("Invalid input action")
|
||||
|
||||
client.use_passphrase("TREZOR")
|
||||
with client:
|
||||
client.set_expected_responses(expected_responses)
|
||||
client.set_input_flow(input_flow)
|
||||
response = cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic)
|
||||
response = cardano.sign_tx(
|
||||
client, inputs, outputs, fee, ttl, protocol_magic, network_id
|
||||
)
|
||||
assert response.tx_hash.hex() == tx_hash
|
||||
assert response.serialized_tx.hex() == serialized_tx
|
||||
|
@ -27,29 +27,54 @@
|
||||
"test_msg_binance_sign_tx.py::test_binance_sign_message[message0-expected_response0]": "d41ee5e01a50f0f96fd7881db1750fab31cfe62c25b4eabbc092cc3daa039c7f",
|
||||
"test_msg_binance_sign_tx.py::test_binance_sign_message[message1-expected_response1]": "7b8bbe5ba7d7b07c95065608fb1cf9aeafcb3f9671835a6e5e5a6997ff4ff99b",
|
||||
"test_msg_binance_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "813ad1b802dee1ace4dfa378edd840dbcea57c1a1b8eed67134def024c40a6e9",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-42-2657WMsDfac5vydkak9a": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-764824073-Ae2tdPwUPEZLC": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-42-2657WMsDfac61ebUDw53": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-764824073-Ae2tdPwUPEZEY": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-42-2657WMsDfac5PMpEsxc1": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-764824073-Ae2tdPwUPEZ3g": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-42-2657WMsDfac5F3zbgs9B": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-764824073-Ae2tdPwUPEZ5Y": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-42-2657WMsDfac6ezKWszxL": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-764824073-Ae2tdPwUPEZJb": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-42-2657WMsDfac7hr1ioJGr": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-764824073-Ae2tdPwUPEZFm": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_base_address[m-1852'-1815'-4'-0-0-m-1852'-1815'-4'": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_base_address_with_staking_key_hash[m-1852'-1815'-0": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_base_address_with_staking_key_hash[m-1852'-1815'-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_enterprise_address[m-1852'-1815'-0'-0-0-0-addr_tes": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_enterprise_address[m-1852'-1815'-0'-0-0-1-addr1vxq": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_pointer_address[m-1852'-1815'-0'-0-0-1-2-3-1-addr1": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_pointer_address[m-1852'-1815'-0'-0-0-24157-177-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_reward_address[m-1852'-1815'-0'-2-0-0-stake_test1u": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_address.py::test_cardano_get_reward_address[m-1852'-1815'-0'-2-0-1-stake1uyfz49": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-1852'-1815'-0'-d507c8f866691bd96e1": "ea2bf594e6a6cabd44dbc8c994dc455ea1f81eae844fa9df8833afdf46cda517",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-1852'-1815'-1'-140791584001446365f": "231bd3a5ccc02a4c27dd36c181c1338f1ef4c0ce19a558528fa53efae453ac25",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-1852'-1815'-2'-ff6ccc3097ca79fc29f": "f26abf1a1a855bda4a05816f12acf485ff1109f5b97d29e505ede199cf6e2e35",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-1852'-1815'-3'-be81ace1f63f4f0cae7": "ec69415277b55f4d53d2fa3c91ece0bef6d766474dd2f13ade3683e6e88f27e5",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-0'-c0fce1839f1a84c4e7702": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-1'-ea5dde31b9f551e08a5b6": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-2'-076338cee5ab3dae19f06": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-3'-5f769380dc6fd17a4e0f2": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-5dd03fb44cb8806": "418f782a5d37c227f2d82f144bcee661d65187e07a421f64e6a5465daf544e7d",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e09bde": "63dc0880175285dc1b3dcadb3ae66439f28b1c18fdaf50245cd84b6c8cb3d0e0",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-42-10-81b14b7e": "63dc0880175285dc1b3dcadb3ae66439f28b1c18fdaf50245cd84b6c8cb3d0e0",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[42-inputs6-outputs6-42-10-Outp": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs0-outputs0-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs1-outputs1-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs2-outputs2-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs3-outputs3-450": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs4-outputs4-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs5-outputs5-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-ac7ef9e4f51": "ba354313a87bff3e5079da58c74cdc265a9be8e9a80006e76ea68d0cdb869665",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e0": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-42-10-4c43": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[42-0-inputs6-outputs6-42-10-input_flow_se": "ae26b018ccceacd0c78de3389a673eca60753a6714f3146296ee17072417ec57",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-1-inputs0-outputs0-42-10-input_": "fe89b315aeb25a4a449f0cadccdafcf58669d247101b038823aad25e4e7ed670",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-1-inputs1-outputs1-42-10-input_": "900bd30689093a89966b56fa2663f260b0b2d4c0ba74dc4fb06cb0f45f5b3b4d",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-1-inputs2-outputs2-42-10-input_": "7a35fb89f41b8730c1df37ab1fc63ee8aa4ba3b14282c68b1b86baf81665ff9c",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-1-inputs3-outputs3-42-10-input_": "09faf9d0ad559154f74ac9cb3962411be07f81c5f310b23249211af1a6d4e1b2",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-1-inputs4-outputs4-42-10-input_": "3141c25aef3c98eed72a91833b5b55b2a4e3593bb8e79562765eb82321e99685",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-1-inputs5-outputs5-42-10-input_": "5a2a0a944774c4cbe6721fb06d41117da09394a9198c465db74dd81f037c701d",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[42-0-inputs12-outputs12-42-10-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[42-0-inputs14-outputs14-42-10-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[42-1-inputs15-outputs15-42-10-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-0-inputs16-outputs16": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs0-outputs0-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs1-outputs1-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs10-outputs10": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs11-outputs11": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs13-outputs13": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs2-outputs2-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs3-outputs3-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs4-outputs4-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs5-outputs5-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs6-outputs6-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs7-outputs7-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs8-outputs8-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs9-outputs9-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[42-0-inputs2-outputs2-42-10-input_flo": "d488a0f2c127a675a1c2e2d410b6c4f402cdb610e19886a8998aa5ad786a779e",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-1-inputs0-outputs0-42-10-in": "6aa71de5007b0faf1eea4b1cfda1da6a739f852c0d875a1e59d83c03178c2f98",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-1-inputs1-outputs1-42-10-in": "7abf2e87a9b1e50afdf3502ba9480b07a59d59ccccf24915b46fb81285ae3fa8",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "9e11b251c03ef09127da79d92f8483c4db438c7303328774790d45e3f6fb8c96",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "d280ed129a2ea4781af9e35542aa31ecf63da75fc6812ed3bd05107809f836a4",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "32c808f00bada2059f933f3515337e494c837bdf65e4ea918b457d1c9f4cb42a",
|
||||
|
Loading…
Reference in New Issue
Block a user