1# This file is part of the Trezor project.
2#
3# Copyright (C) 2012-2022 SatoshiLabs and contributors
4#
5# This library is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3
7# as published by the Free Software Foundation.
8#
9# This library is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the License along with this library.
15# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
16
17import json
18from typing import TYPE_CHECKING, Optional, TextIO
19
20import click
21
22from .. import cardano, messages, tools
23from . import ChoiceType, with_client
24
25if TYPE_CHECKING:
26    from ..client import TrezorClient
27
28PATH_HELP = "BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
29
30
31@click.group(name="cardano")
32def cli() -> None:
33    """Cardano commands."""
34
35
36@cli.command()
37@click.argument("file", type=click.File("r"))
38@click.option("-f", "--file", "_ignore", is_flag=True, hidden=True, expose_value=False)
39@click.option(
40    "-s",
41    "--signing-mode",
42    required=True,
43    type=ChoiceType({m.name: m for m in messages.CardanoTxSigningMode}),
44)
45@click.option(
46    "-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"]
47)
48@click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"])
49@click.option("-t", "--testnet", is_flag=True)
50@click.option(
51    "-D",
52    "--derivation-type",
53    type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}),
54    default=messages.CardanoDerivationType.ICARUS,
55)
56@with_client
57def sign_tx(
58    client: "TrezorClient",
59    file: TextIO,
60    signing_mode: messages.CardanoTxSigningMode,
61    protocol_magic: int,
62    network_id: int,
63    testnet: bool,
64    derivation_type: messages.CardanoDerivationType,
65) -> cardano.SignTxResponse:
66    """Sign Cardano transaction."""
67    transaction = json.load(file)
68
69    if testnet:
70        protocol_magic = cardano.PROTOCOL_MAGICS["testnet"]
71        network_id = cardano.NETWORK_IDS["testnet"]
72
73    inputs = [cardano.parse_input(input) for input in transaction["inputs"]]
74    outputs = [cardano.parse_output(output) for output in transaction["outputs"]]
75    fee = transaction["fee"]
76    ttl = transaction.get("ttl")
77    validity_interval_start = transaction.get("validity_interval_start")
78    certificates = [
79        cardano.parse_certificate(certificate)
80        for certificate in transaction.get("certificates", ())
81    ]
82    withdrawals = [
83        cardano.parse_withdrawal(withdrawal)
84        for withdrawal in transaction.get("withdrawals", ())
85    ]
86    auxiliary_data = cardano.parse_auxiliary_data(transaction.get("auxiliary_data"))
87    mint = cardano.parse_mint(transaction.get("mint", ()))
88    additional_witness_requests = [
89        cardano.parse_additional_witness_request(p)
90        for p in transaction["additional_witness_requests"]
91    ]
92
93    client.init_device(derive_cardano=True)
94    sign_tx_response = cardano.sign_tx(
95        client,
96        signing_mode,
97        inputs,
98        outputs,
99        fee,
100        ttl,
101        validity_interval_start,
102        certificates,
103        withdrawals,
104        protocol_magic,
105        network_id,
106        auxiliary_data,
107        mint,
108        additional_witness_requests,
109        derivation_type=derivation_type,
110    )
111
112    sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex()
113    sign_tx_response["witnesses"] = [
114        {
115            "type": witness["type"],
116            "pub_key": witness["pub_key"].hex(),
117            "signature": witness["signature"].hex(),
118            "chain_code": witness["chain_code"].hex()
119            if witness["chain_code"] is not None
120            else None,
121        }
122        for witness in sign_tx_response["witnesses"]
123    ]
124    auxiliary_data_supplement = sign_tx_response.get("auxiliary_data_supplement")
125    if auxiliary_data_supplement:
126        auxiliary_data_supplement["auxiliary_data_hash"] = auxiliary_data_supplement[
127            "auxiliary_data_hash"
128        ].hex()
129        catalyst_signature = auxiliary_data_supplement.get("catalyst_signature")
130        if catalyst_signature:
131            auxiliary_data_supplement["catalyst_signature"] = catalyst_signature.hex()
132        sign_tx_response["auxiliary_data_supplement"] = auxiliary_data_supplement
133    return sign_tx_response
134
135
136@cli.command()
137@click.option("-n", "--address", type=str, default="", help=PATH_HELP)
138@click.option("-d", "--show-display", is_flag=True)
139@click.option(
140    "-t",
141    "--address-type",
142    type=ChoiceType({m.name: m for m in messages.CardanoAddressType}),
143    default="BASE",
144)
145@click.option("-s", "--staking-address", type=str, default="")
146@click.option("-h", "--staking-key-hash", type=str, default=None)
147@click.option("-b", "--block_index", type=int, default=None)
148@click.option("-x", "--tx_index", type=int, default=None)
149@click.option("-c", "--certificate_index", type=int, default=None)
150@click.option("--script-payment-hash", type=str, default=None)
151@click.option("--script-staking-hash", type=str, default=None)
152@click.option(
153    "-p", "--protocol-magic", type=int, default=cardano.PROTOCOL_MAGICS["mainnet"]
154)
155@click.option("-N", "--network-id", type=int, default=cardano.NETWORK_IDS["mainnet"])
156@click.option("-e", "--testnet", is_flag=True)
157@click.option(
158    "-D",
159    "--derivation-type",
160    type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}),
161    default=messages.CardanoDerivationType.ICARUS,
162)
163@with_client
164def get_address(
165    client: "TrezorClient",
166    address: str,
167    address_type: messages.CardanoAddressType,
168    staking_address: str,
169    staking_key_hash: Optional[str],
170    block_index: Optional[int],
171    tx_index: Optional[int],
172    certificate_index: Optional[int],
173    script_payment_hash: Optional[str],
174    script_staking_hash: Optional[str],
175    protocol_magic: int,
176    network_id: int,
177    show_display: bool,
178    testnet: bool,
179    derivation_type: messages.CardanoDerivationType,
180) -> str:
181    """
182    Get Cardano address.
183
184    All address types require the address, address_type, protocol_magic and
185    network_id parameters.
186
187    When deriving a base address you can choose to include staking info as
188    staking_address or staking_key_hash - one has to be chosen.
189
190    When deriving a pointer address you need to specify the block_index,
191    tx_index and certificate_index parameters.
192
193    Byron, enterprise and reward addresses only require the general parameters.
194    """
195    if testnet:
196        protocol_magic = cardano.PROTOCOL_MAGICS["testnet"]
197        network_id = cardano.NETWORK_IDS["testnet"]
198
199    staking_key_hash_bytes = cardano.parse_optional_bytes(staking_key_hash)
200    script_payment_hash_bytes = cardano.parse_optional_bytes(script_payment_hash)
201    script_staking_hash_bytes = cardano.parse_optional_bytes(script_staking_hash)
202
203    address_parameters = cardano.create_address_parameters(
204        address_type,
205        tools.parse_path(address),
206        tools.parse_path(staking_address),
207        staking_key_hash_bytes,
208        block_index,
209        tx_index,
210        certificate_index,
211        script_payment_hash_bytes,
212        script_staking_hash_bytes,
213    )
214
215    client.init_device(derive_cardano=True)
216    return cardano.get_address(
217        client,
218        address_parameters,
219        protocol_magic,
220        network_id,
221        show_display,
222        derivation_type=derivation_type,
223    )
224
225
226@cli.command()
227@click.option("-n", "--address", required=True, help=PATH_HELP)
228@click.option(
229    "-D",
230    "--derivation-type",
231    type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}),
232    default=messages.CardanoDerivationType.ICARUS,
233)
234@with_client
235def get_public_key(
236    client: "TrezorClient",
237    address: str,
238    derivation_type: messages.CardanoDerivationType,
239) -> messages.CardanoPublicKey:
240    """Get Cardano public key."""
241    address_n = tools.parse_path(address)
242    client.init_device(derive_cardano=True)
243    return cardano.get_public_key(client, address_n, derivation_type=derivation_type)
244
245
246@cli.command()
247@click.argument("file", type=click.File("r"))
248@click.option(
249    "-d",
250    "--display-format",
251    type=ChoiceType({m.name: m for m in messages.CardanoNativeScriptHashDisplayFormat}),
252    default="HIDE",
253)
254@click.option(
255    "-D",
256    "--derivation-type",
257    type=ChoiceType({m.name: m for m in messages.CardanoDerivationType}),
258    default=messages.CardanoDerivationType.ICARUS,
259)
260@with_client
261def get_native_script_hash(
262    client: "TrezorClient",
263    file: TextIO,
264    display_format: messages.CardanoNativeScriptHashDisplayFormat,
265    derivation_type: messages.CardanoDerivationType,
266) -> messages.CardanoNativeScriptHash:
267    """Get Cardano native script hash."""
268    native_script_json = json.load(file)
269    native_script = cardano.parse_native_script(native_script_json)
270
271    client.init_device(derive_cardano=True)
272    return cardano.get_native_script_hash(
273        client, native_script, display_format, derivation_type=derivation_type
274    )
275