mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-11 16:00:57 +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";
|
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
|
* Request: Ask device for Cardano address
|
||||||
* @start
|
* @start
|
||||||
@ -14,9 +54,11 @@ import "messages-common.proto";
|
|||||||
* @next Failure
|
* @next Failure
|
||||||
*/
|
*/
|
||||||
message CardanoGetAddress {
|
message CardanoGetAddress {
|
||||||
repeated uint32 address_n = 1; // BIP-32-style path to derive the key from master node
|
// repeated uint32 address_n = 1; // moved to address_parameters
|
||||||
optional bool show_display = 2; // optionally prompt for confirmation on trezor display
|
optional bool show_display = 2; // optionally prompt for confirmation on trezor display
|
||||||
optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets
|
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 uint32 protocol_magic = 5; // network's protocol magic
|
||||||
optional uint64 fee = 6; // transaction fee - added in shelley
|
optional uint64 fee = 6; // transaction fee - added in shelley
|
||||||
optional uint64 ttl = 7; // transaction ttl - added in shelley
|
optional uint64 ttl = 7; // transaction ttl - added in shelley
|
||||||
|
optional uint32 network_id = 8; // network id - mainnet or testnet
|
||||||
/**
|
/**
|
||||||
* Structure representing cardano transaction input
|
* Structure representing cardano transaction input
|
||||||
*/
|
*/
|
||||||
@ -74,9 +117,10 @@ message CardanoSignTx {
|
|||||||
* Structure representing cardano transaction output
|
* Structure representing cardano transaction output
|
||||||
*/
|
*/
|
||||||
message CardanoTxOutputType {
|
message CardanoTxOutputType {
|
||||||
optional string address = 1; // target coin address in Base58 encoding
|
optional string address = 1; // target coin address in bech32 or base58
|
||||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address"
|
// repeated uint32 address_n = 2; // moved to address_parameters
|
||||||
optional uint64 amount = 3; // amount to spend
|
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 import wire
|
||||||
from trezor.messages import MessageType
|
from trezor.messages import MessageType
|
||||||
|
|
||||||
from apps.common import HARDENED
|
|
||||||
|
|
||||||
CURVE = "ed25519"
|
CURVE = "ed25519"
|
||||||
SEED_NAMESPACE = [HARDENED | 44, HARDENED | 1815]
|
|
||||||
|
|
||||||
|
|
||||||
def boot() -> None:
|
def boot() -> None:
|
||||||
|
@ -1,145 +1,352 @@
|
|||||||
from trezor import log, wire
|
from micropython import const
|
||||||
from trezor.crypto import base58, crc, hashlib
|
|
||||||
|
|
||||||
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 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:
|
if False:
|
||||||
from typing import Tuple
|
from typing import List
|
||||||
from trezor.crypto import bip32
|
from trezor.messages import CardanoBlockchainPointerType
|
||||||
|
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
|
||||||
from . import seed
|
from . import seed
|
||||||
|
|
||||||
PROTOCOL_MAGIC_KEY = 2
|
ADDRESS_TYPES_SHELLEY = (
|
||||||
INVALID_ADDRESS = wire.ProcessError("Invalid address")
|
CardanoAddressType.BASE,
|
||||||
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
|
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:
|
def validate_full_path(path: List[int]) -> bool:
|
||||||
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:
|
|
||||||
"""
|
"""
|
||||||
Determines whether the correct protocol magic (or none)
|
Validates derivation path to fit {44', 1852'}/1815'/a'/{0,1,2}/i,
|
||||||
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,
|
|
||||||
where `a` is an account number and i an address index.
|
where `a` is an account number and i an address index.
|
||||||
The max value for `a` is 20, 1 000 000 for `i`.
|
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:
|
if len(path) != 5:
|
||||||
return False
|
return False
|
||||||
if path[0] != 44 | HARDENED:
|
if path[0] not in (purposes.BYRON, purposes.SHELLEY):
|
||||||
return False
|
return False
|
||||||
if path[1] != 1815 | HARDENED:
|
if path[1] != 1815 | HARDENED:
|
||||||
return False
|
return False
|
||||||
if path[2] < HARDENED or path[2] > 20 | HARDENED:
|
if path[2] < HARDENED or path[2] > 20 | HARDENED:
|
||||||
return False
|
return False
|
||||||
if path[3] != 0 and path[3] != 1:
|
if path[3] not in (0, 1, 2):
|
||||||
return False
|
return False
|
||||||
if path[4] > 1000000:
|
if path[4] > 1000000:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _address_hash(data: list) -> bytes:
|
def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None:
|
||||||
cbor_data = cbor.encode(data)
|
if address is None or len(address) == 0:
|
||||||
sha_data_hash = hashlib.sha3_256(cbor_data).digest()
|
raise INVALID_ADDRESS
|
||||||
res = hashlib.blake2b(data=sha_data_hash, outlen=28).digest()
|
|
||||||
return res
|
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:
|
def get_address_bytes_unsafe(address: str) -> bytes:
|
||||||
extpubkey = remove_ed25519_prefix(node.public_key()) + node.chain_code()
|
try:
|
||||||
return _address_hash([0, [0, extpubkey], address_attributes])
|
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 trezor.messages.CardanoAddress import CardanoAddress
|
||||||
|
|
||||||
from apps.common import paths
|
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 . 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
|
@seed.with_keychain
|
||||||
async def get_address(ctx, msg, keychain: seed.Keychain):
|
async def get_address(
|
||||||
await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE)
|
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:
|
try:
|
||||||
address, _ = derive_address_and_node(
|
address = derive_human_readable_address(
|
||||||
keychain, msg.address_n, msg.protocol_magic
|
keychain, address_parameters, msg.protocol_magic, msg.network_id
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
if __debug__:
|
if __debug__:
|
||||||
log.exception(__name__, e)
|
log.exception(__name__, e)
|
||||||
raise wire.ProcessError("Deriving address failed")
|
raise wire.ProcessError("Deriving address failed")
|
||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
desc = address_n_to_str(msg.address_n)
|
await _display_address(
|
||||||
while True:
|
ctx, keychain, address_parameters, address, msg.protocol_magic
|
||||||
if await show_address(ctx, address, desc=desc):
|
)
|
||||||
break
|
|
||||||
if await show_qr(ctx, address, desc=desc):
|
|
||||||
break
|
|
||||||
|
|
||||||
return CardanoAddress(address=address)
|
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:
|
def to_ui_string(value: int) -> str:
|
||||||
return NAMES.get(value, "Unknown")
|
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 import mnemonic
|
||||||
from apps.common.passphrase import get as get_passphrase
|
from apps.common.passphrase import get as get_passphrase
|
||||||
|
|
||||||
from . import SEED_NAMESPACE
|
from .helpers import seed_namespaces
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from apps.common.paths import Bip32Path
|
from apps.common.paths import Bip32Path
|
||||||
@ -13,19 +13,42 @@ if False:
|
|||||||
|
|
||||||
|
|
||||||
class Keychain:
|
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:
|
def __init__(self, root: bip32.HDNode) -> None:
|
||||||
self.root = root
|
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:
|
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")
|
raise wire.DataError("Forbidden key path")
|
||||||
|
|
||||||
def derive(self, node_path: Bip32Path) -> bip32.HDNode:
|
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
|
# derive child node from the root
|
||||||
node = self.root.clone()
|
node = path_root.clone()
|
||||||
suffix = node_path[len(SEED_NAMESPACE) :]
|
|
||||||
for i in suffix:
|
for i in suffix:
|
||||||
node.derive_cardano(i)
|
node.derive_cardano(i)
|
||||||
return node
|
return node
|
||||||
@ -35,6 +58,14 @@ class Keychain:
|
|||||||
# self.root.__del__()
|
# 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)
|
@cache.stored_async(cache.APP_CARDANO_ROOT)
|
||||||
async def get_keychain(ctx: wire.Context) -> Keychain:
|
async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||||
if not device.is_initialized():
|
if not device.is_initialized():
|
||||||
@ -49,10 +80,6 @@ async def get_keychain(ctx: wire.Context) -> Keychain:
|
|||||||
seed = mnemonic.get_seed(passphrase)
|
seed = mnemonic.get_seed(passphrase)
|
||||||
root = bip32.from_seed(seed, "ed25519 cardano seed")
|
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)
|
keychain = Keychain(root)
|
||||||
return keychain
|
return keychain
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
from trezor import log, wire
|
from trezor import log, wire
|
||||||
from trezor.crypto import base58, hashlib
|
from trezor.crypto import hashlib
|
||||||
from trezor.crypto.curve import ed25519
|
from trezor.crypto.curve import ed25519
|
||||||
|
from trezor.messages import CardanoAddressParametersType
|
||||||
from trezor.messages.CardanoSignedTx import CardanoSignedTx
|
from trezor.messages.CardanoSignedTx import CardanoSignedTx
|
||||||
|
|
||||||
from apps.common import cbor
|
from apps.common import cbor
|
||||||
@ -11,12 +12,24 @@ from apps.common.seed import remove_ed25519_prefix
|
|||||||
|
|
||||||
from . import CURVE, seed
|
from . import CURVE, seed
|
||||||
from .address import (
|
from .address import (
|
||||||
derive_address_and_node,
|
derive_address_bytes,
|
||||||
get_address_attributes,
|
derive_human_readable_address,
|
||||||
|
get_address_bytes_unsafe,
|
||||||
|
to_account_path,
|
||||||
validate_full_path,
|
validate_full_path,
|
||||||
validate_output_address,
|
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:
|
if False:
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
@ -41,10 +54,12 @@ async def sign_tx(
|
|||||||
if msg.fee > LOVELACE_MAX_SUPPLY:
|
if msg.fee > LOVELACE_MAX_SUPPLY:
|
||||||
raise wire.ProcessError("Fee is out of range!")
|
raise wire.ProcessError("Fee is out of range!")
|
||||||
|
|
||||||
|
_validate_network_info(msg.network_id, msg.protocol_magic)
|
||||||
|
|
||||||
for i in msg.inputs:
|
for i in msg.inputs:
|
||||||
await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE)
|
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
|
# display the transaction in UI
|
||||||
await _show_tx(ctx, keychain, msg)
|
await _show_tx(ctx, keychain, msg)
|
||||||
@ -61,18 +76,42 @@ async def sign_tx(
|
|||||||
return 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(
|
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:
|
) -> None:
|
||||||
|
if not outputs:
|
||||||
|
raise wire.ProcessError("Transaction has no outputs!")
|
||||||
|
|
||||||
total_amount = 0
|
total_amount = 0
|
||||||
for output in outputs:
|
for output in outputs:
|
||||||
total_amount += output.amount
|
total_amount += output.amount
|
||||||
if output.address_n:
|
if output.address_parameters:
|
||||||
continue
|
# 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:
|
elif output.address is not None:
|
||||||
validate_output_address(output.address, protocol_magic)
|
validate_output_address(output.address, protocol_magic, network_id)
|
||||||
else:
|
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:
|
if total_amount > LOVELACE_MAX_SUPPLY:
|
||||||
raise wire.ProcessError("Total transaction amount is out of range!")
|
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_body = _build_tx_body(keychain, msg)
|
||||||
tx_hash = _hash_tx_body(tx_body)
|
tx_hash = _hash_tx_body(tx_body)
|
||||||
|
|
||||||
witnesses_for_cbor = _build_witnesses(
|
witnesses = _build_witnesses(keychain, msg.inputs, tx_hash, msg.protocol_magic)
|
||||||
keychain, msg.inputs, tx_hash, msg.protocol_magic
|
|
||||||
)
|
|
||||||
# byron witnesses have the key 2 in Shelley
|
|
||||||
witnesses = {2: witnesses_for_cbor}
|
|
||||||
|
|
||||||
serialized_tx = cbor.encode([tx_body, witnesses, None])
|
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:
|
def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
|
||||||
inputs_for_cbor = _build_inputs(msg.inputs)
|
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 = {
|
tx_body = {
|
||||||
0: inputs_for_cbor,
|
0: inputs_for_cbor,
|
||||||
@ -112,19 +149,23 @@ def _build_inputs(inputs: List[CardanoTxInputType]) -> List[Tuple[bytes, int]]:
|
|||||||
|
|
||||||
|
|
||||||
def _build_outputs(
|
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]]:
|
) -> List[Tuple[bytes, int]]:
|
||||||
result = []
|
result = []
|
||||||
for output in outputs:
|
for output in outputs:
|
||||||
amount = output.amount
|
amount = output.amount
|
||||||
if output.address_n:
|
if output.address_parameters:
|
||||||
address, _ = derive_address_and_node(
|
address = derive_address_bytes(
|
||||||
keychain, output.address_n, protocol_magic
|
keychain, output.address_parameters, protocol_magic, network_id
|
||||||
)
|
)
|
||||||
else:
|
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
|
return result
|
||||||
|
|
||||||
@ -139,9 +180,53 @@ def _build_witnesses(
|
|||||||
inputs: List[CardanoTxInputType],
|
inputs: List[CardanoTxInputType],
|
||||||
tx_body_hash: bytes,
|
tx_body_hash: bytes,
|
||||||
protocol_magic: int,
|
protocol_magic: int,
|
||||||
) -> List[Tuple[bytes, bytes, bytes, bytes]]:
|
) -> Dict:
|
||||||
result = []
|
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:
|
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)
|
node = keychain.derive(input.address_n)
|
||||||
|
|
||||||
public_key = remove_ed25519_prefix(node.public_key())
|
public_key = remove_ed25519_prefix(node.public_key())
|
||||||
@ -151,31 +236,69 @@ def _build_witnesses(
|
|||||||
chain_code = node.chain_code()
|
chain_code = node.chain_code()
|
||||||
address_attributes = cbor.encode(get_address_attributes(protocol_magic))
|
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(
|
async def _show_tx(
|
||||||
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
||||||
) -> None:
|
) -> 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
|
total_amount = 0
|
||||||
for output in msg.outputs:
|
for output in msg.outputs:
|
||||||
if _should_hide_output(output.address_n, msg.inputs):
|
if output.address_parameters:
|
||||||
continue
|
address = derive_human_readable_address(
|
||||||
|
keychain, output.address_parameters, msg.protocol_magic, msg.network_id
|
||||||
total_amount += output.amount
|
|
||||||
|
|
||||||
if not output.address:
|
|
||||||
address, _ = derive_address_and_node(
|
|
||||||
keychain, output.address_n, msg.protocol_magic
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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:
|
else:
|
||||||
address = output.address
|
address = output.address
|
||||||
|
|
||||||
|
total_amount += output.amount
|
||||||
|
|
||||||
await confirm_sending(ctx, output.amount, address)
|
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
|
# 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])
|
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."""
|
"""Validate a Bech32 string, and determine HRP and data."""
|
||||||
if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
|
if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
|
||||||
bech.lower() != bech and bech.upper() != bech
|
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)
|
return (None, None)
|
||||||
bech = bech.lower()
|
bech = bech.lower()
|
||||||
pos = bech.rfind("1")
|
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)
|
return (None, None)
|
||||||
if not all(x in CHARSET for x in bech[pos + 1 :]):
|
if not all(x in CHARSET for x in bech[pos + 1 :]):
|
||||||
return (None, None)
|
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
|
# fmt: off
|
||||||
import protobuf as p
|
import protobuf as p
|
||||||
|
|
||||||
|
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
@ -15,18 +17,21 @@ class CardanoGetAddress(p.MessageType):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
address_n: List[int] = None,
|
|
||||||
show_display: bool = None,
|
show_display: bool = None,
|
||||||
protocol_magic: int = None,
|
protocol_magic: int = None,
|
||||||
|
network_id: int = None,
|
||||||
|
address_parameters: CardanoAddressParametersType = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.address_n = address_n if address_n is not None else []
|
|
||||||
self.show_display = show_display
|
self.show_display = show_display
|
||||||
self.protocol_magic = protocol_magic
|
self.protocol_magic = protocol_magic
|
||||||
|
self.network_id = network_id
|
||||||
|
self.address_parameters = address_parameters
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
|
||||||
2: ('show_display', p.BoolType, 0),
|
2: ('show_display', p.BoolType, 0),
|
||||||
3: ('protocol_magic', p.UVarintType, 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,
|
protocol_magic: int = None,
|
||||||
fee: int = None,
|
fee: int = None,
|
||||||
ttl: int = None,
|
ttl: int = None,
|
||||||
|
network_id: int = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.inputs = inputs if inputs is not None else []
|
self.inputs = inputs if inputs is not None else []
|
||||||
self.outputs = outputs if outputs is not None else []
|
self.outputs = outputs if outputs is not None else []
|
||||||
self.protocol_magic = protocol_magic
|
self.protocol_magic = protocol_magic
|
||||||
self.fee = fee
|
self.fee = fee
|
||||||
self.ttl = ttl
|
self.ttl = ttl
|
||||||
|
self.network_id = network_id
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
@ -38,4 +40,5 @@ class CardanoSignTx(p.MessageType):
|
|||||||
5: ('protocol_magic', p.UVarintType, 0),
|
5: ('protocol_magic', p.UVarintType, 0),
|
||||||
6: ('fee', p.UVarintType, 0),
|
6: ('fee', p.UVarintType, 0),
|
||||||
7: ('ttl', p.UVarintType, 0),
|
7: ('ttl', p.UVarintType, 0),
|
||||||
|
8: ('network_id', p.UVarintType, 0),
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
# fmt: off
|
# fmt: off
|
||||||
import protobuf as p
|
import protobuf as p
|
||||||
|
|
||||||
|
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
@ -15,17 +17,17 @@ class CardanoTxOutputType(p.MessageType):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
address: str = None,
|
address: str = None,
|
||||||
address_n: List[int] = None,
|
|
||||||
amount: int = None,
|
amount: int = None,
|
||||||
|
address_parameters: CardanoAddressParametersType = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.address = address
|
self.address = address
|
||||||
self.address_n = address_n if address_n is not None else []
|
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
|
self.address_parameters = address_parameters
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('address', p.UnicodeType, 0),
|
1: ('address', p.UnicodeType, 0),
|
||||||
2: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
|
||||||
3: ('amount', p.UVarintType, 0),
|
3: ('amount', p.UVarintType, 0),
|
||||||
|
4: ('address_parameters', CardanoAddressParametersType, 0),
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
from common import *
|
from common import *
|
||||||
from apps.common import seed
|
from trezor import wire
|
||||||
|
|
||||||
from apps.common import HARDENED
|
|
||||||
from trezor.crypto import bip32, slip39
|
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:
|
if not utils.BITCOIN_ONLY:
|
||||||
from apps.cardano import protocol_magics
|
|
||||||
from apps.cardano.address import (
|
from apps.cardano.address import (
|
||||||
|
derive_human_readable_address,
|
||||||
|
validate_full_path,
|
||||||
|
)
|
||||||
|
from apps.cardano.byron_address import (
|
||||||
_get_address_root,
|
_get_address_root,
|
||||||
_address_hash,
|
_address_hash,
|
||||||
validate_full_path,
|
|
||||||
derive_address_and_node
|
|
||||||
)
|
)
|
||||||
|
from apps.cardano.helpers import network_ids, protocol_magics
|
||||||
from apps.cardano.seed import Keychain
|
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"
|
mnemonic = "all all all all all all all all all all all all"
|
||||||
passphrase = ""
|
passphrase = ""
|
||||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
addresses = [
|
addresses = [
|
||||||
@ -32,7 +36,11 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
|
|
||||||
for i, expected in enumerate(addresses):
|
for i, expected in enumerate(addresses):
|
||||||
# 44'/1815'/0'/0/i'
|
# 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)
|
self.assertEqual(expected, address)
|
||||||
|
|
||||||
nodes = [
|
nodes = [
|
||||||
@ -57,7 +65,7 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for i, (priv, ext, pub, chain) in enumerate(nodes):
|
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()), priv)
|
||||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
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"
|
mnemonic = "all all all all all all all all all all all all"
|
||||||
passphrase = ""
|
passphrase = ""
|
||||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
addresses = [
|
addresses = [
|
||||||
@ -79,7 +85,11 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
|
|
||||||
for i, expected in enumerate(addresses):
|
for i, expected in enumerate(addresses):
|
||||||
# 44'/1815'/0'/0/i
|
# 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)
|
self.assertEqual(address, expected)
|
||||||
|
|
||||||
nodes = [
|
nodes = [
|
||||||
@ -104,7 +114,7 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for i, (priv, ext, pub, chain) in enumerate(nodes):
|
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()), priv)
|
||||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
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"
|
mnemonic = "all all all all all all all all all all all all"
|
||||||
passphrase = ""
|
passphrase = ""
|
||||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
# 44'/1815'
|
# 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")
|
self.assertEqual(address, "Ae2tdPwUPEZ2FGHX3yCKPSbSgyuuTYgMxNq652zKopxT4TuWvEd8Utd92w3")
|
||||||
|
|
||||||
priv, ext, pub, chain = (
|
priv, ext, pub, chain = (
|
||||||
@ -130,7 +142,7 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
b"02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a"
|
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()), priv)
|
||||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
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)
|
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
||||||
|
|
||||||
# Check derived nodes and addresses.
|
# Check derived nodes and addresses.
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
nodes = [
|
nodes = [
|
||||||
@ -228,7 +238,12 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
|
|
||||||
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
|
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
|
||||||
# 44'/1815'/0'/0/i
|
# 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(a, address)
|
||||||
self.assertEqual(hexlify(n.private_key()), priv)
|
self.assertEqual(hexlify(n.private_key()), priv)
|
||||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||||
@ -262,8 +277,6 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
||||||
|
|
||||||
# Check derived nodes and addresses.
|
# Check derived nodes and addresses.
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
nodes = [
|
nodes = [
|
||||||
@ -292,19 +305,274 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
|
|
||||||
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
|
for i, (address, priv, ext, pub, chain) in enumerate(nodes):
|
||||||
# 44'/1815'/0'/0/i
|
# 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(a, address)
|
||||||
self.assertEqual(hexlify(n.private_key()), priv)
|
self.assertEqual(hexlify(n.private_key()), priv)
|
||||||
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
self.assertEqual(hexlify(n.private_key_ext()), ext)
|
||||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub)
|
||||||
self.assertEqual(hexlify(n.chain_code()), chain)
|
self.assertEqual(hexlify(n.chain_code()), chain)
|
||||||
|
|
||||||
def test_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"
|
mnemonic = "all all all all all all all all all all all all"
|
||||||
passphrase = ""
|
passphrase = ""
|
||||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
addresses = [
|
addresses = [
|
||||||
@ -315,7 +583,11 @@ class TestCardanoAddress(unittest.TestCase):
|
|||||||
|
|
||||||
for i, expected in enumerate(addresses):
|
for i, expected in enumerate(addresses):
|
||||||
# 44'/1815'/0'/0/i'
|
# 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)
|
self.assertEqual(expected, address)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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"
|
mnemonic = "all all all all all all all all all all all all"
|
||||||
passphrase = ""
|
passphrase = ""
|
||||||
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
derivation_paths = [
|
derivation_paths = [
|
||||||
@ -20,6 +18,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
|||||||
[0x80000000 | 44, 0x80000000 | 1815],
|
[0x80000000 | 44, 0x80000000 | 1815],
|
||||||
[0x80000000 | 44, 0x80000000 | 1815, 0, 0, 0],
|
[0x80000000 | 44, 0x80000000 | 1815, 0, 0, 0],
|
||||||
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 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 = [
|
public_keys = [
|
||||||
@ -27,6 +30,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
|||||||
b'8c47ebce34234d04fd3dfbac33feaba6133e4e3d77c4b5ab18120ec6878ad4ce',
|
b'8c47ebce34234d04fd3dfbac33feaba6133e4e3d77c4b5ab18120ec6878ad4ce',
|
||||||
b'17cc0bf978756d0d5c76f931629036a810c61801b78beecb44555773d13e3791',
|
b'17cc0bf978756d0d5c76f931629036a810c61801b78beecb44555773d13e3791',
|
||||||
b'b90fb812a2268e9569ff1172e8daed1da3dc7e72c7bded7c5bcb7282039f90d5',
|
b'b90fb812a2268e9569ff1172e8daed1da3dc7e72c7bded7c5bcb7282039f90d5',
|
||||||
|
|
||||||
|
b'f698a764b23aa6667b1157fc4247c6a1b58c21a3865ac6a47a3590167a9e0211',
|
||||||
|
b'e9c46841be76e3be0289694fd5c7503c04f40e5b036abac200b98a9006cf6647',
|
||||||
|
b'6d225f078ca611f00d86cbfd8ba6c6ac7826721434eae6525686efb878b72370',
|
||||||
|
b'5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1',
|
||||||
]
|
]
|
||||||
|
|
||||||
chain_codes = [
|
chain_codes = [
|
||||||
@ -34,6 +42,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
|||||||
b'02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a',
|
b'02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a',
|
||||||
b'646ac4a6295326bae6831be05921edfbcb362de48dfd37b12e74c227dfad768d',
|
b'646ac4a6295326bae6831be05921edfbcb362de48dfd37b12e74c227dfad768d',
|
||||||
b'fd8e71c1543de2cdc7f7623130c5f2cceb53549055fa1f5bc88199989e08cce7',
|
b'fd8e71c1543de2cdc7f7623130c5f2cceb53549055fa1f5bc88199989e08cce7',
|
||||||
|
|
||||||
|
b'13cfb6de37a568aae56cadac907e6469b121464fe1b70a10c213eaea2cbb6636',
|
||||||
|
b'58f3f46f4a93e7a4431e75b10af7497b747c3053cb7466ed53f4277e78a63c52',
|
||||||
|
b'f72b3c361381db2d88289440268c94c5e7467c9414375e6b63d03026750f3c66',
|
||||||
|
b'f123474e140a2c360b01f0fa66f2f22e2e965a5b07a80358cf75f77abbd66088',
|
||||||
]
|
]
|
||||||
|
|
||||||
xpub_keys = [
|
xpub_keys = [
|
||||||
@ -41,6 +54,11 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
|||||||
'8c47ebce34234d04fd3dfbac33feaba6133e4e3d77c4b5ab18120ec6878ad4ce02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a',
|
'8c47ebce34234d04fd3dfbac33feaba6133e4e3d77c4b5ab18120ec6878ad4ce02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a',
|
||||||
'17cc0bf978756d0d5c76f931629036a810c61801b78beecb44555773d13e3791646ac4a6295326bae6831be05921edfbcb362de48dfd37b12e74c227dfad768d',
|
'17cc0bf978756d0d5c76f931629036a810c61801b78beecb44555773d13e3791646ac4a6295326bae6831be05921edfbcb362de48dfd37b12e74c227dfad768d',
|
||||||
'b90fb812a2268e9569ff1172e8daed1da3dc7e72c7bded7c5bcb7282039f90d5fd8e71c1543de2cdc7f7623130c5f2cceb53549055fa1f5bc88199989e08cce7',
|
'b90fb812a2268e9569ff1172e8daed1da3dc7e72c7bded7c5bcb7282039f90d5fd8e71c1543de2cdc7f7623130c5f2cceb53549055fa1f5bc88199989e08cce7',
|
||||||
|
|
||||||
|
'f698a764b23aa6667b1157fc4247c6a1b58c21a3865ac6a47a3590167a9e021113cfb6de37a568aae56cadac907e6469b121464fe1b70a10c213eaea2cbb6636',
|
||||||
|
'e9c46841be76e3be0289694fd5c7503c04f40e5b036abac200b98a9006cf664758f3f46f4a93e7a4431e75b10af7497b747c3053cb7466ed53f4277e78a63c52',
|
||||||
|
'6d225f078ca611f00d86cbfd8ba6c6ac7826721434eae6525686efb878b72370f72b3c361381db2d88289440268c94c5e7467c9414375e6b63d03026750f3c66',
|
||||||
|
'5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1f123474e140a2c360b01f0fa66f2f22e2e965a5b07a80358cf75f77abbd66088',
|
||||||
]
|
]
|
||||||
|
|
||||||
for index, derivation_path in enumerate(derivation_paths):
|
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 = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
||||||
|
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
# 44'/1815'/0'/0/i
|
# 44'/1815'/0'/0/i
|
||||||
@ -116,8 +132,6 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
|||||||
|
|
||||||
node = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
node = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
||||||
|
|
||||||
node.derive_cardano(0x80000000 | 44)
|
|
||||||
node.derive_cardano(0x80000000 | 1815)
|
|
||||||
keychain = Keychain(node)
|
keychain = Keychain(node)
|
||||||
|
|
||||||
# 44'/1815'/0'/0/i
|
# 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
|
from .tools import expect
|
||||||
|
|
||||||
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
|
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
|
||||||
|
NETWORK_IDS = {"mainnet": 1, "testnet": 0}
|
||||||
|
|
||||||
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs")
|
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs")
|
||||||
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index")
|
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")
|
@expect(messages.CardanoAddress, field="address")
|
||||||
def get_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:
|
) -> messages.CardanoAddress:
|
||||||
return client.call(
|
return client.call(
|
||||||
messages.CardanoGetAddress(
|
messages.CardanoGetAddress(
|
||||||
address_n=address_n,
|
address_parameters=address_parameters,
|
||||||
protocol_magic=protocol_magic,
|
protocol_magic=protocol_magic,
|
||||||
|
network_id=network_id,
|
||||||
show_display=show_display,
|
show_display=show_display,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -51,6 +162,7 @@ def sign_tx(
|
|||||||
fee: int,
|
fee: int,
|
||||||
ttl: int,
|
ttl: int,
|
||||||
protocol_magic: int,
|
protocol_magic: int,
|
||||||
|
network_id: int,
|
||||||
) -> messages.CardanoSignedTx:
|
) -> messages.CardanoSignedTx:
|
||||||
response = client.call(
|
response = client.call(
|
||||||
messages.CardanoSignTx(
|
messages.CardanoSignTx(
|
||||||
@ -59,36 +171,8 @@ def sign_tx(
|
|||||||
fee=fee,
|
fee=fee,
|
||||||
ttl=ttl,
|
ttl=ttl,
|
||||||
protocol_magic=protocol_magic,
|
protocol_magic=protocol_magic,
|
||||||
|
network_id=network_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
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
|
import click
|
||||||
|
|
||||||
from .. import cardano, tools
|
from .. import cardano, messages, tools
|
||||||
from . import with_client
|
from . import ChoiceType, with_client
|
||||||
|
|
||||||
PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
|
PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
|
||||||
|
|
||||||
|
ADDRESS_TYPES = {
|
||||||
|
"byron": messages.CardanoAddressType.BYRON,
|
||||||
|
"base": messages.CardanoAddressType.BASE,
|
||||||
|
"pointer": messages.CardanoAddressType.POINTER,
|
||||||
|
"enterprise": messages.CardanoAddressType.ENTERPRISE,
|
||||||
|
"reward": messages.CardanoAddressType.REWARD,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@click.group(name="cardano")
|
@click.group(name="cardano")
|
||||||
def cli():
|
def cli():
|
||||||
@ -37,19 +45,27 @@ def cli():
|
|||||||
required=True,
|
required=True,
|
||||||
help="Transaction in JSON format",
|
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
|
@with_client
|
||||||
def sign_tx(client, file, protocol_magic):
|
def sign_tx(client, file, protocol_magic, network_id, testnet):
|
||||||
"""Sign Cardano transaction."""
|
"""Sign Cardano transaction."""
|
||||||
transaction = json.load(file)
|
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"]]
|
inputs = [cardano.create_input(input) for input in transaction["inputs"]]
|
||||||
outputs = [cardano.create_output(output) for output in transaction["outputs"]]
|
outputs = [cardano.create_output(output) for output in transaction["outputs"]]
|
||||||
fee = transaction["fee"]
|
fee = transaction["fee"]
|
||||||
ttl = transaction["ttl"]
|
ttl = transaction["ttl"]
|
||||||
|
|
||||||
signed_transaction = cardano.sign_tx(
|
signed_transaction = cardano.sign_tx(
|
||||||
client, inputs, outputs, fee, ttl, protocol_magic
|
client, inputs, outputs, fee, ttl, protocol_magic, network_id
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -61,12 +77,67 @@ def sign_tx(client, file, protocol_magic):
|
|||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
||||||
@click.option("-d", "--show-display", is_flag=True)
|
@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
|
@with_client
|
||||||
def get_address(client, address, show_display, protocol_magic):
|
def get_address(
|
||||||
"""Get Cardano address."""
|
client,
|
||||||
address_n = tools.parse_path(address)
|
address,
|
||||||
return cardano.get_address(client, address_n, protocol_magic, show_display)
|
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()
|
@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
|
# fmt: off
|
||||||
from .. import protobuf as p
|
from .. import protobuf as p
|
||||||
|
|
||||||
|
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
@ -15,18 +17,21 @@ class CardanoGetAddress(p.MessageType):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
address_n: List[int] = None,
|
|
||||||
show_display: bool = None,
|
show_display: bool = None,
|
||||||
protocol_magic: int = None,
|
protocol_magic: int = None,
|
||||||
|
network_id: int = None,
|
||||||
|
address_parameters: CardanoAddressParametersType = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.address_n = address_n if address_n is not None else []
|
|
||||||
self.show_display = show_display
|
self.show_display = show_display
|
||||||
self.protocol_magic = protocol_magic
|
self.protocol_magic = protocol_magic
|
||||||
|
self.network_id = network_id
|
||||||
|
self.address_parameters = address_parameters
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
|
||||||
2: ('show_display', p.BoolType, 0),
|
2: ('show_display', p.BoolType, 0),
|
||||||
3: ('protocol_magic', p.UVarintType, 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,
|
protocol_magic: int = None,
|
||||||
fee: int = None,
|
fee: int = None,
|
||||||
ttl: int = None,
|
ttl: int = None,
|
||||||
|
network_id: int = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.inputs = inputs if inputs is not None else []
|
self.inputs = inputs if inputs is not None else []
|
||||||
self.outputs = outputs if outputs is not None else []
|
self.outputs = outputs if outputs is not None else []
|
||||||
self.protocol_magic = protocol_magic
|
self.protocol_magic = protocol_magic
|
||||||
self.fee = fee
|
self.fee = fee
|
||||||
self.ttl = ttl
|
self.ttl = ttl
|
||||||
|
self.network_id = network_id
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
@ -38,4 +40,5 @@ class CardanoSignTx(p.MessageType):
|
|||||||
5: ('protocol_magic', p.UVarintType, 0),
|
5: ('protocol_magic', p.UVarintType, 0),
|
||||||
6: ('fee', p.UVarintType, 0),
|
6: ('fee', p.UVarintType, 0),
|
||||||
7: ('ttl', p.UVarintType, 0),
|
7: ('ttl', p.UVarintType, 0),
|
||||||
|
8: ('network_id', p.UVarintType, 0),
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
# fmt: off
|
# fmt: off
|
||||||
from .. import protobuf as p
|
from .. import protobuf as p
|
||||||
|
|
||||||
|
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
try:
|
try:
|
||||||
from typing import Dict, List # noqa: F401
|
from typing import Dict, List # noqa: F401
|
||||||
@ -15,17 +17,17 @@ class CardanoTxOutputType(p.MessageType):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
address: str = None,
|
address: str = None,
|
||||||
address_n: List[int] = None,
|
|
||||||
amount: int = None,
|
amount: int = None,
|
||||||
|
address_parameters: CardanoAddressParametersType = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.address = address
|
self.address = address
|
||||||
self.address_n = address_n if address_n is not None else []
|
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
|
self.address_parameters = address_parameters
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('address', p.UnicodeType, 0),
|
1: ('address', p.UnicodeType, 0),
|
||||||
2: ('address_n', p.UVarintType, p.FLAG_REPEATED),
|
|
||||||
3: ('amount', p.UVarintType, 0),
|
3: ('amount', p.UVarintType, 0),
|
||||||
|
4: ('address_parameters', CardanoAddressParametersType, 0),
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ from .ButtonAck import ButtonAck
|
|||||||
from .ButtonRequest import ButtonRequest
|
from .ButtonRequest import ButtonRequest
|
||||||
from .Cancel import Cancel
|
from .Cancel import Cancel
|
||||||
from .CardanoAddress import CardanoAddress
|
from .CardanoAddress import CardanoAddress
|
||||||
|
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||||
|
from .CardanoBlockchainPointerType import CardanoBlockchainPointerType
|
||||||
from .CardanoGetAddress import CardanoGetAddress
|
from .CardanoGetAddress import CardanoGetAddress
|
||||||
from .CardanoGetPublicKey import CardanoGetPublicKey
|
from .CardanoGetPublicKey import CardanoGetPublicKey
|
||||||
from .CardanoPublicKey import CardanoPublicKey
|
from .CardanoPublicKey import CardanoPublicKey
|
||||||
@ -277,6 +279,7 @@ from . import BinanceOrderType
|
|||||||
from . import BinanceTimeInForce
|
from . import BinanceTimeInForce
|
||||||
from . import ButtonRequestType
|
from . import ButtonRequestType
|
||||||
from . import Capability
|
from . import Capability
|
||||||
|
from . import CardanoAddressType
|
||||||
from . import DebugLinkShowTextStyle
|
from . import DebugLinkShowTextStyle
|
||||||
from . import DebugSwipeDirection
|
from . import DebugSwipeDirection
|
||||||
from . import FailureType
|
from . import FailureType
|
||||||
|
@ -16,10 +16,14 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib.cardano import PROTOCOL_MAGICS, get_address
|
from trezorlib import tools
|
||||||
from trezorlib.tools import parse_path
|
from trezorlib.cardano import (
|
||||||
|
NETWORK_IDS,
|
||||||
from ..common import MNEMONIC12
|
PROTOCOL_MAGICS,
|
||||||
|
create_address_parameters,
|
||||||
|
get_address,
|
||||||
|
)
|
||||||
|
from trezorlib.messages import CardanoAddressType
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.altcoin
|
@pytest.mark.altcoin
|
||||||
@ -32,38 +36,250 @@ from ..common import MNEMONIC12
|
|||||||
(
|
(
|
||||||
"m/44'/1815'/0'/0/0",
|
"m/44'/1815'/0'/0/0",
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
"Ae2tdPwUPEZLCq3sFv4wVYxwqjMH2nUzBVt1HFr4v87snYrtYq3d3bq2PUQ",
|
"Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRdsswsZ",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"m/44'/1815'/0'/0/1",
|
"m/44'/1815'/0'/0/1",
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
"Ae2tdPwUPEZEY6pVJoyuNNdLp7VbMB7U7qfebeJ7XGunk5Z2eHarkcN1bHK",
|
"Ae2tdPwUPEZJb8r1VZxweSwHDTYtqeYqF39rZmVbrNK62JHd4Wd7Ytsc8eG",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"m/44'/1815'/0'/0/2",
|
"m/44'/1815'/0'/0/2",
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
"Ae2tdPwUPEZ3gZD1QeUHvAqadAV59Zid6NP9VCR9BG5LLAja9YtBUgr6ttK",
|
"Ae2tdPwUPEZFm6Y7aPZGKMyMAK16yA5pWWKU9g73ncUQNZsAjzjhszenCsq",
|
||||||
),
|
),
|
||||||
# testnet
|
# testnet
|
||||||
# data generated by code under test
|
# data generated by code under test
|
||||||
(
|
(
|
||||||
"m/44'/1815'/0'/0/0",
|
"m/44'/1815'/0'/0/0",
|
||||||
PROTOCOL_MAGICS["testnet"],
|
PROTOCOL_MAGICS["testnet"],
|
||||||
"2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
|
"2657WMsDfac5F3zbgs9BwNWx3dhGAJERkAL93gPa68NJ2i8mbCHm2pLUHWSj8Mfea",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"m/44'/1815'/0'/0/1",
|
"m/44'/1815'/0'/0/1",
|
||||||
PROTOCOL_MAGICS["testnet"],
|
PROTOCOL_MAGICS["testnet"],
|
||||||
"2657WMsDfac61ebUDw53WUX49Dcfya8S8G7iYbhN4nP8JSFuh38T1LuFax1bUnhxA",
|
"2657WMsDfac6ezKWszxLFqJjSUgpg9NgxKc1koqi24sVpRaPhiwMaExk4useKn5HA",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"m/44'/1815'/0'/0/2",
|
"m/44'/1815'/0'/0/2",
|
||||||
PROTOCOL_MAGICS["testnet"],
|
PROTOCOL_MAGICS["testnet"],
|
||||||
"2657WMsDfac5PMpEsxc1md3pgZKUZRZ11MUK8tjkDHBQG9b3TMBsTQc4PmmumVrcn",
|
"2657WMsDfac7hr1ioJGr6g7r6JRx4r1My8Rj91tcPTeVjJDpfBYKURrPG2zVLx2Sq",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.setup_client(mnemonic=MNEMONIC12)
|
|
||||||
def test_cardano_get_address(client, path, protocol_magic, expected_address):
|
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
|
assert address == expected_address
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib.cardano import PROTOCOL_MAGICS, get_address
|
from trezorlib import tools
|
||||||
from trezorlib.tools import parse_path
|
from trezorlib.cardano import PROTOCOL_MAGICS, create_address_parameters, get_address
|
||||||
|
from trezorlib.messages import CardanoAddressType
|
||||||
|
|
||||||
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
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
|
assert client.features.passphrase_protection is True
|
||||||
client.use_passphrase("TREZOR")
|
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
|
assert address == expected_address
|
||||||
|
@ -37,14 +37,24 @@ from trezorlib.tools import parse_path
|
|||||||
"70f131bb799fd659c997221ad8cae7dcce4e8da701f8101cf15307fd3a3712a1",
|
"70f131bb799fd659c997221ad8cae7dcce4e8da701f8101cf15307fd3a3712a1",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"m/44'/1815'/2'",
|
"m/1852'/1815'/0'",
|
||||||
"076338cee5ab3dae19f06ccaa80e3d4428cf0e1bdc04243e41bba7be63a90da7",
|
"d507c8f866691bd96e131334c355188b1a1d0b2fa0ab11545075aab332d77d9e",
|
||||||
"5dcdf129f6f2d108292e615c4b67a1fc41a64e6a96130f5c981e5e8e046a6cd7",
|
"b19657ad13ee581b56b0f8d744d66ca356b93d42fe176b3de007d53e9c4c4e7a",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"m/44'/1815'/3'",
|
"m/1852'/1815'/1'",
|
||||||
"5f769380dc6fd17a4e0f2d23aa359442a712e5e96d7838ebb91eb020003cccc3",
|
"140791584001446365f169c82241c7c214475000180dab39fa0588fc9c3d6d80",
|
||||||
"1197ea234f528987cbac9817ebc31344395b837a3bb7c2332f87e095e70550a5",
|
"7f9f812d49816844b52e319857aa75961724ad1a146701679d02d7168622233d",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"m/1852'/1815'/2'",
|
||||||
|
"ff6ccc3097ca79fc29fe92a9639c47644746780c63acae10a9e6f03bf5c919dd",
|
||||||
|
"27d985feabf40d83a30aa4645ff008c068187559dd224ba59e26d0d2dc3598ce",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"m/1852'/1815'/3'",
|
||||||
|
"be81ace1f63f4f0cae74dd274a72d7818f238bc764ab3e0dc0beb1945b756dca",
|
||||||
|
"29034f036a162ac4f9f9f397b2d1f289754bb6633915f26b199e156f81d05c88",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -17,21 +17,73 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from trezorlib import cardano, messages
|
from trezorlib import cardano, messages
|
||||||
from trezorlib.cardano import PROTOCOL_MAGICS
|
from trezorlib.cardano import NETWORK_IDS, PROTOCOL_MAGICS
|
||||||
from trezorlib.exceptions import TrezorFailure
|
from trezorlib.exceptions import TrezorFailure
|
||||||
|
|
||||||
SAMPLE_INPUT = {
|
|
||||||
"path": "m/44'/1815'/0'/0/1",
|
class InputAction:
|
||||||
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
|
"""
|
||||||
"prev_index": 0,
|
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 = {
|
SAMPLE_OUTPUTS = {
|
||||||
"simple_output": {
|
"simple_byron_output": {
|
||||||
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
|
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
|
||||||
"amount": "3003112",
|
"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": {
|
"invalid_address": {
|
||||||
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
|
"address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q",
|
||||||
"amount": "3003112",
|
"amount": "3003112",
|
||||||
@ -44,14 +96,42 @@ SAMPLE_OUTPUTS = {
|
|||||||
"address": "Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRZR3Mbm",
|
"address": "Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRZR3Mbm",
|
||||||
"amount": "3003112",
|
"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",
|
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
|
||||||
"amount": "449999999199999999",
|
"amount": "449999999199999999",
|
||||||
},
|
},
|
||||||
"testnet_output": {
|
"testnet_output": {
|
||||||
"address": "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r",
|
"address": "2657WMsDfac7BteXkJq5Jzdog4h47fPbkwUM49isuWbYAr2cFRHa3rURP236h9PBe",
|
||||||
"amount": "3003112",
|
"amount": "3003112",
|
||||||
},
|
},
|
||||||
|
"shelley_testnet_output": {
|
||||||
|
"address": "addr_test1vr9s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqqtmut0e",
|
||||||
|
"amount": "1",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
VALID_VECTORS = [
|
VALID_VECTORS = [
|
||||||
@ -59,14 +139,18 @@ VALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["simple_output"]],
|
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
10,
|
10,
|
||||||
|
# input flow
|
||||||
|
[[InputAction.SWIPE, InputAction.YES], [InputAction.SWIPE, InputAction.YES]],
|
||||||
# tx hash
|
# tx hash
|
||||||
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
|
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
|
||||||
# serialized tx
|
# serialized tx
|
||||||
@ -74,37 +158,166 @@ VALID_VECTORS = [
|
|||||||
),
|
),
|
||||||
# Mainnet transaction with change
|
# Mainnet transaction with change
|
||||||
(
|
(
|
||||||
# protocol magic (mainnet)
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]],
|
[SAMPLE_OUTPUTS["simple_byron_output"], SAMPLE_OUTPUTS["byron_change_output"]],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
10,
|
10,
|
||||||
|
# input flow
|
||||||
|
[
|
||||||
|
[InputAction.SWIPE, InputAction.YES],
|
||||||
|
[InputAction.YES],
|
||||||
|
[InputAction.SWIPE, InputAction.YES],
|
||||||
|
],
|
||||||
# tx hash
|
# tx hash
|
||||||
"81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43",
|
"81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43",
|
||||||
# serialized tx
|
# serialized tx
|
||||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840d909b16038c4fd772a177038242e6793be39c735430b03ee924ed18026bd28d06920b5846247945f1204276e4b759aa5ac05a4a73b49ce705ab0e5e54a3a170e582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6",
|
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840d909b16038c4fd772a177038242e6793be39c735430b03ee924ed18026bd28d06920b5846247945f1204276e4b759aa5ac05a4a73b49ce705ab0e5e54a3a170e582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6",
|
||||||
),
|
),
|
||||||
# Testnet transaction
|
# simple transaction with base address change output
|
||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["testnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["shelley_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]],
|
[
|
||||||
|
SAMPLE_OUTPUTS["simple_shelley_output"],
|
||||||
|
SAMPLE_OUTPUTS["base_address_change_output"],
|
||||||
|
],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
10,
|
10,
|
||||||
|
# input flow
|
||||||
|
[[InputAction.SWIPE, InputAction.YES], [InputAction.SWIPE, InputAction.YES]],
|
||||||
# tx hash
|
# 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
|
# serialized tx
|
||||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840fc30afdd0d4a6d8581e0f6abe895994d208fd382f2b23ff1553d711477a4fedbd1f68a76e7465c4816d5477f4287f7360acf71fca3b3d5902e4448e48c447106582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6",
|
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018382582f82d818582583581cc817d85b524e3d073795819a25cdbb84cff6aa2bbb3a081980d248cba10242182a001a0fb6fc611a002dd2e882581d60cb03849e268f989b5a843107bad7fa2908246986a8f3d643f8c184800182582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840cc11adf81cb3c3b75a438325f8577666f5cbb4d5d6b73fa6dbbcf5ab36897df34eecacdb54c3bc3ce7fc594ebb2c7aa4db4700f4290facad9b611a035af8710a582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -113,8 +326,10 @@ INVALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["invalid_address"]],
|
[SAMPLE_OUTPUTS["invalid_address"]],
|
||||||
# fee
|
# fee
|
||||||
@ -126,10 +341,12 @@ INVALID_VECTORS = [
|
|||||||
),
|
),
|
||||||
# Output address is invalid CBOR
|
# Output address is invalid CBOR
|
||||||
(
|
(
|
||||||
# protocol magic (mainnet)
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["invalid_cbor"]],
|
[SAMPLE_OUTPUTS["invalid_cbor"]],
|
||||||
# fee
|
# fee
|
||||||
@ -143,8 +360,10 @@ INVALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic (mainnet)
|
# protocol magic (mainnet)
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["invalid_crc"]],
|
[SAMPLE_OUTPUTS["invalid_crc"]],
|
||||||
# fee
|
# fee
|
||||||
@ -154,14 +373,118 @@ INVALID_VECTORS = [
|
|||||||
# error message
|
# error message
|
||||||
"Invalid address",
|
"Invalid address",
|
||||||
),
|
),
|
||||||
# Fee is too high
|
# Output base address is too short
|
||||||
(
|
(
|
||||||
# protocol magic (mainnet)
|
# protocol magic (mainnet)
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["shelley_input"]],
|
||||||
# outputs
|
# 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
|
# fee
|
||||||
45000000000000001,
|
45000000000000001,
|
||||||
# ttl
|
# ttl
|
||||||
@ -171,12 +494,17 @@ INVALID_VECTORS = [
|
|||||||
),
|
),
|
||||||
# Output total is too high
|
# Output total is too high
|
||||||
(
|
(
|
||||||
# protocol magic (mainnet)
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["large_simple_output"], SAMPLE_OUTPUTS["change_output"]],
|
[
|
||||||
|
SAMPLE_OUTPUTS["large_simple_byron_output"],
|
||||||
|
SAMPLE_OUTPUTS["byron_change_output"],
|
||||||
|
],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
@ -188,8 +516,10 @@ INVALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["testnet_output"]],
|
[SAMPLE_OUTPUTS["testnet_output"]],
|
||||||
# fee
|
# fee
|
||||||
@ -203,10 +533,12 @@ INVALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["testnet"],
|
PROTOCOL_MAGICS["testnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["testnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["simple_output"]],
|
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
@ -214,6 +546,74 @@ INVALID_VECTORS = [
|
|||||||
# error message
|
# error message
|
||||||
"Output address network mismatch!",
|
"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.cardano
|
||||||
@pytest.mark.skip_t1 # T1 support is not planned
|
@pytest.mark.skip_t1 # T1 support is not planned
|
||||||
@pytest.mark.parametrize(
|
@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(
|
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]
|
inputs = [cardano.create_input(i) for i in inputs]
|
||||||
outputs = [cardano.create_output(o) for o in outputs]
|
outputs = [cardano.create_output(o) for o in outputs]
|
||||||
|
|
||||||
expected_responses = [
|
expected_responses = [
|
||||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
for i in range(len(input_flow_sequences))
|
||||||
messages.CardanoSignedTx(),
|
|
||||||
]
|
]
|
||||||
|
expected_responses.append(messages.CardanoSignedTx())
|
||||||
|
|
||||||
def input_flow():
|
def input_flow():
|
||||||
yield
|
for sequence in input_flow_sequences:
|
||||||
client.debug.swipe_up()
|
yield
|
||||||
client.debug.press_yes()
|
for action in sequence:
|
||||||
yield
|
if action == InputAction.SWIPE:
|
||||||
client.debug.swipe_up()
|
client.debug.swipe_up()
|
||||||
client.debug.press_yes()
|
elif action == InputAction.YES:
|
||||||
|
client.debug.press_yes()
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid input action")
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(expected_responses)
|
client.set_expected_responses(expected_responses)
|
||||||
client.set_input_flow(input_flow)
|
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.tx_hash.hex() == tx_hash
|
||||||
assert response.serialized_tx.hex() == serialized_tx
|
assert response.serialized_tx.hex() == serialized_tx
|
||||||
|
|
||||||
@ -255,10 +670,18 @@ def test_cardano_sign_tx(
|
|||||||
@pytest.mark.cardano
|
@pytest.mark.cardano
|
||||||
@pytest.mark.skip_t1 # T1 support is not planned
|
@pytest.mark.skip_t1 # T1 support is not planned
|
||||||
@pytest.mark.parametrize(
|
@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(
|
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]
|
inputs = [cardano.create_input(i) for i in inputs]
|
||||||
outputs = [cardano.create_output(o) for o in outputs]
|
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)
|
client.set_expected_responses(expected_responses)
|
||||||
|
|
||||||
with pytest.raises(TrezorFailure, match=expected_error_message):
|
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
|
import pytest
|
||||||
|
|
||||||
from trezorlib import cardano, messages
|
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
|
from ..common import MNEMONIC_SLIP39_BASIC_20_3of6
|
||||||
|
from .test_msg_cardano_sign_transaction import (
|
||||||
SAMPLE_INPUT = {
|
SAMPLE_INPUTS,
|
||||||
"path": "m/44'/1815'/0'/0/1",
|
SAMPLE_OUTPUTS,
|
||||||
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
|
InputAction,
|
||||||
"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",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
VALID_VECTORS = [
|
VALID_VECTORS = [
|
||||||
# Mainnet transaction without change
|
# Mainnet transaction without change
|
||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["simple_output"]],
|
[SAMPLE_OUTPUTS["simple_byron_output"]],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
10,
|
10,
|
||||||
|
# input flow
|
||||||
|
[[InputAction.SWIPE, InputAction.YES], [InputAction.SWIPE, InputAction.YES]],
|
||||||
# tx hash
|
# tx hash
|
||||||
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
|
"73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
|
||||||
# serialized tx
|
# serialized tx
|
||||||
@ -61,14 +52,22 @@ VALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic (mainnet)
|
# protocol magic (mainnet)
|
||||||
PROTOCOL_MAGICS["mainnet"],
|
PROTOCOL_MAGICS["mainnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["mainnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]],
|
[SAMPLE_OUTPUTS["simple_byron_output"], SAMPLE_OUTPUTS["byron_change_output"]],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
10,
|
10,
|
||||||
|
# input flow
|
||||||
|
[
|
||||||
|
[InputAction.SWIPE, InputAction.YES],
|
||||||
|
[InputAction.YES],
|
||||||
|
[InputAction.SWIPE, InputAction.YES],
|
||||||
|
],
|
||||||
# tx hash
|
# tx hash
|
||||||
"4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae",
|
"4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae",
|
||||||
# serialized tx
|
# serialized tx
|
||||||
@ -78,18 +77,26 @@ VALID_VECTORS = [
|
|||||||
(
|
(
|
||||||
# protocol magic
|
# protocol magic
|
||||||
PROTOCOL_MAGICS["testnet"],
|
PROTOCOL_MAGICS["testnet"],
|
||||||
|
# network id
|
||||||
|
NETWORK_IDS["testnet"],
|
||||||
# inputs
|
# inputs
|
||||||
[SAMPLE_INPUT],
|
[SAMPLE_INPUTS["byron_input"]],
|
||||||
# outputs
|
# outputs
|
||||||
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]],
|
[SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["byron_change_output"]],
|
||||||
# fee
|
# fee
|
||||||
42,
|
42,
|
||||||
# ttl
|
# ttl
|
||||||
10,
|
10,
|
||||||
|
# input flow
|
||||||
|
[
|
||||||
|
[InputAction.SWIPE, InputAction.YES],
|
||||||
|
[InputAction.YES],
|
||||||
|
[InputAction.SWIPE, InputAction.YES],
|
||||||
|
],
|
||||||
# tx hash
|
# tx hash
|
||||||
"ac7ef9e4f51ed4d6b791cee111b240dae2f00c39c5cc1a150631eba8aa955528",
|
"93a2c3cfb67ef1e4bae167b0f443c3370664bdb9171bc9cd41bad98e5cc049b2",
|
||||||
# serialized tx
|
# serialized tx
|
||||||
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c709bfb5d9733cbdd72f520cd2c8b9f8f942da5e6cd0b6994e1803b0aa10242182a001aef14e76d1a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840cfd68676454ad8bed8575dcb8ee91824c0f836da4f07a54112088b12c6b89be0c8f729d4e3fb1df0de10f049a66dea372f3e2888cabb6110d538a0e9a06fbb0758206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b45a10242182af6",
|
"83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581cc817d85b524e3d073795819a25cdbb84cff6aa2bbb3a081980d248cba10242182a001a0fb6fc611a002dd2e882582f82d818582583581c709bfb5d9733cbdd72f520cd2c8b9f8f942da5e6cd0b6994e1803b0aa10242182a001aef14e76d1a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840552d1d66972598532fa539faa98cdc7889c8dce00577626a62fb22d0e244d9f49732b6ab65593352a7486123077b7e36308c5048cc8ee6dc465e576f065cb70558206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b45a10242182af6",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -99,33 +106,48 @@ VALID_VECTORS = [
|
|||||||
@pytest.mark.skip_t1 # T1 support is not planned
|
@pytest.mark.skip_t1 # T1 support is not planned
|
||||||
@pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True)
|
@pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True)
|
||||||
@pytest.mark.parametrize(
|
@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(
|
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]
|
inputs = [cardano.create_input(i) for i in inputs]
|
||||||
outputs = [cardano.create_output(o) for o in outputs]
|
outputs = [cardano.create_output(o) for o in outputs]
|
||||||
|
|
||||||
expected_responses = [
|
expected_responses = [messages.PassphraseRequest()]
|
||||||
messages.PassphraseRequest(),
|
expected_responses += [
|
||||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
messages.ButtonRequest(code=messages.ButtonRequestType.Other)
|
||||||
messages.ButtonRequest(code=messages.ButtonRequestType.Other),
|
for i in range(len(input_flow_sequences))
|
||||||
messages.CardanoSignedTx(),
|
|
||||||
]
|
]
|
||||||
|
expected_responses.append(messages.CardanoSignedTx())
|
||||||
|
|
||||||
def input_flow():
|
def input_flow():
|
||||||
yield
|
for sequence in input_flow_sequences:
|
||||||
client.debug.swipe_up()
|
yield
|
||||||
client.debug.press_yes()
|
for action in sequence:
|
||||||
yield
|
if action == InputAction.SWIPE:
|
||||||
client.debug.swipe_up()
|
client.debug.swipe_up()
|
||||||
client.debug.press_yes()
|
elif action == InputAction.YES:
|
||||||
|
client.debug.press_yes()
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid input action")
|
||||||
|
|
||||||
client.use_passphrase("TREZOR")
|
client.use_passphrase("TREZOR")
|
||||||
with client:
|
with client:
|
||||||
client.set_expected_responses(expected_responses)
|
client.set_expected_responses(expected_responses)
|
||||||
client.set_input_flow(input_flow)
|
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.tx_hash.hex() == tx_hash
|
||||||
assert response.serialized_tx.hex() == serialized_tx
|
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[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[message1-expected_response1]": "7b8bbe5ba7d7b07c95065608fb1cf9aeafcb3f9671835a6e5e5a6997ff4ff99b",
|
||||||
"test_msg_binance_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "813ad1b802dee1ace4dfa378edd840dbcea57c1a1b8eed67134def024c40a6e9",
|
"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-42-2657WMsDfac5F3zbgs9B": "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-0-764824073-Ae2tdPwUPEZ5Y": "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-42-2657WMsDfac6ezKWszxL": "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-1-764824073-Ae2tdPwUPEZJb": "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-42-2657WMsDfac7hr1ioJGr": "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-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'-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'-1'-ea5dde31b9f551e08a5b6": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-2'-076338cee5ab3dae19f06": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[42-0-inputs6-outputs6-42-10-input_flow_se": "ae26b018ccceacd0c78de3389a673eca60753a6714f3146296ee17072417ec57",
|
||||||
"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[764824073-1-inputs0-outputs0-42-10-input_": "fe89b315aeb25a4a449f0cadccdafcf58669d247101b038823aad25e4e7ed670",
|
||||||
"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-1-inputs1-outputs1-42-10-input_": "900bd30689093a89966b56fa2663f260b0b2d4c0ba74dc4fb06cb0f45f5b3b4d",
|
||||||
"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-1-inputs2-outputs2-42-10-input_": "7a35fb89f41b8730c1df37ab1fc63ee8aa4ba3b14282c68b1b86baf81665ff9c",
|
||||||
"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[764824073-1-inputs3-outputs3-42-10-input_": "09faf9d0ad559154f74ac9cb3962411be07f81c5f310b23249211af1a6d4e1b2",
|
||||||
"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[764824073-1-inputs4-outputs4-42-10-input_": "3141c25aef3c98eed72a91833b5b55b2a4e3593bb8e79562765eb82321e99685",
|
||||||
"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[764824073-1-inputs5-outputs5-42-10-input_": "5a2a0a944774c4cbe6721fb06d41117da09394a9198c465db74dd81f037c701d",
|
||||||
"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[42-0-inputs12-outputs12-42-10-": "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[42-0-inputs14-outputs14-42-10-": "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[42-1-inputs15-outputs15-42-10-": "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-0-inputs16-outputs16": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs5-outputs5-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs0-outputs0-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-ac7ef9e4f51": "ba354313a87bff3e5079da58c74cdc265a9be8e9a80006e76ea68d0cdb869665",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs1-outputs1-4": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e0": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16",
|
"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-1-inputs10-outputs10": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||||
"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_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_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_remove_wipe_code": "d280ed129a2ea4781af9e35542aa31ecf63da75fc6812ed3bd05107809f836a4",
|
||||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "32c808f00bada2059f933f3515337e494c837bdf65e4ea918b457d1c9f4cb42a",
|
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "32c808f00bada2059f933f3515337e494c837bdf65e4ea918b457d1c9f4cb42a",
|
||||||
|
Loading…
Reference in New Issue
Block a user