2018-09-05 12:56:31 +00:00
|
|
|
# This file is part of the Trezor project.
|
|
|
|
#
|
2019-05-29 16:44:09 +00:00
|
|
|
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
2018-09-05 12:56:31 +00:00
|
|
|
#
|
|
|
|
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
|
|
|
|
|
|
|
from typing import List
|
|
|
|
|
|
|
|
from . import messages, tools
|
2020-07-01 13:43:02 +00:00
|
|
|
from .tools import expect
|
2018-09-05 12:56:31 +00:00
|
|
|
|
2020-07-01 13:43:02 +00:00
|
|
|
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
|
2020-07-23 13:54:49 +00:00
|
|
|
NETWORK_IDS = {"mainnet": 1, "testnet": 0}
|
2020-07-01 13:43:02 +00:00
|
|
|
|
|
|
|
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs")
|
|
|
|
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index")
|
2020-07-27 10:52:02 +00:00
|
|
|
REQUIRED_FIELDS_CERTIFICATE = ("path", "type")
|
|
|
|
REQUIRED_FIELDS_WITHDRAWAL = ("path", "amount")
|
2018-09-05 12:56:31 +00:00
|
|
|
|
2020-07-23 13:54:49 +00:00
|
|
|
INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields"
|
|
|
|
|
|
|
|
ADDRESS_TYPES = (
|
|
|
|
messages.CardanoAddressType.BYRON,
|
|
|
|
messages.CardanoAddressType.BASE,
|
|
|
|
messages.CardanoAddressType.POINTER,
|
|
|
|
messages.CardanoAddressType.ENTERPRISE,
|
|
|
|
messages.CardanoAddressType.REWARD,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_address_parameters(
|
|
|
|
address_type: messages.CardanoAddressType,
|
|
|
|
address_n: List[int],
|
|
|
|
address_n_staking: List[int] = None,
|
|
|
|
staking_key_hash: bytes = None,
|
|
|
|
block_index: int = None,
|
|
|
|
tx_index: int = None,
|
|
|
|
certificate_index: int = None,
|
|
|
|
) -> messages.CardanoAddressParametersType:
|
|
|
|
certificate_pointer = None
|
|
|
|
|
|
|
|
if address_type not in ADDRESS_TYPES:
|
|
|
|
raise ValueError("Unknown address type")
|
|
|
|
|
|
|
|
if address_type == messages.CardanoAddressType.POINTER:
|
|
|
|
certificate_pointer = create_certificate_pointer(
|
|
|
|
block_index, tx_index, certificate_index
|
|
|
|
)
|
|
|
|
|
|
|
|
return messages.CardanoAddressParametersType(
|
|
|
|
address_type=address_type,
|
|
|
|
address_n=address_n,
|
|
|
|
address_n_staking=address_n_staking,
|
|
|
|
staking_key_hash=staking_key_hash,
|
|
|
|
certificate_pointer=certificate_pointer,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_certificate_pointer(
|
|
|
|
block_index: int, tx_index: int, certificate_index: int
|
|
|
|
) -> messages.CardanoBlockchainPointerType:
|
|
|
|
if block_index is None or tx_index is None or certificate_index is None:
|
|
|
|
raise ValueError("Invalid pointer parameters")
|
|
|
|
|
|
|
|
return messages.CardanoBlockchainPointerType(
|
|
|
|
block_index=block_index, tx_index=tx_index, certificate_index=certificate_index
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_input(input) -> messages.CardanoTxInputType:
|
|
|
|
if not all(input.get(k) is not None for k in REQUIRED_FIELDS_INPUT):
|
|
|
|
raise ValueError("The input is missing some fields")
|
|
|
|
|
|
|
|
path = input["path"]
|
|
|
|
|
|
|
|
return messages.CardanoTxInputType(
|
|
|
|
address_n=tools.parse_path(path),
|
|
|
|
prev_hash=bytes.fromhex(input["prev_hash"]),
|
|
|
|
prev_index=input["prev_index"],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_output(output) -> messages.CardanoTxOutputType:
|
|
|
|
contains_address = output.get("address") is not None
|
|
|
|
contains_address_type = output.get("addressType") is not None
|
|
|
|
|
|
|
|
if output.get("amount") is None:
|
|
|
|
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
|
|
|
|
if not (contains_address or contains_address_type):
|
|
|
|
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
|
|
|
|
|
|
|
|
if contains_address:
|
|
|
|
return messages.CardanoTxOutputType(
|
|
|
|
address=output["address"], amount=int(output["amount"])
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return _create_change_output(output)
|
|
|
|
|
|
|
|
|
|
|
|
def _create_change_output(output) -> messages.CardanoTxOutputType:
|
|
|
|
if output.get("path") is None:
|
|
|
|
raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE)
|
|
|
|
|
|
|
|
staking_key_hash_bytes = None
|
|
|
|
if output.get("stakingKeyHash"):
|
|
|
|
staking_key_hash_bytes = bytes.fromhex(output.get("stakingKeyHash"))
|
|
|
|
|
|
|
|
address_parameters = create_address_parameters(
|
|
|
|
int(output["addressType"]),
|
|
|
|
tools.parse_path(output["path"]),
|
|
|
|
tools.parse_path(output.get("stakingPath")),
|
|
|
|
staking_key_hash_bytes,
|
|
|
|
output.get("blockIndex"),
|
|
|
|
output.get("txIndex"),
|
|
|
|
output.get("certificateIndex"),
|
|
|
|
)
|
|
|
|
|
|
|
|
return messages.CardanoTxOutputType(
|
|
|
|
address_parameters=address_parameters, amount=int(output["amount"])
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-07-27 10:52:02 +00:00
|
|
|
def create_certificate(certificate) -> messages.CardanoTxCertificateType:
|
|
|
|
if not all(certificate.get(k) is not None for k in REQUIRED_FIELDS_CERTIFICATE):
|
|
|
|
raise ValueError("The certificate is missing some fields")
|
|
|
|
|
|
|
|
path = certificate["path"]
|
|
|
|
certificate_type = certificate["type"]
|
|
|
|
|
|
|
|
if certificate_type == messages.CardanoCertificateType.STAKE_DELEGATION:
|
|
|
|
if certificate.get("pool") is None:
|
|
|
|
raise ValueError("The certificate is missing some fields")
|
|
|
|
|
|
|
|
pool = certificate["pool"]
|
|
|
|
return messages.CardanoTxCertificateType(
|
|
|
|
type=certificate_type,
|
|
|
|
path=tools.parse_path(path),
|
|
|
|
pool=bytes.fromhex(pool),
|
|
|
|
)
|
|
|
|
elif (
|
|
|
|
certificate_type == messages.CardanoCertificateType.STAKE_REGISTRATION
|
|
|
|
or certificate_type == messages.CardanoCertificateType.STAKE_DEREGISTRATION
|
|
|
|
):
|
|
|
|
return messages.CardanoTxCertificateType(
|
|
|
|
type=certificate_type, path=tools.parse_path(path),
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
raise ValueError("Unknown certificate type")
|
|
|
|
|
|
|
|
|
|
|
|
def create_withdrawal(withdrawal) -> messages.CardanoTxWithdrawalType:
|
|
|
|
if not all(withdrawal.get(k) is not None for k in REQUIRED_FIELDS_WITHDRAWAL):
|
|
|
|
raise ValueError("Withdrawal is missing some fields")
|
|
|
|
|
|
|
|
path = withdrawal["path"]
|
|
|
|
return messages.CardanoTxWithdrawalType(
|
|
|
|
path=tools.parse_path(path), amount=int(withdrawal["amount"]),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-07-23 13:54:49 +00:00
|
|
|
# ====== Client functions ====== #
|
|
|
|
|
2018-09-05 12:56:31 +00:00
|
|
|
|
|
|
|
@expect(messages.CardanoAddress, field="address")
|
2020-07-01 13:43:02 +00:00
|
|
|
def get_address(
|
2020-07-23 13:54:49 +00:00
|
|
|
client,
|
|
|
|
address_parameters: messages.CardanoAddressParametersType,
|
2020-07-28 13:33:12 +00:00
|
|
|
protocol_magic: int = PROTOCOL_MAGICS["mainnet"],
|
|
|
|
network_id: int = NETWORK_IDS["mainnet"],
|
|
|
|
show_display: bool = False,
|
2020-07-01 13:43:02 +00:00
|
|
|
) -> messages.CardanoAddress:
|
2018-09-05 12:56:31 +00:00
|
|
|
return client.call(
|
2020-07-01 13:43:02 +00:00
|
|
|
messages.CardanoGetAddress(
|
2020-07-23 13:54:49 +00:00
|
|
|
address_parameters=address_parameters,
|
2020-07-01 13:43:02 +00:00
|
|
|
protocol_magic=protocol_magic,
|
2020-07-23 13:54:49 +00:00
|
|
|
network_id=network_id,
|
2020-07-01 13:43:02 +00:00
|
|
|
show_display=show_display,
|
|
|
|
)
|
2018-09-05 12:56:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@expect(messages.CardanoPublicKey)
|
2020-07-01 13:43:02 +00:00
|
|
|
def get_public_key(client, address_n: List[int]) -> messages.CardanoPublicKey:
|
2018-09-05 12:56:31 +00:00
|
|
|
return client.call(messages.CardanoGetPublicKey(address_n=address_n))
|
|
|
|
|
|
|
|
|
2020-07-01 13:43:02 +00:00
|
|
|
@expect(messages.CardanoSignedTx)
|
2018-09-05 12:56:31 +00:00
|
|
|
def sign_tx(
|
|
|
|
client,
|
|
|
|
inputs: List[messages.CardanoTxInputType],
|
|
|
|
outputs: List[messages.CardanoTxOutputType],
|
2020-07-01 13:43:02 +00:00
|
|
|
fee: int,
|
|
|
|
ttl: int,
|
2020-07-28 13:33:12 +00:00
|
|
|
certificates: List[messages.CardanoTxCertificateType] = (),
|
|
|
|
withdrawals: List[messages.CardanoTxWithdrawalType] = (),
|
|
|
|
metadata_hash: bytes = None,
|
|
|
|
protocol_magic: int = PROTOCOL_MAGICS["mainnet"],
|
|
|
|
network_id: int = NETWORK_IDS["mainnet"],
|
2020-07-01 13:43:02 +00:00
|
|
|
) -> messages.CardanoSignedTx:
|
2018-09-05 12:56:31 +00:00
|
|
|
response = client.call(
|
|
|
|
messages.CardanoSignTx(
|
2018-09-06 15:25:55 +00:00
|
|
|
inputs=inputs,
|
|
|
|
outputs=outputs,
|
2020-07-01 13:43:02 +00:00
|
|
|
fee=fee,
|
|
|
|
ttl=ttl,
|
2020-07-27 10:52:02 +00:00
|
|
|
certificates=certificates,
|
|
|
|
withdrawals=withdrawals,
|
|
|
|
metadata_hash=metadata_hash,
|
2018-11-30 11:14:46 +00:00
|
|
|
protocol_magic=protocol_magic,
|
2020-07-23 13:54:49 +00:00
|
|
|
network_id=network_id,
|
2018-09-05 12:56:31 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return response
|