# This file is part of the Trezor project. # # Copyright (C) 2012-2022 SatoshiLabs and contributors # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the License along with this library. # If not, see . import json from typing import TYPE_CHECKING, Optional, TextIO import click from .. import cardano, messages, tools from . import ChoiceType, with_cardano_session if TYPE_CHECKING: from ..transport.session import Session PATH_HELP = "BIP-32 path to key, e.g. m/44h/1815h/0h/0/0" TESTNET_CHOICES = { "preprod": "testnet_preprod", "preview": "testnet_preview", "legacy": "testnet_legacy", } @click.group(name="cardano") def cli() -> None: """Cardano commands.""" @cli.command() @click.argument("file", type=click.File("r")) @click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False) @click.option( "-s", "--signing-mode", required=True, type=ChoiceType({m.name: m for m in messages.CardanoTxSigningMode}), ) @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", type=ChoiceType(TESTNET_CHOICES)) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @click.option("-i", "--include-network-id", is_flag=True) @click.option("-C", "--chunkify", is_flag=True) @click.option("-T", "--tag-cbor-sets", is_flag=True) @with_cardano_session def sign_tx( session: "Session", file: TextIO, signing_mode: messages.CardanoTxSigningMode, protocol_magic: int, network_id: int, testnet: str, derivation_type: messages.CardanoDerivationType, include_network_id: bool, chunkify: bool, tag_cbor_sets: bool, ) -> cardano.SignTxResponse: """Sign Cardano transaction.""" transaction = json.load(file) if testnet: protocol_magic = cardano.PROTOCOL_MAGICS[testnet] network_id = cardano.NETWORK_IDS["testnet"] inputs = [cardano.parse_input(input) for input in transaction["inputs"]] outputs = [cardano.parse_output(output) for output in transaction["outputs"]] fee = transaction["fee"] ttl = transaction.get("ttl") validity_interval_start = transaction.get("validity_interval_start") certificates = [ cardano.parse_certificate(certificate) for certificate in transaction.get("certificates", ()) ] withdrawals = [ cardano.parse_withdrawal(withdrawal) for withdrawal in transaction.get("withdrawals", ()) ] auxiliary_data = cardano.parse_auxiliary_data(transaction.get("auxiliary_data")) mint = cardano.parse_mint(transaction.get("mint", ())) script_data_hash = cardano.parse_script_data_hash( transaction.get("script_data_hash") ) collateral_inputs = [ cardano.parse_collateral_input(collateral_input) for collateral_input in transaction.get("collateral_inputs", ()) ] required_signers = [ cardano.parse_required_signer(required_signer) for required_signer in transaction.get("required_signers", ()) ] collateral_return = ( cardano.parse_output(transaction["collateral_return"]) if transaction.get("collateral_return") else None ) total_collateral = transaction.get("total_collateral") reference_inputs = [ cardano.parse_reference_input(reference_input) for reference_input in transaction.get("reference_inputs", ()) ] additional_witness_requests = [ cardano.parse_additional_witness_request(p) for p in transaction["additional_witness_requests"] ] sign_tx_response = cardano.sign_tx( session, signing_mode, inputs, outputs, fee, ttl, validity_interval_start, certificates, withdrawals, protocol_magic, network_id, auxiliary_data, mint, script_data_hash, collateral_inputs, required_signers, collateral_return, total_collateral, reference_inputs, additional_witness_requests, derivation_type=derivation_type, include_network_id=include_network_id, chunkify=chunkify, tag_cbor_sets=tag_cbor_sets, ) sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex() sign_tx_response["witnesses"] = [ { "type": witness["type"], "pub_key": witness["pub_key"].hex(), "signature": witness["signature"].hex(), "chain_code": ( witness["chain_code"].hex() if witness["chain_code"] is not None else None ), } for witness in sign_tx_response["witnesses"] ] auxiliary_data_supplement = sign_tx_response.get("auxiliary_data_supplement") if auxiliary_data_supplement: auxiliary_data_supplement["auxiliary_data_hash"] = auxiliary_data_supplement[ "auxiliary_data_hash" ].hex() cvote_registration_signature = auxiliary_data_supplement.get( "cvote_registration_signature" ) if cvote_registration_signature: auxiliary_data_supplement["cvote_registration_signature"] = ( cvote_registration_signature.hex() ) sign_tx_response["auxiliary_data_supplement"] = auxiliary_data_supplement return sign_tx_response @cli.command() @click.option("-n", "--address", type=str, default="", help=PATH_HELP) @click.option("-d", "--show-display", is_flag=True) @click.option( "-t", "--address-type", type=ChoiceType({m.name: m for m in messages.CardanoAddressType}), default="BASE", ) @click.option("-s", "--staking-address", type=str, default="") @click.option("-h", "--staking-key-hash", type=str, default=None) @click.option("-b", "--block_index", type=int, default=None) @click.option("-x", "--tx_index", type=int, default=None) @click.option("-c", "--certificate_index", type=int, default=None) @click.option("--script-payment-hash", type=str, default=None) @click.option("--script-staking-hash", type=str, default=None) @click.option( "-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"] ) @click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"]) @click.option("-e", "--testnet", type=ChoiceType(TESTNET_CHOICES)) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @click.option("-C", "--chunkify", is_flag=True) @with_cardano_session def get_address( session: "Session", address: str, address_type: messages.CardanoAddressType, staking_address: str, staking_key_hash: Optional[str], block_index: Optional[int], tx_index: Optional[int], certificate_index: Optional[int], script_payment_hash: Optional[str], script_staking_hash: Optional[str], protocol_magic: int, network_id: int, show_display: bool, testnet: str, derivation_type: messages.CardanoDerivationType, chunkify: bool, ) -> str: """ 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 = cardano.parse_optional_bytes(staking_key_hash) script_payment_hash_bytes = cardano.parse_optional_bytes(script_payment_hash) script_staking_hash_bytes = cardano.parse_optional_bytes(script_staking_hash) address_parameters = cardano.create_address_parameters( address_type, tools.parse_path(address), tools.parse_path(staking_address), staking_key_hash_bytes, block_index, tx_index, certificate_index, script_payment_hash_bytes, script_staking_hash_bytes, ) return cardano.get_address( session, address_parameters, protocol_magic, network_id, show_display, derivation_type=derivation_type, chunkify=chunkify, ) @cli.command() @click.option("-n", "--address", required=True, help=PATH_HELP) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @click.option("-d", "--show-display", is_flag=True) @with_cardano_session def get_public_key( session: "Session", address: str, derivation_type: messages.CardanoDerivationType, show_display: bool, ) -> messages.CardanoPublicKey: """Get Cardano public key.""" address_n = tools.parse_path(address) return cardano.get_public_key( session, address_n, derivation_type=derivation_type, show_display=show_display ) @cli.command() @click.argument("file", type=click.File("r")) @click.option( "-d", "--display-format", type=ChoiceType({m.name: m for m in messages.CardanoNativeScriptHashDisplayFormat}), default="HIDE", ) @click.option( "-D", "--derivation-type", type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}), default=messages.CardanoDerivationType.ICARUS, ) @with_cardano_session def get_native_script_hash( session: "Session", file: TextIO, display_format: messages.CardanoNativeScriptHashDisplayFormat, derivation_type: messages.CardanoDerivationType, ) -> messages.CardanoNativeScriptHash: """Get Cardano native script hash.""" native_script_json = json.load(file) native_script = cardano.parse_native_script(native_script_json) return cardano.get_native_script_hash( session, native_script, display_format, derivation_type=derivation_type )