diff --git a/common/protob/messages-cardano.proto b/common/protob/messages-cardano.proto index c18f7b99a..ccca5a20a 100644 --- a/common/protob/messages-cardano.proto +++ b/common/protob/messages-cardano.proto @@ -14,8 +14,9 @@ import "messages-common.proto"; * @next Failure */ message CardanoGetAddress { - repeated uint32 address_n = 1; // BIP-32-style path to derive the key from master node - optional bool show_display = 2; // optionally prompt for confirmation on trezor display + repeated uint32 address_n = 1; // BIP-32-style path to derive the key from master node + optional bool show_display = 2; // optionally prompt for confirmation on trezor display + optional uint32 protocol_magic = 3; // network's protocol magic - needed for Byron addresses on testnets } /** @@ -50,14 +51,15 @@ message CardanoPublicKey { * Request: Ask device to sign Cardano transaction * @start * @next CardanoSignedTx - * @next CardanoTxRequest * @next Failure */ message CardanoSignTx { repeated CardanoTxInputType inputs = 1; // inputs to be used in transaction repeated CardanoTxOutputType outputs = 2; // outputs to be used in transaction - optional uint32 transactions_count = 3; // transactions count + // optional uint32 transactions_count = 3; // left as a comment so we know to skip the id 3 in the future optional uint32 protocol_magic = 5; // network's protocol magic + optional uint64 fee = 6; // transaction fee - added in shelley + optional uint64 ttl = 7; // transaction ttl - added in shelley /** * Structure representing cardano transaction input */ @@ -65,7 +67,8 @@ message CardanoSignTx { repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node optional bytes prev_hash = 2; // hash of previous transaction output to spend by this input optional uint32 prev_index = 3; // index of previous output to spend - optional uint32 type = 4; // input type, defaults to 0 + // left as a comment so we know to skip the id 4 in the future + // optional uint32 type = 4; } /** * Structure representing cardano transaction output @@ -77,31 +80,11 @@ message CardanoSignTx { } } -/** - * Response: Serialised signed cardano transaction if tx_index is not specified. - * If tx_index is specified, trezor will wait for transaction - * @next CardanoTxAck - */ -message CardanoTxRequest { - optional uint32 tx_index = 1; // index of requested transaction - optional bytes tx_hash = 2; // hash of the signed transaction - optional bytes tx_body = 3; // serialised body of the signed transaction -} - -/** - * Request: Reported transaction data - * @next CardanoSignedTx - * @next CardanoTxRequest - */ -message CardanoTxAck { - optional bytes transaction = 1; -} - /** * Response: Serialised signed cardano transaction * @end */ message CardanoSignedTx { - optional bytes tx_hash = 1; // hash of the signed transaction - optional bytes tx_body = 2; // serialised body of the signed transaction + optional bytes tx_hash = 1; // hash of the transaction body + optional bytes serialized_tx = 2; // serialized, signed transaction } diff --git a/common/protob/messages.proto b/common/protob/messages.proto index 0857485ce..9ab796dba 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -181,13 +181,12 @@ enum MessageType { // Cardano // dropped Sign/VerifyMessage ids 300-302 + // dropped TxRequest/TxAck ids 304 and 309 (shelley update) MessageType_CardanoSignTx = 303 [(wire_in) = true]; - MessageType_CardanoTxRequest = 304 [(wire_out) = true]; MessageType_CardanoGetPublicKey = 305 [(wire_in) = true]; MessageType_CardanoPublicKey = 306 [(wire_out) = true]; MessageType_CardanoGetAddress = 307 [(wire_in) = true]; MessageType_CardanoAddress = 308 [(wire_out) = true]; - MessageType_CardanoTxAck = 309 [(wire_in) = true]; MessageType_CardanoSignedTx = 310 [(wire_out) = true]; // Ripple diff --git a/core/src/apps/cardano/address.py b/core/src/apps/cardano/address.py index 37792f354..ac889813a 100644 --- a/core/src/apps/cardano/address.py +++ b/core/src/apps/cardano/address.py @@ -1,11 +1,22 @@ -from trezor import log +from trezor import log, wire from trezor.crypto import base58, crc, hashlib from apps.common import HARDENED, cbor from apps.common.seed import remove_ed25519_prefix +from . import protocol_magics -def _encode_address_raw(address_data_encoded): +if False: + from typing import Tuple + from trezor.crypto import bip32 + from . import seed + +PROTOCOL_MAGIC_KEY = 2 +INVALID_ADDRESS = wire.ProcessError("Invalid address") +NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!") + + +def _encode_address_raw(address_data_encoded: bytes) -> str: return base58.encode( cbor.encode( [cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)] @@ -13,13 +24,14 @@ def _encode_address_raw(address_data_encoded): ) -def derive_address_and_node(keychain, path: list): +def derive_address_and_node( + keychain: seed.Keychain, path: list, protocol_magic: int +) -> Tuple[str, bip32.HDNode]: node = keychain.derive(path) - address_payload = None - address_attributes = {} + address_attributes = get_address_attributes(protocol_magic) - address_root = _get_address_root(node, address_payload) + 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) @@ -27,28 +39,75 @@ def derive_address_and_node(keychain, path: list): return (_encode_address_raw(address_data_encoded), node) -def is_safe_output_address(address) -> bool: - """ - Determines whether it is safe to include the address as-is as - a tx output, preventing unintended side effects (e.g. CBOR injection) - """ +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) - return False + raise INVALID_ADDRESS if not isinstance(address_unpacked, list) or len(address_unpacked) != 2: - return False + raise INVALID_ADDRESS address_data_encoded = address_unpacked[0] - if not isinstance(address_data_encoded, bytes): - return False + 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 _encode_address_raw(address_data_encoded) == 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_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: @@ -74,17 +133,13 @@ def validate_full_path(path: list) -> bool: return True -def _address_hash(data) -> bytes: - data = cbor.encode(data) - data = hashlib.sha3_256(data).digest() - res = hashlib.blake2b(data=data, outlen=28).digest() +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, payload): +def _get_address_root(node: bip32.HDNode, address_attributes: dict) -> bytes: extpubkey = remove_ed25519_prefix(node.public_key()) + node.chain_code() - if payload: - payload = {1: cbor.encode(payload)} - else: - payload = {} - return _address_hash([0, [0, extpubkey], payload]) + return _address_hash([0, [0, extpubkey], address_attributes]) diff --git a/core/src/apps/cardano/get_address.py b/core/src/apps/cardano/get_address.py index ac5455cb7..b3ffeef46 100644 --- a/core/src/apps/cardano/get_address.py +++ b/core/src/apps/cardano/get_address.py @@ -1,18 +1,21 @@ from trezor import log, wire from trezor.messages.CardanoAddress import CardanoAddress -from apps.cardano import CURVE, seed -from apps.cardano.address import derive_address_and_node, validate_full_path from apps.common import paths from apps.common.layout import address_n_to_str, show_address, show_qr +from . import CURVE, seed +from .address import derive_address_and_node, validate_full_path + @seed.with_keychain async def get_address(ctx, msg, keychain: seed.Keychain): await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n, CURVE) try: - address, _ = derive_address_and_node(keychain, msg.address_n) + address, _ = derive_address_and_node( + keychain, msg.address_n, msg.protocol_magic + ) except ValueError as e: if __debug__: log.exception(__name__, e) diff --git a/core/src/apps/cardano/get_public_key.py b/core/src/apps/cardano/get_public_key.py index 27766726e..7a0f3001b 100644 --- a/core/src/apps/cardano/get_public_key.py +++ b/core/src/apps/cardano/get_public_key.py @@ -4,11 +4,11 @@ from trezor import log, wire from trezor.messages.CardanoPublicKey import CardanoPublicKey from trezor.messages.HDNodeType import HDNodeType -from apps.cardano import CURVE, seed -from apps.cardano.address import derive_address_and_node from apps.common import layout, paths from apps.common.seed import remove_ed25519_prefix +from . import CURVE, seed + @seed.with_keychain async def get_public_key(ctx, msg, keychain: seed.Keychain): @@ -34,7 +34,7 @@ async def get_public_key(ctx, msg, keychain: seed.Keychain): def _get_public_key(keychain, derivation_path: list): - _, node = derive_address_and_node(keychain, derivation_path) + node = keychain.derive(derivation_path) public_key = hexlify(remove_ed25519_prefix(node.public_key())).decode() chain_code = hexlify(node.chain_code()).decode() diff --git a/core/src/apps/cardano/layout/__init__.py b/core/src/apps/cardano/layout/__init__.py index f5f8fae0a..e84c70f63 100644 --- a/core/src/apps/cardano/layout/__init__.py +++ b/core/src/apps/cardano/layout/__init__.py @@ -8,6 +8,8 @@ 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") @@ -35,7 +37,7 @@ async def confirm_sending(ctx, amount, to): await require_confirm(ctx, Paginated(pages)) -async def confirm_transaction(ctx, amount, fee, network_name): +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)) @@ -44,6 +46,6 @@ async def confirm_transaction(ctx, amount, fee, network_name): t2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN) t2.normal("Network:") - t2.bold(network_name) + t2.bold(protocol_magics.to_ui_string(protocol_magic)) await require_hold_to_confirm(ctx, Paginated([t1, t2])) diff --git a/core/src/apps/cardano/layout/progress.py b/core/src/apps/cardano/layout/progress.py deleted file mode 100644 index fe08d6f0c..000000000 --- a/core/src/apps/cardano/layout/progress.py +++ /dev/null @@ -1,28 +0,0 @@ -from trezor import ui - -_progress = 0 -_steps = 0 - - -def init(total_steps, text): - global _progress, _steps - _progress = 0 - _steps = total_steps - report_init(text) - report() - - -def advance(): - global _progress - _progress += 1 - report() - - -def report_init(text): - ui.display.clear() - ui.header(text) - - -def report(): - p = 1000 * _progress // _steps - ui.display.loader(p, False, 18, ui.WHITE, ui.BG) diff --git a/core/src/apps/cardano/protocol_magics.py b/core/src/apps/cardano/protocol_magics.py new file mode 100644 index 000000000..5d56e6abb --- /dev/null +++ b/core/src/apps/cardano/protocol_magics.py @@ -0,0 +1,11 @@ +MAINNET = 764824073 +TESTNET = 42 + +NAMES = { + MAINNET: "Mainnet", + TESTNET: "Testnet", +} + + +def to_ui_string(value: int) -> str: + return NAMES.get(value, "Unknown") diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index f148cb528..912961121 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -2,10 +2,11 @@ from storage import cache, device from trezor import wire from trezor.crypto import bip32 -from apps.cardano import SEED_NAMESPACE from apps.common import mnemonic from apps.common.passphrase import get as get_passphrase +from . import SEED_NAMESPACE + if False: from apps.common.paths import Bip32Path from apps.common.keychain import MsgIn, MsgOut, Handler, HandlerWithKeychain diff --git a/core/src/apps/cardano/sign_tx.py b/core/src/apps/cardano/sign_tx.py index dafc589d0..a17aaaa7d 100644 --- a/core/src/apps/cardano/sign_tx.py +++ b/core/src/apps/cardano/sign_tx.py @@ -4,238 +4,189 @@ from trezor import log, wire from trezor.crypto import base58, hashlib from trezor.crypto.curve import ed25519 from trezor.messages.CardanoSignedTx import CardanoSignedTx -from trezor.messages.CardanoTxAck import CardanoTxAck -from trezor.messages.CardanoTxRequest import CardanoTxRequest -from apps.cardano import CURVE, seed -from apps.cardano.address import ( - derive_address_and_node, - is_safe_output_address, - validate_full_path, -) -from apps.cardano.layout import confirm_sending, confirm_transaction, progress from apps.common import cbor from apps.common.paths import validate_path from apps.common.seed import remove_ed25519_prefix +from . import CURVE, seed +from .address import ( + derive_address_and_node, + get_address_attributes, + validate_full_path, + validate_output_address, +) +from .layout import confirm_sending, confirm_transaction + +if False: + from typing import Dict, List, Tuple + from trezor.messages.CardanoSignTx import CardanoSignTx + from trezor.messages.CardanoTxInputType import CardanoTxInputType + from trezor.messages.CardanoTxOutputType import CardanoTxOutputType + # the maximum allowed change address. this should be large enough for normal # use and still allow to quickly brute-force the correct bip32 path MAX_CHANGE_ADDRESS_INDEX = const(1000000) -ACCOUNT_PREFIX_DEPTH = const(2) +ACCOUNT_PATH_INDEX = const(2) +BIP_PATH_LENGTH = const(5) -KNOWN_PROTOCOL_MAGICS = {764824073: "Mainnet", 1097911063: "Testnet"} - - -# we consider addresses from the external chain as possible change addresses as well -def is_change(output, inputs): - for input in inputs: - inp = input.address_n - if ( - not output[:ACCOUNT_PREFIX_DEPTH] == inp[:ACCOUNT_PREFIX_DEPTH] - or not output[-2] < 2 - or not output[-1] < MAX_CHANGE_ADDRESS_INDEX - ): - return False - return True - - -async def show_tx( - ctx, - outputs: list, - outcoins: list, - fee: int, - network_name: str, - raw_inputs: list, - raw_outputs: list, -) -> None: - for index, output in enumerate(outputs): - if is_change(raw_outputs[index].address_n, raw_inputs): - continue - - await confirm_sending(ctx, outcoins[index], output) - - total_amount = sum(outcoins) - await confirm_transaction(ctx, total_amount, fee, network_name) - - -async def request_transaction(ctx, tx_req: CardanoTxRequest, index: int): - tx_req.tx_index = index - return await ctx.call(tx_req, CardanoTxAck) +LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000 @seed.with_keychain -async def sign_tx(ctx, msg, keychain: seed.Keychain): - progress.init(msg.transactions_count, "Loading data") - +async def sign_tx( + ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain +) -> CardanoSignedTx: try: - attested = len(msg.inputs) * [False] - input_coins_sum = 0 - # request transactions - tx_req = CardanoTxRequest() - - for index in range(msg.transactions_count): - progress.advance() - tx_ack = await request_transaction(ctx, tx_req, index) - tx_hash = hashlib.blake2b( - data=bytes(tx_ack.transaction), outlen=32 - ).digest() - tx_decoded = cbor.decode(tx_ack.transaction) - for i, input in enumerate(msg.inputs): - if not attested[i] and input.prev_hash == tx_hash: - attested[i] = True - outputs = tx_decoded[1] - amount = outputs[input.prev_index][1] - input_coins_sum += amount - - if not all(attested): - raise wire.ProcessError( - "No tx data sent for input " + str(attested.index(False)) - ) - - transaction = Transaction( - msg.inputs, msg.outputs, keychain, msg.protocol_magic, input_coins_sum - ) + if msg.fee > LOVELACE_MAX_SUPPLY: + raise wire.ProcessError("Fee is out of range!") for i in msg.inputs: await validate_path(ctx, validate_full_path, keychain, i.address_n, CURVE) + _validate_outputs(keychain, msg.outputs, msg.protocol_magic) + + # display the transaction in UI + await _show_tx(ctx, keychain, msg) + # sign the transaction bundle and prepare the result - tx_body, tx_hash = transaction.serialise_tx() - tx = CardanoSignedTx(tx_body=tx_body, tx_hash=tx_hash) + serialized_tx, tx_hash = _serialize_tx(keychain, msg) + tx = CardanoSignedTx(serialized_tx=serialized_tx, tx_hash=tx_hash) except ValueError as e: if __debug__: log.exception(__name__, e) raise wire.ProcessError("Signing failed") - # display the transaction in UI - await show_tx( - ctx, - transaction.output_addresses, - transaction.outgoing_coins, - transaction.fee, - transaction.network_name, - transaction.inputs, - transaction.outputs, + return tx + + +def _validate_outputs( + keychain: seed.Keychain, outputs: List[CardanoTxOutputType], protocol_magic: int +) -> None: + total_amount = 0 + for output in outputs: + total_amount += output.amount + if output.address_n: + continue + elif output.address is not None: + validate_output_address(output.address, protocol_magic) + else: + raise wire.ProcessError("Each output must have address or address_n field!") + + if total_amount > LOVELACE_MAX_SUPPLY: + raise wire.ProcessError("Total transaction amount is out of range!") + + +def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, bytes]: + tx_body = _build_tx_body(keychain, msg) + tx_hash = _hash_tx_body(tx_body) + + witnesses_for_cbor = _build_witnesses( + keychain, msg.inputs, tx_hash, msg.protocol_magic ) + # byron witnesses have the key 2 in Shelley + witnesses = {2: witnesses_for_cbor} - return tx + serialized_tx = cbor.encode([tx_body, witnesses, None]) + return serialized_tx, tx_hash -class Transaction: - def __init__( - self, - inputs: list, - outputs: list, - keychain, - protocol_magic: int, - input_coins_sum: int, - ): - self.inputs = inputs - self.outputs = outputs - self.keychain = keychain - # attributes have to be always empty in current Cardano - self.attributes = {} - - self.network_name = KNOWN_PROTOCOL_MAGICS.get(protocol_magic, "Unknown") - self.protocol_magic = protocol_magic - self.input_coins_sum = input_coins_sum - - def _process_outputs(self): - change_addresses = [] - change_derivation_paths = [] - output_addresses = [] - outgoing_coins = [] - change_coins = [] - - for output in self.outputs: - if output.address_n: - address, _ = derive_address_and_node(self.keychain, output.address_n) - change_addresses.append(address) - change_derivation_paths.append(output.address_n) - change_coins.append(output.amount) - else: - if output.address is None: - raise wire.ProcessError( - "Each output must have address or address_n field!" - ) - if not is_safe_output_address(output.address): - raise wire.ProcessError("Invalid output address!") - - outgoing_coins.append(output.amount) - output_addresses.append(output.address) - - self.change_addresses = change_addresses - self.output_addresses = output_addresses - self.outgoing_coins = outgoing_coins - self.change_coins = change_coins - self.change_derivation_paths = change_derivation_paths - - def _build_witnesses(self, tx_aux_hash: str): - witnesses = [] - for input in self.inputs: - _, node = derive_address_and_node(self.keychain, input.address_n) - message = ( - b"\x01" + cbor.encode(self.protocol_magic) + b"\x58\x20" + tx_aux_hash - ) - signature = ed25519.sign_ext( - node.private_key(), node.private_key_ext(), message - ) - extended_public_key = ( - remove_ed25519_prefix(node.public_key()) + node.chain_code() - ) - witnesses.append( - [ - (input.type or 0), - cbor.Tagged(24, cbor.encode([extended_public_key, signature])), - ] - ) - return witnesses +def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict: + inputs_for_cbor = _build_inputs(msg.inputs) + outputs_for_cbor = _build_outputs(keychain, msg.outputs, msg.protocol_magic) - @staticmethod - def compute_fee(input_coins_sum: int, outgoing_coins: list, change_coins: list): - outgoing_coins_sum = sum(outgoing_coins) - change_coins_sum = sum(change_coins) + tx_body = { + 0: inputs_for_cbor, + 1: outputs_for_cbor, + 2: msg.fee, + 3: msg.ttl, + } - return input_coins_sum - outgoing_coins_sum - change_coins_sum + return tx_body - def serialise_tx(self): - self._process_outputs() +def _build_inputs(inputs: List[CardanoTxInputType]) -> List[Tuple[bytes, int]]: + return [(input.prev_hash, input.prev_index) for input in inputs] - inputs_cbor = [] - for input in self.inputs: - inputs_cbor.append( - [ - (input.type or 0), - cbor.Tagged(24, cbor.encode([input.prev_hash, input.prev_index])), - ] + +def _build_outputs( + keychain: seed.Keychain, outputs: List[CardanoTxOutputType], protocol_magic: int +) -> List[Tuple[bytes, int]]: + result = [] + for output in outputs: + amount = output.amount + if output.address_n: + address, _ = derive_address_and_node( + keychain, output.address_n, protocol_magic ) + else: + address = output.address - inputs_cbor = cbor.IndefiniteLengthArray(inputs_cbor) + result.append((base58.decode(address), amount)) - outputs_cbor = [] - for index, address in enumerate(self.output_addresses): - outputs_cbor.append( - [cbor.Raw(base58.decode(address)), self.outgoing_coins[index]] - ) + return result - for index, address in enumerate(self.change_addresses): - outputs_cbor.append( - [cbor.Raw(base58.decode(address)), self.change_coins[index]] - ) - outputs_cbor = cbor.IndefiniteLengthArray(outputs_cbor) +def _hash_tx_body(tx_body: Dict) -> bytes: + tx_body_cbor = cbor.encode(tx_body) + return hashlib.blake2b(data=tx_body_cbor, outlen=32).digest() - tx_aux_cbor = [inputs_cbor, outputs_cbor, self.attributes] - tx_hash = hashlib.blake2b(data=cbor.encode(tx_aux_cbor), outlen=32).digest() - witnesses = self._build_witnesses(tx_hash) - tx_body = cbor.encode([tx_aux_cbor, witnesses]) +def _build_witnesses( + keychain: seed.Keychain, + inputs: List[CardanoTxInputType], + tx_body_hash: bytes, + protocol_magic: int, +) -> List[Tuple[bytes, bytes, bytes, bytes]]: + result = [] + for input in inputs: + node = keychain.derive(input.address_n) - self.fee = self.compute_fee( - self.input_coins_sum, self.outgoing_coins, self.change_coins + public_key = remove_ed25519_prefix(node.public_key()) + signature = ed25519.sign_ext( + node.private_key(), node.private_key_ext(), tx_body_hash ) + chain_code = node.chain_code() + address_attributes = cbor.encode(get_address_attributes(protocol_magic)) + + result.append((public_key, signature, chain_code, address_attributes)) + + return result - return tx_body, tx_hash + +async def _show_tx( + ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx +) -> None: + total_amount = 0 + for output in msg.outputs: + if _should_hide_output(output.address_n, msg.inputs): + continue + + total_amount += output.amount + + if not output.address: + address, _ = derive_address_and_node( + keychain, output.address_n, msg.protocol_magic + ) + else: + address = output.address + + await confirm_sending(ctx, output.amount, address) + + await confirm_transaction(ctx, total_amount, msg.fee, msg.protocol_magic) + + +# addresses from the same account as inputs should be hidden +def _should_hide_output(output: List[int], inputs: List[CardanoTxInputType]) -> bool: + for input in inputs: + inp = input.address_n + if ( + len(output) != BIP_PATH_LENGTH + or output[: (ACCOUNT_PATH_INDEX + 1)] != inp[: (ACCOUNT_PATH_INDEX + 1)] + or output[-2] >= 2 + or output[-1] >= MAX_CHANGE_ADDRESS_INDEX + ): + return False + return True diff --git a/core/src/apps/common/cbor.py b/core/src/apps/common/cbor.py index dccd722ae..f91b93934 100644 --- a/core/src/apps/common/cbor.py +++ b/core/src/apps/common/cbor.py @@ -32,6 +32,7 @@ _CBOR_VAR_FOLLOWS = const(0x1F) _CBOR_FALSE = const(0x14) _CBOR_TRUE = const(0x15) +_CBOR_NULL = const(0x16) _CBOR_BREAK = const(0x1F) _CBOR_RAW_TAG = const(0x18) @@ -67,7 +68,7 @@ def _cbor_encode(value: Value) -> Iterable[bytes]: encoded_value = value.encode() yield _header(_CBOR_TEXT_STRING, len(encoded_value)) yield encoded_value - elif isinstance(value, list): + elif isinstance(value, list) or isinstance(value, tuple): # definite-length valued list yield _header(_CBOR_ARRAY, len(value)) for x in value: @@ -91,8 +92,8 @@ def _cbor_encode(value: Value) -> Iterable[bytes]: yield bytes([_CBOR_PRIMITIVE + _CBOR_TRUE]) else: yield bytes([_CBOR_PRIMITIVE + _CBOR_FALSE]) - elif isinstance(value, Raw): - yield value.value + elif value is None: + yield bytes([_CBOR_PRIMITIVE + _CBOR_NULL]) else: if __debug__: log.debug(__name__, "not implemented (encode): %s", type(value)) @@ -194,6 +195,8 @@ def _cbor_decode(cbor: bytes) -> Tuple[Value, bytes]: return (False, cbor[1:]) elif fb_aux == _CBOR_TRUE: return (True, cbor[1:]) + elif fb_aux == _CBOR_NULL: + return (None, cbor[1:]) elif fb_aux == _CBOR_BREAK: return (cbor[0], cbor[1:]) else: @@ -217,11 +220,6 @@ class Tagged: ) -class Raw: - def __init__(self, value: Value): - self.value = value - - class IndefiniteLengthArray: def __init__(self, array: List[Value]) -> None: self.array = array diff --git a/core/src/trezor/messages/CardanoGetAddress.py b/core/src/trezor/messages/CardanoGetAddress.py index fa608a8b3..d6c8f35ec 100644 --- a/core/src/trezor/messages/CardanoGetAddress.py +++ b/core/src/trezor/messages/CardanoGetAddress.py @@ -17,13 +17,16 @@ class CardanoGetAddress(p.MessageType): self, address_n: List[int] = None, show_display: bool = None, + protocol_magic: int = None, ) -> None: self.address_n = address_n if address_n is not None else [] self.show_display = show_display + self.protocol_magic = protocol_magic @classmethod def get_fields(cls) -> Dict: return { 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), 2: ('show_display', p.BoolType, 0), + 3: ('protocol_magic', p.UVarintType, 0), } diff --git a/core/src/trezor/messages/CardanoSignTx.py b/core/src/trezor/messages/CardanoSignTx.py index ac9879e0f..74d817674 100644 --- a/core/src/trezor/messages/CardanoSignTx.py +++ b/core/src/trezor/messages/CardanoSignTx.py @@ -20,19 +20,22 @@ class CardanoSignTx(p.MessageType): self, inputs: List[CardanoTxInputType] = None, outputs: List[CardanoTxOutputType] = None, - transactions_count: int = None, protocol_magic: int = None, + fee: int = None, + ttl: int = None, ) -> None: self.inputs = inputs if inputs is not None else [] self.outputs = outputs if outputs is not None else [] - self.transactions_count = transactions_count self.protocol_magic = protocol_magic + self.fee = fee + self.ttl = ttl @classmethod def get_fields(cls) -> Dict: return { 1: ('inputs', CardanoTxInputType, p.FLAG_REPEATED), 2: ('outputs', CardanoTxOutputType, p.FLAG_REPEATED), - 3: ('transactions_count', p.UVarintType, 0), 5: ('protocol_magic', p.UVarintType, 0), + 6: ('fee', p.UVarintType, 0), + 7: ('ttl', p.UVarintType, 0), } diff --git a/core/src/trezor/messages/CardanoSignedTx.py b/core/src/trezor/messages/CardanoSignedTx.py index 0633eaa45..00ce12f3b 100644 --- a/core/src/trezor/messages/CardanoSignedTx.py +++ b/core/src/trezor/messages/CardanoSignedTx.py @@ -16,14 +16,14 @@ class CardanoSignedTx(p.MessageType): def __init__( self, tx_hash: bytes = None, - tx_body: bytes = None, + serialized_tx: bytes = None, ) -> None: self.tx_hash = tx_hash - self.tx_body = tx_body + self.serialized_tx = serialized_tx @classmethod def get_fields(cls) -> Dict: return { 1: ('tx_hash', p.BytesType, 0), - 2: ('tx_body', p.BytesType, 0), + 2: ('serialized_tx', p.BytesType, 0), } diff --git a/core/src/trezor/messages/CardanoTxAck.py b/core/src/trezor/messages/CardanoTxAck.py deleted file mode 100644 index 016973147..000000000 --- a/core/src/trezor/messages/CardanoTxAck.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 CardanoTxAck(p.MessageType): - MESSAGE_WIRE_TYPE = 309 - - def __init__( - self, - transaction: bytes = None, - ) -> None: - self.transaction = transaction - - @classmethod - def get_fields(cls) -> Dict: - return { - 1: ('transaction', p.BytesType, 0), - } diff --git a/core/src/trezor/messages/CardanoTxInputType.py b/core/src/trezor/messages/CardanoTxInputType.py index 668e612a7..829949f9e 100644 --- a/core/src/trezor/messages/CardanoTxInputType.py +++ b/core/src/trezor/messages/CardanoTxInputType.py @@ -17,12 +17,10 @@ class CardanoTxInputType(p.MessageType): address_n: List[int] = None, prev_hash: bytes = None, prev_index: int = None, - type: int = None, ) -> None: self.address_n = address_n if address_n is not None else [] self.prev_hash = prev_hash self.prev_index = prev_index - self.type = type @classmethod def get_fields(cls) -> Dict: @@ -30,5 +28,4 @@ class CardanoTxInputType(p.MessageType): 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), 2: ('prev_hash', p.BytesType, 0), 3: ('prev_index', p.UVarintType, 0), - 4: ('type', p.UVarintType, 0), } diff --git a/core/src/trezor/messages/CardanoTxRequest.py b/core/src/trezor/messages/CardanoTxRequest.py deleted file mode 100644 index f47134f00..000000000 --- a/core/src/trezor/messages/CardanoTxRequest.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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 CardanoTxRequest(p.MessageType): - MESSAGE_WIRE_TYPE = 304 - - def __init__( - self, - tx_index: int = None, - tx_hash: bytes = None, - tx_body: bytes = None, - ) -> None: - self.tx_index = tx_index - self.tx_hash = tx_hash - self.tx_body = tx_body - - @classmethod - def get_fields(cls) -> Dict: - return { - 1: ('tx_index', p.UVarintType, 0), - 2: ('tx_hash', p.BytesType, 0), - 3: ('tx_body', p.BytesType, 0), - } diff --git a/core/src/trezor/messages/MessageType.py b/core/src/trezor/messages/MessageType.py index 7a56e17be..3a855bf14 100644 --- a/core/src/trezor/messages/MessageType.py +++ b/core/src/trezor/messages/MessageType.py @@ -133,12 +133,10 @@ if not utils.BITCOIN_ONLY: StellarBumpSequenceOp = 221 # type: Literal[221] StellarSignedTx = 230 # type: Literal[230] CardanoSignTx = 303 # type: Literal[303] - CardanoTxRequest = 304 # type: Literal[304] CardanoGetPublicKey = 305 # type: Literal[305] CardanoPublicKey = 306 # type: Literal[306] CardanoGetAddress = 307 # type: Literal[307] CardanoAddress = 308 # type: Literal[308] - CardanoTxAck = 309 # type: Literal[309] CardanoSignedTx = 310 # type: Literal[310] RippleGetAddress = 400 # type: Literal[400] RippleAddress = 401 # type: Literal[401] diff --git a/core/tests/test_apps.cardano.address.py b/core/tests/test_apps.cardano.address.py index 80b4ef282..56ce12246 100644 --- a/core/tests/test_apps.cardano.address.py +++ b/core/tests/test_apps.cardano.address.py @@ -4,6 +4,7 @@ from apps.common import seed from apps.common import HARDENED from trezor.crypto import bip32, slip39 if not utils.BITCOIN_ONLY: + from apps.cardano import protocol_magics from apps.cardano.address import ( _get_address_root, _address_hash, @@ -31,7 +32,7 @@ class TestCardanoAddress(unittest.TestCase): for i, expected in enumerate(addresses): # 44'/1815'/0'/0/i' - address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i]) + address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i], protocol_magics.MAINNET) self.assertEqual(expected, address) nodes = [ @@ -56,7 +57,7 @@ class TestCardanoAddress(unittest.TestCase): ] for i, (priv, ext, pub, chain) in enumerate(nodes): - _, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i]) + _, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i], protocol_magics.MAINNET) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) @@ -78,7 +79,7 @@ class TestCardanoAddress(unittest.TestCase): for i, expected in enumerate(addresses): # 44'/1815'/0'/0/i - address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i]) + address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET) self.assertEqual(address, expected) nodes = [ @@ -103,7 +104,7 @@ class TestCardanoAddress(unittest.TestCase): ] for i, (priv, ext, pub, chain) in enumerate(nodes): - _, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i]) + _, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) @@ -119,7 +120,7 @@ class TestCardanoAddress(unittest.TestCase): keychain = Keychain(node) # 44'/1815' - address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815]) + address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815], protocol_magics.MAINNET) self.assertEqual(address, "Ae2tdPwUPEZ2FGHX3yCKPSbSgyuuTYgMxNq652zKopxT4TuWvEd8Utd92w3") priv, ext, pub, chain = ( @@ -129,7 +130,7 @@ class TestCardanoAddress(unittest.TestCase): b"02ac67c59a8b0264724a635774ca2c242afa10d7ab70e2bf0a8f7d4bb10f1f7a" ) - _, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815]) + _, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815], protocol_magics.MAINNET) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) @@ -170,14 +171,6 @@ class TestCardanoAddress(unittest.TestCase): for path in correct_derivation_paths: self.assertTrue(validate_full_path(path)) - def test_get_address_root_scheme(self): - mnemonic = "all all all all all all all all all all all all" - passphrase = "" - root_node = bip32.from_mnemonic_cardano(mnemonic, passphrase) - - address_root = _get_address_root(root_node, {1: b'X\x1cr,zu\x81?\xaf\xde\x9f\xf9\xe4\xd4\x90\xadH$\xe9\xf3\x88\x16\xcb\xd2)\x02M\x0c#\xde'}) - self.assertEqual(address_root, b'\xb3\xbbS\xa8;uN:E=\xe8\xe5\x9c\x18\xbcn\xcf\xd0c\xba\x0e\xba\xaelL}\xba\xbb') - def test_slip39_128(self): mnemonics = [ "extra extend academic bishop cricket bundle tofu goat apart victim " @@ -235,7 +228,7 @@ class TestCardanoAddress(unittest.TestCase): for i, (address, priv, ext, pub, chain) in enumerate(nodes): # 44'/1815'/0'/0/i - a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i]) + a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET) self.assertEqual(a, address) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) @@ -299,12 +292,31 @@ class TestCardanoAddress(unittest.TestCase): for i, (address, priv, ext, pub, chain) in enumerate(nodes): # 44'/1815'/0'/0/i - a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i]) + a, n = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.MAINNET) self.assertEqual(a, address) self.assertEqual(hexlify(n.private_key()), priv) self.assertEqual(hexlify(n.private_key_ext()), ext) self.assertEqual(hexlify(seed.remove_ed25519_prefix(n.public_key())), pub) self.assertEqual(hexlify(n.chain_code()), chain) + def test_testnet_address(self): + mnemonic = "all all all all all all all all all all all all" + passphrase = "" + node = bip32.from_mnemonic_cardano(mnemonic, passphrase) + node.derive_cardano(0x80000000 | 44) + node.derive_cardano(0x80000000 | 1815) + keychain = Keychain(node) + + addresses = [ + "2657WMsDfac5F3zbgs9BwNWx3dhGAJERkAL93gPa68NJ2i8mbCHm2pLUHWSj8Mfea", + "2657WMsDfac6ezKWszxLFqJjSUgpg9NgxKc1koqi24sVpRaPhiwMaExk4useKn5HA", + "2657WMsDfac7hr1ioJGr6g7r6JRx4r1My8Rj91tcPTeVjJDpfBYKURrPG2zVLx2Sq", + ] + + for i, expected in enumerate(addresses): + # 44'/1815'/0'/0/i' + address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i], protocol_magics.TESTNET) + self.assertEqual(expected, address) + if __name__ == '__main__': unittest.main() diff --git a/core/tests/test_apps.cardano.sign_tx.py b/core/tests/test_apps.cardano.sign_tx.py new file mode 100644 index 000000000..f90f00c02 --- /dev/null +++ b/core/tests/test_apps.cardano.sign_tx.py @@ -0,0 +1,87 @@ +from common import * +from apps.common import HARDENED +from trezor.messages.CardanoTxInputType import CardanoTxInputType + +if not utils.BITCOIN_ONLY: + from apps.cardano.sign_tx import _should_hide_output + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestCardanoSignTransaction(unittest.TestCase): + def test_should_show_outputs(self): + outputs_to_show = [ + # output is from the same address as input + ( + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], + [[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]], + ), + # output is from the same account but from different addresses + ( + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], + [ + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 1], + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 2], + ], + ), + # both output and input are from account 2 + ( + [44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0], + [ + [44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0], + [44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 1], + [44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 2], + ], + ), + ] + outputs_to_hide = [ + # output is from different account + ( + [44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0], + [[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]], + ), + # output path is not complete + ( + [44 | HARDENED, 1815 | HARDENED], + [[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]], + ), + # output path is not complete + ( + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED], + [[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]], + ), + # one of the inputs has different account than output + ( + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], + [ + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0], + [44 | HARDENED, 1815 | HARDENED, 2 | HARDENED, 0, 0], + ], + ), + # staking output path + ( + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0,], + [[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]], + ), + # output address too large + ( + [44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 1000001], + [[44 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0]], + ), + ] + + for output_path, input_paths in outputs_to_show: + inputs = [ + CardanoTxInputType(input_path, "", 0) for input_path in input_paths + ] + self.assertTrue(_should_hide_output(output_path, inputs)) + + for output_path, input_paths in outputs_to_hide: + inputs = [ + CardanoTxInputType(input_path, "", 0) for input_path in input_paths + ] + self.assertFalse(_should_hide_output(output_path, inputs)) + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_apps.common.cbor.py b/core/tests/test_apps.common.cbor.py index 5b6e882b8..39454b54a 100644 --- a/core/tests/test_apps.common.cbor.py +++ b/core/tests/test_apps.common.cbor.py @@ -65,10 +65,28 @@ class TestCardanoCbor(unittest.TestCase): # boolean (True, 'f5'), (False, 'f4'), + + # null + (None, 'f6'), ] for val, encoded in test_vectors: self.assertEqual(unhexlify(encoded), encode(val)) self.assertEqual(val, decode(unhexlify(encoded))) + def test_cbor_tuples(self): + """ + Tuples should be encoded as arrays and decoded back as lists. + """ + test_vectors = [ + ([], '80'), + ([1, 2, 3], '83010203'), + ([1, [2, 3], [4, 5]], '8301820203820405'), + (list(range(1, 26)), '98190102030405060708090a0b0c0d0e0f101112131415161718181819'), + ] + for val, encoded in test_vectors: + value_tuple = tuple(val) + self.assertEqual(unhexlify(encoded), encode(value_tuple)) + self.assertEqual(val, decode(unhexlify(encoded))) + if __name__ == '__main__': unittest.main() diff --git a/python/src/trezorlib/cardano.py b/python/src/trezorlib/cardano.py index 18e744dc8..9ee483aa9 100644 --- a/python/src/trezorlib/cardano.py +++ b/python/src/trezorlib/cardano.py @@ -17,48 +17,51 @@ from typing import List from . import messages, tools -from .tools import expect, session +from .tools import expect -REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs", "transactions") -REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index", "type") +PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42} + +REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs") +REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index") @expect(messages.CardanoAddress, field="address") -def get_address(client, address_n, show_display=False): +def get_address( + client, address_n: List[int], protocol_magic: int, show_display=False +) -> messages.CardanoAddress: return client.call( - messages.CardanoGetAddress(address_n=address_n, show_display=show_display) + messages.CardanoGetAddress( + address_n=address_n, + protocol_magic=protocol_magic, + show_display=show_display, + ) ) @expect(messages.CardanoPublicKey) -def get_public_key(client, address_n): +def get_public_key(client, address_n: List[int]) -> messages.CardanoPublicKey: return client.call(messages.CardanoGetPublicKey(address_n=address_n)) -@session +@expect(messages.CardanoSignedTx) def sign_tx( client, inputs: List[messages.CardanoTxInputType], outputs: List[messages.CardanoTxOutputType], - transactions: List[bytes], - protocol_magic, -): + fee: int, + ttl: int, + protocol_magic: int, +) -> messages.CardanoSignedTx: response = client.call( messages.CardanoSignTx( inputs=inputs, outputs=outputs, - transactions_count=len(transactions), + fee=fee, + ttl=ttl, protocol_magic=protocol_magic, ) ) - while isinstance(response, messages.CardanoTxRequest): - tx_index = response.tx_index - - transaction_data = bytes.fromhex(transactions[tx_index]) - ack_message = messages.CardanoTxAck(transaction=transaction_data) - response = client.call(ack_message) - return response @@ -72,7 +75,6 @@ def create_input(input) -> messages.CardanoTxInputType: address_n=tools.parse_path(path), prev_hash=bytes.fromhex(input["prev_hash"]), prev_index=input["prev_index"], - type=input["type"], ) diff --git a/python/src/trezorlib/cli/cardano.py b/python/src/trezorlib/cli/cardano.py index fbb7e9d06..d0d1400c2 100644 --- a/python/src/trezorlib/cli/cardano.py +++ b/python/src/trezorlib/cli/cardano.py @@ -37,32 +37,36 @@ def cli(): required=True, help="Transaction in JSON format", ) -@click.option("-N", "--network", type=int, default=1) +@click.option("-p", "--protocol-magic", type=int, default=1) @with_client -def sign_tx(client, file, network): +def sign_tx(client, file, protocol_magic): """Sign Cardano transaction.""" transaction = json.load(file) inputs = [cardano.create_input(input) for input in transaction["inputs"]] outputs = [cardano.create_output(output) for output in transaction["outputs"]] - transactions = transaction["transactions"] + fee = transaction["fee"] + ttl = transaction["ttl"] - signed_transaction = cardano.sign_tx(client, inputs, outputs, transactions, network) + signed_transaction = cardano.sign_tx( + client, inputs, outputs, fee, ttl, protocol_magic + ) return { "tx_hash": signed_transaction.tx_hash.hex(), - "tx_body": signed_transaction.tx_body.hex(), + "serialized_tx": signed_transaction.serialized_tx.hex(), } @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) +@click.option("-p", "--protocol-magic", type=int, default=1) @with_client -def get_address(client, address, show_display): +def get_address(client, address, show_display, protocol_magic): """Get Cardano address.""" address_n = tools.parse_path(address) - return cardano.get_address(client, address_n, show_display) + return cardano.get_address(client, address_n, protocol_magic, show_display) @cli.command() diff --git a/python/src/trezorlib/messages/CardanoGetAddress.py b/python/src/trezorlib/messages/CardanoGetAddress.py index 546c9cfa3..c89bd872a 100644 --- a/python/src/trezorlib/messages/CardanoGetAddress.py +++ b/python/src/trezorlib/messages/CardanoGetAddress.py @@ -17,13 +17,16 @@ class CardanoGetAddress(p.MessageType): self, address_n: List[int] = None, show_display: bool = None, + protocol_magic: int = None, ) -> None: self.address_n = address_n if address_n is not None else [] self.show_display = show_display + self.protocol_magic = protocol_magic @classmethod def get_fields(cls) -> Dict: return { 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), 2: ('show_display', p.BoolType, 0), + 3: ('protocol_magic', p.UVarintType, 0), } diff --git a/python/src/trezorlib/messages/CardanoSignTx.py b/python/src/trezorlib/messages/CardanoSignTx.py index b2758e06d..2b97a4af4 100644 --- a/python/src/trezorlib/messages/CardanoSignTx.py +++ b/python/src/trezorlib/messages/CardanoSignTx.py @@ -20,19 +20,22 @@ class CardanoSignTx(p.MessageType): self, inputs: List[CardanoTxInputType] = None, outputs: List[CardanoTxOutputType] = None, - transactions_count: int = None, protocol_magic: int = None, + fee: int = None, + ttl: int = None, ) -> None: self.inputs = inputs if inputs is not None else [] self.outputs = outputs if outputs is not None else [] - self.transactions_count = transactions_count self.protocol_magic = protocol_magic + self.fee = fee + self.ttl = ttl @classmethod def get_fields(cls) -> Dict: return { 1: ('inputs', CardanoTxInputType, p.FLAG_REPEATED), 2: ('outputs', CardanoTxOutputType, p.FLAG_REPEATED), - 3: ('transactions_count', p.UVarintType, 0), 5: ('protocol_magic', p.UVarintType, 0), + 6: ('fee', p.UVarintType, 0), + 7: ('ttl', p.UVarintType, 0), } diff --git a/python/src/trezorlib/messages/CardanoSignedTx.py b/python/src/trezorlib/messages/CardanoSignedTx.py index c9da42130..fb0326df9 100644 --- a/python/src/trezorlib/messages/CardanoSignedTx.py +++ b/python/src/trezorlib/messages/CardanoSignedTx.py @@ -16,14 +16,14 @@ class CardanoSignedTx(p.MessageType): def __init__( self, tx_hash: bytes = None, - tx_body: bytes = None, + serialized_tx: bytes = None, ) -> None: self.tx_hash = tx_hash - self.tx_body = tx_body + self.serialized_tx = serialized_tx @classmethod def get_fields(cls) -> Dict: return { 1: ('tx_hash', p.BytesType, 0), - 2: ('tx_body', p.BytesType, 0), + 2: ('serialized_tx', p.BytesType, 0), } diff --git a/python/src/trezorlib/messages/CardanoTxAck.py b/python/src/trezorlib/messages/CardanoTxAck.py deleted file mode 100644 index 5b07e9e08..000000000 --- a/python/src/trezorlib/messages/CardanoTxAck.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 CardanoTxAck(p.MessageType): - MESSAGE_WIRE_TYPE = 309 - - def __init__( - self, - transaction: bytes = None, - ) -> None: - self.transaction = transaction - - @classmethod - def get_fields(cls) -> Dict: - return { - 1: ('transaction', p.BytesType, 0), - } diff --git a/python/src/trezorlib/messages/CardanoTxInputType.py b/python/src/trezorlib/messages/CardanoTxInputType.py index ce2ccdb2c..4531f395f 100644 --- a/python/src/trezorlib/messages/CardanoTxInputType.py +++ b/python/src/trezorlib/messages/CardanoTxInputType.py @@ -17,12 +17,10 @@ class CardanoTxInputType(p.MessageType): address_n: List[int] = None, prev_hash: bytes = None, prev_index: int = None, - type: int = None, ) -> None: self.address_n = address_n if address_n is not None else [] self.prev_hash = prev_hash self.prev_index = prev_index - self.type = type @classmethod def get_fields(cls) -> Dict: @@ -30,5 +28,4 @@ class CardanoTxInputType(p.MessageType): 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), 2: ('prev_hash', p.BytesType, 0), 3: ('prev_index', p.UVarintType, 0), - 4: ('type', p.UVarintType, 0), } diff --git a/python/src/trezorlib/messages/CardanoTxRequest.py b/python/src/trezorlib/messages/CardanoTxRequest.py deleted file mode 100644 index 57b48b875..000000000 --- a/python/src/trezorlib/messages/CardanoTxRequest.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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 CardanoTxRequest(p.MessageType): - MESSAGE_WIRE_TYPE = 304 - - def __init__( - self, - tx_index: int = None, - tx_hash: bytes = None, - tx_body: bytes = None, - ) -> None: - self.tx_index = tx_index - self.tx_hash = tx_hash - self.tx_body = tx_body - - @classmethod - def get_fields(cls) -> Dict: - return { - 1: ('tx_index', p.UVarintType, 0), - 2: ('tx_hash', p.BytesType, 0), - 3: ('tx_body', p.BytesType, 0), - } diff --git a/python/src/trezorlib/messages/MessageType.py b/python/src/trezorlib/messages/MessageType.py index f0483ec35..91a9718c7 100644 --- a/python/src/trezorlib/messages/MessageType.py +++ b/python/src/trezorlib/messages/MessageType.py @@ -130,12 +130,10 @@ StellarManageDataOp = 220 # type: Literal[220] StellarBumpSequenceOp = 221 # type: Literal[221] StellarSignedTx = 230 # type: Literal[230] CardanoSignTx = 303 # type: Literal[303] -CardanoTxRequest = 304 # type: Literal[304] CardanoGetPublicKey = 305 # type: Literal[305] CardanoPublicKey = 306 # type: Literal[306] CardanoGetAddress = 307 # type: Literal[307] CardanoAddress = 308 # type: Literal[308] -CardanoTxAck = 309 # type: Literal[309] CardanoSignedTx = 310 # type: Literal[310] RippleGetAddress = 400 # type: Literal[400] RippleAddress = 401 # type: Literal[401] diff --git a/python/src/trezorlib/messages/__init__.py b/python/src/trezorlib/messages/__init__.py index b664538a0..0460baec4 100644 --- a/python/src/trezorlib/messages/__init__.py +++ b/python/src/trezorlib/messages/__init__.py @@ -26,10 +26,8 @@ from .CardanoGetPublicKey import CardanoGetPublicKey from .CardanoPublicKey import CardanoPublicKey from .CardanoSignTx import CardanoSignTx from .CardanoSignedTx import CardanoSignedTx -from .CardanoTxAck import CardanoTxAck from .CardanoTxInputType import CardanoTxInputType from .CardanoTxOutputType import CardanoTxOutputType -from .CardanoTxRequest import CardanoTxRequest from .ChangePin import ChangePin from .ChangeWipeCode import ChangeWipeCode from .CipherKeyValue import CipherKeyValue diff --git a/tests/device_tests/test_msg_cardano_get_address.py b/tests/device_tests/test_msg_cardano_get_address.py index 22cefe115..9fa68b5a5 100644 --- a/tests/device_tests/test_msg_cardano_get_address.py +++ b/tests/device_tests/test_msg_cardano_get_address.py @@ -16,7 +16,7 @@ import pytest -from trezorlib.cardano import get_address +from trezorlib.cardano import PROTOCOL_MAGICS, get_address from trezorlib.tools import parse_path from ..common import MNEMONIC12 @@ -26,24 +26,44 @@ from ..common import MNEMONIC12 @pytest.mark.cardano @pytest.mark.skip_t1 # T1 support is not planned @pytest.mark.parametrize( - "path,expected_address", + "path,protocol_magic,expected_address", [ + # mainnet ( "m/44'/1815'/0'/0/0", + PROTOCOL_MAGICS["mainnet"], "Ae2tdPwUPEZLCq3sFv4wVYxwqjMH2nUzBVt1HFr4v87snYrtYq3d3bq2PUQ", ), ( "m/44'/1815'/0'/0/1", + PROTOCOL_MAGICS["mainnet"], "Ae2tdPwUPEZEY6pVJoyuNNdLp7VbMB7U7qfebeJ7XGunk5Z2eHarkcN1bHK", ), ( "m/44'/1815'/0'/0/2", + PROTOCOL_MAGICS["mainnet"], "Ae2tdPwUPEZ3gZD1QeUHvAqadAV59Zid6NP9VCR9BG5LLAja9YtBUgr6ttK", ), + # testnet + # data generated by code under test + ( + "m/44'/1815'/0'/0/0", + PROTOCOL_MAGICS["testnet"], + "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r", + ), + ( + "m/44'/1815'/0'/0/1", + PROTOCOL_MAGICS["testnet"], + "2657WMsDfac61ebUDw53WUX49Dcfya8S8G7iYbhN4nP8JSFuh38T1LuFax1bUnhxA", + ), + ( + "m/44'/1815'/0'/0/2", + PROTOCOL_MAGICS["testnet"], + "2657WMsDfac5PMpEsxc1md3pgZKUZRZ11MUK8tjkDHBQG9b3TMBsTQc4PmmumVrcn", + ), ], ) @pytest.mark.setup_client(mnemonic=MNEMONIC12) -def test_cardano_get_address(client, path, expected_address): - # data from https://iancoleman.io/bip39/ - address = get_address(client, parse_path(path)) +def test_cardano_get_address(client, path, protocol_magic, expected_address): + address = get_address(client, parse_path(path), protocol_magic) assert address == expected_address diff --git a/tests/device_tests/test_msg_cardano_get_address_slip39_basic.py b/tests/device_tests/test_msg_cardano_get_address_slip39_basic.py index 159a8a0ac..9c357ab8e 100644 --- a/tests/device_tests/test_msg_cardano_get_address_slip39_basic.py +++ b/tests/device_tests/test_msg_cardano_get_address_slip39_basic.py @@ -16,7 +16,7 @@ import pytest -from trezorlib.cardano import get_address +from trezorlib.cardano import PROTOCOL_MAGICS, get_address from trezorlib.tools import parse_path from ..common import MNEMONIC_SLIP39_BASIC_20_3of6 @@ -27,27 +27,48 @@ from ..common import MNEMONIC_SLIP39_BASIC_20_3of6 @pytest.mark.skip_t1 # T1 support is not planned @pytest.mark.skip_ui @pytest.mark.parametrize( - "path,expected_address", + "path,protocol_magic,expected_address", [ + # mainnet ( "m/44'/1815'/0'/0/0", + PROTOCOL_MAGICS["mainnet"], "Ae2tdPwUPEYxF9NAMNdd3v2LZoMeWp7gCZiDb6bZzFQeeVASzoP7HC4V9s6", ), ( "m/44'/1815'/0'/0/1", + PROTOCOL_MAGICS["mainnet"], "Ae2tdPwUPEZ1TjYcvfkWAbiHtGVxv4byEHHZoSyQXjPJ362DifCe1ykgqgy", ), ( "m/44'/1815'/0'/0/2", + PROTOCOL_MAGICS["mainnet"], "Ae2tdPwUPEZGXmSbda1kBNfyhRQGRcQxJFdk7mhWZXAGnapyejv2b2U3aRb", ), + # testnet + # data generated by code under test + ( + "m/44'/1815'/0'/0/0", + PROTOCOL_MAGICS["testnet"], + "2657WMsDfac7SH1rhA2PWBggGAPrKyLt1r9SL9gajPxxcH15ZxuCUb4aK9mQ9w7dU", + ), + ( + "m/44'/1815'/0'/0/1", + PROTOCOL_MAGICS["testnet"], + "2657WMsDfac6Cmfg4Varph2qyLKGi2K9E8jrtvjHVzfSjmbTMGy5sY3HpxCKsmtDA", + ), + ( + "m/44'/1815'/0'/0/2", + PROTOCOL_MAGICS["testnet"], + "2657WMsDfac5ANb5Mw6Rbgdz6nvs2Tu675vGbbVSzXQbAkQuMWtqBvEeKTrHNtXY7", + ), ], ) @pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True) -def test_cardano_get_address(client, path, expected_address): +def test_cardano_get_address(client, path, protocol_magic, expected_address): # enter passphrase assert client.features.passphrase_protection is True client.use_passphrase("TREZOR") - address = get_address(client, parse_path(path)) + address = get_address(client, parse_path(path), protocol_magic) assert address == expected_address diff --git a/tests/device_tests/test_msg_cardano_sign_transaction.py b/tests/device_tests/test_msg_cardano_sign_transaction.py index 6c2aa99f5..116ef3e05 100644 --- a/tests/device_tests/test_msg_cardano_sign_transaction.py +++ b/tests/device_tests/test_msg_cardano_sign_transaction.py @@ -17,21 +17,42 @@ import pytest from trezorlib import cardano, messages +from trezorlib.cardano import PROTOCOL_MAGICS from trezorlib.exceptions import TrezorFailure -PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 1097911063} - -SAMPLE_INPUTS = [ - { - "input": { - "path": "m/44'/1815'/0'/0/1", - "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", - "prev_index": 0, - "type": 0, - }, - "prev_tx": "839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0", - } -] +SAMPLE_INPUT = { + "path": "m/44'/1815'/0'/0/1", + "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", + "prev_index": 0, +} + +SAMPLE_OUTPUTS = { + "simple_output": { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112", + }, + "change_output": {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"}, + "invalid_address": { + "address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q", + "amount": "3003112", + }, + "invalid_cbor": { + "address": "5dnY6xgRcNUSLGa4gfqef2jGAMHb7koQs9EXErXLNC1LiMPUnhn8joXhvEJpWQtN3F4ysATcBvCn5tABgL3e4hPWapPHmcK5GJMSEaET5JafgAGwSrznzL1Mqa", + "amount": "3003112", + }, + "invalid_crc": { + "address": "Ae2tdPwUPEZ5YUb8sM3eS8JqKgrRLzhiu71crfuH2MFtqaYr5ACNRZR3Mbm", + "amount": "3003112", + }, + "large_simple_output": { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "449999999199999999", + }, + "testnet_output": { + "address": "2657WMsDfac5vydkak9a7BqGrsLqBzB7K3vT55rucZKYDmVnUCf6hXAFkZSTcUx7r", + "amount": "3003112", + }, +} VALID_VECTORS = [ # Mainnet transaction without change @@ -39,68 +60,51 @@ VALID_VECTORS = [ # protocol magic PROTOCOL_MAGICS["mainnet"], # inputs - [SAMPLE_INPUTS[0]["input"]], + [SAMPLE_INPUT], # outputs - [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - } - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], + [SAMPLE_OUTPUTS["simple_output"]], + # fee + 42, + # ttl + 10, # tx hash - "799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d", - # tx body - "82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a6355840312c01c27317415b0b8acc86aa789da877fe7e15c65b7ea4c4565d8739117f5f6d9d38bf5d058f7be809b2b9b06c1d79fc6b20f9a4d76d8c89bae333edf5680c", + "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6", + # serialized tx + "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018182582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e802182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840da07ac5246e3f20ebd1276476a4ae34a019dd4b264ffc22eea3c28cb0f1a6bb1c7764adeecf56bcb0bc6196fd1dbe080f3a7ef5b49f56980fe5b2881a4fdfa00582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6", ), # Mainnet transaction with change ( # protocol magic (mainnet) - 764824073, + PROTOCOL_MAGICS["mainnet"], # inputs - [ - { - "path": "m/44'/1815'/0'/0/1", - "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", - "prev_index": 0, - "type": 0, - } - ], + [SAMPLE_INPUT], # outputs - [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - }, - {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"}, - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], + [SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]], + # fee + 42, + # ttl + 10, # tx hash - "40bf94518f31aba7779dd99aa71fe867887bcb3e0bac2c6dc33d3f20ec74a6b1", - # tx body - "82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e88282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f4240ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63558400b47193163462023bdb72f03b2f6afc8e3645dbc9252cb70f7516da402ce3b8468e4a60929674de5862d6253315008e07b60aa189f5c455dd272ff1c84c89d0c", + "81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43", + # serialized tx + "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840d909b16038c4fd772a177038242e6793be39c735430b03ee924ed18026bd28d06920b5846247945f1204276e4b759aa5ac05a4a73b49ce705ab0e5e54a3a170e582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63541a0f6", ), # Testnet transaction ( # protocol magic PROTOCOL_MAGICS["testnet"], # inputs - [SAMPLE_INPUTS[0]["input"]], + [SAMPLE_INPUT], # outputs - [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - } - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], + [SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]], + # fee + 42, + # ttl + 10, # tx hash - "799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d", - # tx body - "82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63558403594ee7e2bfe4c84f886a8336cecb7c42983ce9a057345ebb6294a436087d8db93ca78cf514c7c48edff4c8435f690a5817951e2b55d2db729875ee7cc0f7d08", + "5dd03fb44cb88061b2a1c246981bb31adfe4f57be69b58badb5ae8f448450932", + # serialized tx + "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c98c3a558f39d1d993cc8770e8825c70a6d0f5a9eb243501c4526c29da10242182a001aa8566c011a000f424002182a030aa1028184582089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea5840fc30afdd0d4a6d8581e0f6abe895994d208fd382f2b23ff1553d711477a4fedbd1f68a76e7465c4816d5477f4287f7360acf71fca3b3d5902e4448e48c447106582026308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a63545a10242182af6", ), ] @@ -110,60 +114,105 @@ INVALID_VECTORS = [ # protocol magic PROTOCOL_MAGICS["mainnet"], # inputs - [SAMPLE_INPUTS[0]["input"]], + [SAMPLE_INPUT], # outputs - [ - { - "address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q", - "amount": "3003112", - } - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], - "Invalid output address!", + [SAMPLE_OUTPUTS["invalid_address"]], + # fee + 42, + # ttl + 10, + # error message + "Invalid address", ), - # Output address is an invalid CBOR + # Output address is invalid CBOR ( - # protocol magic + # protocol magic (mainnet) PROTOCOL_MAGICS["mainnet"], # inputs - [SAMPLE_INPUTS[0]["input"]], + [SAMPLE_INPUT], # outputs - [ - { - "address": "jsK75PTH2esX8k4Wvxenyz83LJJWToBbVmGrWUer2CHFHanLseh7r3sW5X5q", - "amount": "3003112", - } - ], - # transactions - [ - "839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0" - ], - "Invalid output address!", + [SAMPLE_OUTPUTS["invalid_cbor"]], + # fee + 42, + # ttl + 10, + # error message + "Invalid address", ), - # Output address is invalid CBOR + # Output address has invalid CRC + ( + # protocol magic (mainnet) + PROTOCOL_MAGICS["mainnet"], + # inputs + [SAMPLE_INPUT], + # outputs + [SAMPLE_OUTPUTS["invalid_crc"]], + # fee + 42, + # ttl + 10, + # error message + "Invalid address", + ), + # Fee is too high ( # protocol magic (mainnet) - 764824073, + PROTOCOL_MAGICS["mainnet"], # inputs - [ - { - "path": "m/44'/1815'/0'/0/1", - "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", - "prev_index": 0, - "type": 0, - } - ], + [SAMPLE_INPUT], # outputs - [ - { - "address": "5dnY6xgRcNUSLGa4gfqef2jGAMHb7koQs9EXErXLNC1LiMPUnhn8joXhvEJpWQtN3F4ysATcBvCn5tABgL3e4hPWapPHmcK5GJMSEaET5JafgAGwSrznzL1Mqa", - "amount": "3003112", - } - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], - "Invalid output address!", + [SAMPLE_OUTPUTS["simple_output"]], + # fee + 45000000000000001, + # ttl + 10, + # error message + "Fee is out of range!", + ), + # Output total is too high + ( + # protocol magic (mainnet) + PROTOCOL_MAGICS["mainnet"], + # inputs + [SAMPLE_INPUT], + # outputs + [SAMPLE_OUTPUTS["large_simple_output"], SAMPLE_OUTPUTS["change_output"]], + # fee + 42, + # ttl + 10, + # error message + "Total transaction amount is out of range!", + ), + # Mainnet transaction with testnet output + ( + # protocol magic + PROTOCOL_MAGICS["mainnet"], + # inputs + [SAMPLE_INPUT], + # outputs + [SAMPLE_OUTPUTS["testnet_output"]], + # fee + 42, + # ttl + 10, + # error message + "Output address network mismatch!", + ), + # Testnet transaction with mainnet output + ( + # protocol magic + PROTOCOL_MAGICS["testnet"], + # inputs + [SAMPLE_INPUT], + # outputs + [SAMPLE_OUTPUTS["simple_output"]], + # fee + 42, + # ttl + 10, + # error message + "Output address network mismatch!", ), ] @@ -172,18 +221,15 @@ INVALID_VECTORS = [ @pytest.mark.cardano @pytest.mark.skip_t1 # T1 support is not planned @pytest.mark.parametrize( - "protocol_magic,inputs,outputs,transactions,tx_hash,tx_body", VALID_VECTORS + "protocol_magic,inputs,outputs,fee,ttl,tx_hash,serialized_tx", VALID_VECTORS ) def test_cardano_sign_tx( - client, protocol_magic, inputs, outputs, transactions, tx_hash, tx_body + client, protocol_magic, inputs, outputs, fee, ttl, tx_hash, serialized_tx ): inputs = [cardano.create_input(i) for i in inputs] outputs = [cardano.create_output(o) for o in outputs] expected_responses = [ - messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions)) - ] - expected_responses += [ messages.ButtonRequest(code=messages.ButtonRequestType.Other), messages.ButtonRequest(code=messages.ButtonRequestType.Other), messages.CardanoSignedTx(), @@ -200,32 +246,27 @@ def test_cardano_sign_tx( with client: client.set_expected_responses(expected_responses) client.set_input_flow(input_flow) - response = cardano.sign_tx( - client, inputs, outputs, transactions, protocol_magic - ) + response = cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic) assert response.tx_hash.hex() == tx_hash - assert response.tx_body.hex() == tx_body + assert response.serialized_tx.hex() == serialized_tx @pytest.mark.altcoin @pytest.mark.cardano @pytest.mark.skip_t1 # T1 support is not planned @pytest.mark.parametrize( - "protocol_magic,inputs,outputs,transactions,expected_error_message", INVALID_VECTORS + "protocol_magic,inputs,outputs,fee,ttl,expected_error_message", INVALID_VECTORS ) def test_cardano_sign_tx_validation( - client, protocol_magic, inputs, outputs, transactions, expected_error_message + client, protocol_magic, inputs, outputs, fee, ttl, expected_error_message ): inputs = [cardano.create_input(i) for i in inputs] outputs = [cardano.create_output(o) for o in outputs] - expected_responses = [ - messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions)) - ] - expected_responses += [messages.Failure()] + expected_responses = [messages.Failure()] with client: client.set_expected_responses(expected_responses) with pytest.raises(TrezorFailure, match=expected_error_message): - cardano.sign_tx(client, inputs, outputs, transactions, protocol_magic) + cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic) diff --git a/tests/device_tests/test_msg_cardano_sign_tx_slip39_basic.py b/tests/device_tests/test_msg_cardano_sign_tx_slip39_basic.py index 558721616..1c1e50be3 100644 --- a/tests/device_tests/test_msg_cardano_sign_tx_slip39_basic.py +++ b/tests/device_tests/test_msg_cardano_sign_tx_slip39_basic.py @@ -17,22 +17,27 @@ import pytest from trezorlib import cardano, messages +from trezorlib.cardano import PROTOCOL_MAGICS from ..common import MNEMONIC_SLIP39_BASIC_20_3of6 -PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 1097911063} +SAMPLE_INPUT = { + "path": "m/44'/1815'/0'/0/1", + "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", + "prev_index": 0, +} -SAMPLE_INPUTS = [ - { - "input": { - "path": "m/44'/1815'/0'/0/1", - "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", - "prev_index": 0, - "type": 0, - }, - "prev_tx": "839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0", - } -] +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 = [ # Mainnet transaction without change @@ -40,68 +45,51 @@ VALID_VECTORS = [ # protocol magic PROTOCOL_MAGICS["mainnet"], # inputs - [SAMPLE_INPUTS[0]["input"]], + [SAMPLE_INPUT], # outputs - [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - } - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], + [SAMPLE_OUTPUTS["simple_output"]], + # fee + 42, + # ttl + 10, # tx hash - "799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d", - # tx body - "82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b584032a773bcd60c83880de09676c45e52cc2c2189c1b46d93de596a5cf6e3e93041c22e6e5762144feb65b40e905659c9b5e51528fa6574273279c2507a2b996f0e", + "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6", + # serialized tx + "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018182582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e802182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c584055c179ff2beca2c6a78d66de3dea5a6e3134ca3430447c9b73ede73d9b6ae524cde73db59d93a4dfccbbd42b4f4dbacbb655b27171d0f248fdd2d0dc16e0130458206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b41a0f6", ), # Mainnet transaction with change ( # protocol magic (mainnet) PROTOCOL_MAGICS["mainnet"], # inputs - [ - { - "path": "m/44'/1815'/0'/0/1", - "prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc", - "prev_index": 0, - "type": 0, - } - ], + [SAMPLE_INPUT], # outputs - [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - }, - {"path": "m/44'/1815'/0'/0/1", "amount": "1000000"}, - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], + [SAMPLE_OUTPUTS["simple_output"], SAMPLE_OUTPUTS["change_output"]], + # fee + 42, + # ttl + 10, # tx hash - "5a3921053daabc6a2ffc1528963352fa8ea842bd04056371effcd58256e0cd55", - # tx body - "82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e88282d818582183581c2ea63b3db3a1865f59c11762a5aede800ed8f2dc0605d75df2ed7c9ca0001ae82668161a000f4240ffa0818200d818588582584024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b5840ea38a37167d652fd35ac3517a6b3a5ec73e01a9f3b6d57d645c7727856a17a2c8d9403b497e148811cb087822c49b5ab6e14b1bc78acc21eca434c3e5147260f", + "4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae", + # serialized tx + "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582b82d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e882582b82d818582183581c2ea63b3db3a1865f59c11762a5aede800ed8f2dc0605d75df2ed7c9ca0001ae82668161a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840594c986290cc5cddf3c242f2d650fcbfd0705949c9990569798c29e42ca7b0d6e92a589be6962dcce9c53c63de973d84c38cf53374b5329e20973a280abec00d58206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b41a0f6", ), # Testnet transaction ( # protocol magic PROTOCOL_MAGICS["testnet"], # inputs - [SAMPLE_INPUTS[0]["input"]], + [SAMPLE_INPUT], # outputs - [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - } - ], - # transactions - [SAMPLE_INPUTS[0]["prev_tx"]], + [SAMPLE_OUTPUTS["testnet_output"], SAMPLE_OUTPUTS["change_output"]], + # fee + 42, + # ttl + 10, # tx hash - "799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d", - # tx body - "82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c6f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b58407aab2a707a6d295c0a93e396429721c48d2c09238e32112f2e1d14a8296ff463204240e7d9168e2dfe8276f426cd1f73f1254df434cdab7c942e2a920c8ce800", + "ac7ef9e4f51ed4d6b791cee111b240dae2f00c39c5cc1a150631eba8aa955528", + # serialized tx + "83a400818258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00018282582f82d818582583581c586b90cf80c021db288ce1c18ecfd3610acf64f8748768b0eb7335b1a10242182a001aae3129311a002dd2e882582f82d818582583581c709bfb5d9733cbdd72f520cd2c8b9f8f942da5e6cd0b6994e1803b0aa10242182a001aef14e76d1a000f424002182a030aa1028184582024c4fe188a39103db88818bc191fd8571eae7b284ebcbdf2462bde97b058a95c5840cfd68676454ad8bed8575dcb8ee91824c0f836da4f07a54112088b12c6b89be0c8f729d4e3fb1df0de10f049a66dea372f3e2888cabb6110d538a0e9a06fbb0758206f7a744035f4b3ddb8f861c18446169643cc3ae85e271b4b4f0eda05cf84c65b45a10242182af6", ), ] @@ -111,19 +99,16 @@ VALID_VECTORS = [ @pytest.mark.skip_t1 # T1 support is not planned @pytest.mark.setup_client(mnemonic=MNEMONIC_SLIP39_BASIC_20_3of6, passphrase=True) @pytest.mark.parametrize( - "protocol_magic,inputs,outputs,transactions,tx_hash,tx_body", VALID_VECTORS + "protocol_magic,inputs,outputs,fee,ttl,tx_hash,serialized_tx", VALID_VECTORS ) def test_cardano_sign_tx( - client, protocol_magic, inputs, outputs, transactions, tx_hash, tx_body + client, protocol_magic, inputs, outputs, fee, ttl, tx_hash, serialized_tx ): inputs = [cardano.create_input(i) for i in inputs] outputs = [cardano.create_output(o) for o in outputs] - expected_responses = [messages.PassphraseRequest()] - expected_responses += [ - messages.CardanoTxRequest(tx_index=i) for i in range(len(transactions)) - ] - expected_responses += [ + expected_responses = [ + messages.PassphraseRequest(), messages.ButtonRequest(code=messages.ButtonRequestType.Other), messages.ButtonRequest(code=messages.ButtonRequestType.Other), messages.CardanoSignedTx(), @@ -141,8 +126,6 @@ def test_cardano_sign_tx( with client: client.set_expected_responses(expected_responses) client.set_input_flow(input_flow) - response = cardano.sign_tx( - client, inputs, outputs, transactions, protocol_magic - ) + response = cardano.sign_tx(client, inputs, outputs, fee, ttl, protocol_magic) assert response.tx_hash.hex() == tx_hash - assert response.tx_body.hex() == tx_body + assert response.serialized_tx.hex() == serialized_tx diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 93998a30e..aba01a239 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -27,22 +27,29 @@ "test_msg_binance_sign_tx.py::test_binance_sign_message[message0-expected_response0]": "d41ee5e01a50f0f96fd7881db1750fab31cfe62c25b4eabbc092cc3daa039c7f", "test_msg_binance_sign_tx.py::test_binance_sign_message[message1-expected_response1]": "7b8bbe5ba7d7b07c95065608fb1cf9aeafcb3f9671835a6e5e5a6997ff4ff99b", "test_msg_binance_sign_tx.py::test_binance_sign_message[message2-expected_response2]": "813ad1b802dee1ace4dfa378edd840dbcea57c1a1b8eed67134def024c40a6e9", -"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-Ae2tdPwUPEZLCq3sFv4wVYx": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", -"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-Ae2tdPwUPEZEY6pVJoyuNNd": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", -"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-Ae2tdPwUPEZ3gZD1QeUHvAq": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-42-2657WMsDfac5vydkak9a": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-0-764824073-Ae2tdPwUPEZLC": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-42-2657WMsDfac61ebUDw53": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-1-764824073-Ae2tdPwUPEZEY": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-42-2657WMsDfac5PMpEsxc1": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_get_address.py::test_cardano_get_address[m-44'-1815'-0'-0-2-764824073-Ae2tdPwUPEZ3g": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-0'-c0fce1839f1a84c4e7702": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-1'-ea5dde31b9f551e08a5b6": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-2'-076338cee5ab3dae19f06": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", "test_msg_cardano_get_public_key.py::test_cardano_get_public_key[m-44'-1815'-3'-5f769380dc6fd17a4e0f2": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", -"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transactions2": "fdac7a4eb74827fc2318c3646206c60a10ddfe244c089f5a798e7652f69d313e", -"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactions0-": "b1f022d81fb324a136a8401232b7112bca2b5212b98578a189d3486911d469cc", -"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactions1-": "c5f547077924a76527e8cf58abf437e27059386bff775499d21d52105d60ed44", -"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs0-outputs0-tra": "f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649", -"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs1-outputs1-tra": "f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649", -"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs2-outputs2-tra": "f504163122424398b008ec86cbd219e543eea7889d52651e0e69f707b4a14649", -"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transacti": "32edff90a2fecf74bf7e3e46794a48924082c069b9d1e57adb531749ac71e4af", -"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactio": "85fb1f1bf90131d6cd04e58b2303bf8d3b9a641b1f418852bc27c4432d427976", -"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactio": "15f55a8fc499364aa239c319100072d16ae5dc1ee9dd0db3cd1795363fde679a", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-5dd03fb44cb8806": "418f782a5d37c227f2d82f144bcee661d65187e07a421f64e6a5465daf544e7d", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e09bde": "63dc0880175285dc1b3dcadb3ae66439f28b1c18fdaf50245cd84b6c8cb3d0e0", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx[764824073-inputs1-outputs1-42-10-81b14b7e": "63dc0880175285dc1b3dcadb3ae66439f28b1c18fdaf50245cd84b6c8cb3d0e0", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[42-inputs6-outputs6-42-10-Outp": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs0-outputs0-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs1-outputs1-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs2-outputs2-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs3-outputs3-450": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs4-outputs4-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_transaction.py::test_cardano_sign_tx_validation[764824073-inputs5-outputs5-42-": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586", +"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[42-inputs2-outputs2-42-10-ac7ef9e4f51": "ba354313a87bff3e5079da58c74cdc265a9be8e9a80006e76ea68d0cdb869665", +"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-42-10-73e0": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16", +"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-42-10-4c43": "37b670a523fca62a6d6d997541722ed070e1acf58fe618bc040e907272aa0f16", "test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "9e11b251c03ef09127da79d92f8483c4db438c7303328774790d45e3f6fb8c96", "test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "d280ed129a2ea4781af9e35542aa31ecf63da75fc6812ed3bd05107809f836a4", "test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "32c808f00bada2059f933f3515337e494c837bdf65e4ea918b457d1c9f4cb42a",