1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-26 17:38:39 +00:00

Add cardano support to trezorctl and some tests (#300)

Add cardano support to trezorctl and some tests
This commit is contained in:
Dušan Plavák 2018-09-05 13:56:31 +01:00 committed by matejcik
parent 6c15a41514
commit fb22b894ba
7 changed files with 575 additions and 0 deletions

View File

@ -32,6 +32,7 @@ import click
from trezorlib import (
btc,
cardano,
coins,
cosi,
debuglink,
@ -1078,6 +1079,86 @@ def ethereum_sign_tx(
return "Signed raw transaction: %s" % tx_hex
#
# ADA functions
#
@cli.command(help="Sign Cardano transaction.")
@click.option(
"-f",
"--file",
type=click.File("r"),
required=True,
help="Transaction in JSON format",
)
@click.pass_obj
def cardano_sign_tx(connect, file):
client = connect()
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"]
signed_transaction = cardano.sign_tx(client, inputs, outputs, transactions)
return {
"tx_hash": binascii.hexlify(signed_transaction.tx_hash).decode(),
"tx_body": binascii.hexlify(signed_transaction.tx_body).decode(),
}
@cli.command(help="Get Cardano address.")
@click.option(
"-n", "--address", required=True, help="BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
)
@click.option("-d", "--show-display", is_flag=True)
@click.pass_obj
def cardano_get_address(connect, address, show_display):
client = connect()
address_n = tools.parse_path(address)
return cardano.get_address(client, address_n, show_display)
@cli.command(help="Get Cardano public key.")
@click.option(
"-n", "--address", required=True, help="BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
)
@click.pass_obj
def cardano_get_public_key(connect, address):
client = connect()
address_n = tools.parse_path(address)
return cardano.get_public_key(client, address_n)
@cli.command(help="Sign Cardano message.")
@click.option(
"-n", "--address", required=True, help="BIP-32 path to key, e.g. m/44'/1815'/0'/0/0"
)
@click.option("-m", "--message", required=True, help="String message to sign")
@click.pass_obj
def cardano_sign_message(connect, address, message):
client = connect()
address_n = tools.parse_path(address)
return cardano.sign_message(client, address_n, message)
@cli.command(help="Verify Cardano message.")
@click.option("-p", "--public_key", required=True, help="Public key as hex string")
@click.option("-s", "--signature", required=True, help="Signature as hex string")
@click.option("-m", "--message", required=True, help="String message which was signed")
@click.pass_obj
def cardano_verify_message(connect, public_key, signature, message):
client = connect()
return cardano.verify_message(client, public_key, signature, message)
#
# NEM functions
#

115
trezorlib/cardano.py Normal file
View File

@ -0,0 +1,115 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import binascii
from typing import List
from . import messages, tools
from .tools import CallException, expect, session
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs", "transactions")
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index", "type")
@expect(messages.CardanoAddress, field="address")
def get_address(client, address_n, show_display=False):
return client.call(
messages.CardanoGetAddress(address_n=address_n, show_display=show_display)
)
@expect(messages.CardanoPublicKey)
def get_public_key(client, address_n):
return client.call(messages.CardanoGetPublicKey(address_n=address_n))
@expect(messages.CardanoMessageSignature)
def sign_message(client, address_n, message):
return client.call(
messages.CardanoSignMessage(address_n=address_n, message=message.encode())
)
def verify_message(client, public_key, signature, message):
try:
response = client.call(
messages.CardanoVerifyMessage(
public_key=binascii.unhexlify(public_key),
signature=binascii.unhexlify(signature),
message=message.encode(),
)
)
except CallException as error:
response = error
if isinstance(response, messages.Success):
return True
return False
@session
def sign_tx(
client,
inputs: List[messages.CardanoTxInputType],
outputs: List[messages.CardanoTxOutputType],
transactions: List[bytes],
):
response = client.call(
messages.CardanoSignTx(
inputs=inputs, outputs=outputs, transactions_count=len(transactions)
)
)
while isinstance(response, messages.CardanoTxRequest):
tx_index = response.tx_index
transaction_data = binascii.unhexlify(transactions[tx_index])
ack_message = messages.CardanoTxAck(transaction=transaction_data)
response = client.call(ack_message)
return response
def create_input(input) -> messages.CardanoTxInputType:
if not all(input.get(k) is not None for k in REQUIRED_FIELDS_INPUT):
raise ValueError("The input is missing some fields")
path = input["path"]
return messages.CardanoTxInputType(
address_n=tools.parse_path(path),
prev_hash=binascii.unhexlify(input["prev_hash"]),
prev_index=input["prev_index"],
type=input["type"],
)
def create_output(output) -> messages.CardanoTxOutputType:
if not output.get("amount") or not (output.get("address") or output.get("path")):
raise ValueError("The output is missing some fields")
if output.get("path"):
path = output["path"]
return messages.CardanoTxOutputType(
address_n=tools.parse_path(path), amount=int(output["amount"])
)
return messages.CardanoTxOutputType(
address=output["address"], amount=int(output["amount"])
)

View File

@ -0,0 +1,53 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib.cardano import get_address
from trezorlib.tools import parse_path
from .common import TrezorTest
from .conftest import TREZOR_VERSION
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished")
class TestMsgCardanoGetAddress(TrezorTest):
@pytest.mark.parametrize(
"path,expected_address",
[
(
"m/44'/1815'/0'/0/0",
"Ae2tdPwUPEZLCq3sFv4wVYxwqjMH2nUzBVt1HFr4v87snYrtYq3d3bq2PUQ",
),
(
"m/44'/1815'/0'/0/1",
"Ae2tdPwUPEZEY6pVJoyuNNdLp7VbMB7U7qfebeJ7XGunk5Z2eHarkcN1bHK",
),
(
"m/44'/1815'/0'/0/2",
"Ae2tdPwUPEZ3gZD1QeUHvAqadAV59Zid6NP9VCR9BG5LLAja9YtBUgr6ttK",
),
],
)
def test_cardano_get_address(self, path, expected_address):
# data from https://iancoleman.io/bip39/#english
self.setup_mnemonic_nopin_nopassphrase()
address = get_address(self.client, parse_path(path))
assert address == expected_address

View File

@ -0,0 +1,67 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
from binascii import hexlify
import pytest
from trezorlib.cardano import get_public_key
from trezorlib.tools import parse_path
from .common import TrezorTest
from .conftest import TREZOR_VERSION
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished")
class TestMsgCardanoGetPublicKey(TrezorTest):
@pytest.mark.parametrize(
"path,public_key,chain_code",
[
(
"m/44'/1815'/0'",
"c0fce1839f1a84c4e770293ac2f5e0875141b29017b7f56ab135352d00ad6966",
"07faa161c9f5464315d2855f70fdf1431d5fa39eb838767bf17b69772137452f",
),
(
"m/44'/1815'/1'",
"ea5dde31b9f551e08a5b6b2f98b8c42c726f726c9ce0a7072102ead53bd8f21e",
"70f131bb799fd659c997221ad8cae7dcce4e8da701f8101cf15307fd3a3712a1",
),
(
"m/44'/1815'/2'",
"076338cee5ab3dae19f06ccaa80e3d4428cf0e1bdc04243e41bba7be63a90da7",
"5dcdf129f6f2d108292e615c4b67a1fc41a64e6a96130f5c981e5e8e046a6cd7",
),
(
"m/44'/1815'/3'",
"5f769380dc6fd17a4e0f2d23aa359442a712e5e96d7838ebb91eb020003cccc3",
"1197ea234f528987cbac9817ebc31344395b837a3bb7c2332f87e095e70550a5",
),
],
)
def test_cardano_get_public_key(self, path, public_key, chain_code):
self.setup_mnemonic_allallall()
root_hd_passphrase = None
key = get_public_key(self.client, parse_path(path))
assert hexlify(key.node.public_key).decode("utf8") == public_key
assert hexlify(key.node.chain_code).decode("utf8") == chain_code
assert key.xpub == public_key + chain_code
assert key.root_hd_passphrase == root_hd_passphrase

View File

@ -0,0 +1,61 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
from binascii import hexlify
import pytest
from trezorlib.cardano import sign_message
from trezorlib.tools import parse_path
from .common import TrezorTest
from .conftest import TREZOR_VERSION
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished")
class TestMsgCardanoSignMessage(TrezorTest):
@pytest.mark.parametrize(
"message,path,expected_signature",
[
(
"Test message to sign",
"m/44'/1815'/0'/0/0",
"dfb89d2b22c20ac7270e7640f9b27fee030c30d72afc342f83f6cb79a2522e17142597dbfb979462fc9fbf6ea17b4eba3b7cbf582e41b6ac31cb491e7cd1e308",
),
(
"New Test message to sign",
"m/44'/1815'/0'/0/1",
"d2c68818859f94138ad28a59aa3419a96394008bd38657fe5e74b299df33e70ff7de1b2091ba4a4351153ce4b6beb7eb7316d917ed9303b9f7de57f76e4e1307",
),
(
"Another Test message to sign",
"m/44'/1815'/0'/0/2",
"cfb1a8f76e566d387ed727e3eefbb3a0d280917045f2fc82ff381f296a17344d520c00882bc0656bf04c9e95f8138540d4b6d10ddf34d80e27704d1b0cbd0f05",
),
(
"Just another Test message to sign",
"m/44'/1815'/0'/0/3",
"a1aadbea98fc4075affb0e0b166b71934ac19420688b80e2ac2cfe3cf0d66404da19a0ab4a9f23335c080dc4cc76d1fd4fdfbb44289a50707d3fcf122a96060d",
),
],
)
def test_cardano_sign_message(self, message, path, expected_signature):
self.setup_mnemonic_allallall()
signature = sign_message(self.client, parse_path(path), message)
assert expected_signature == hexlify(signature.signature).decode("utf8")

View File

@ -0,0 +1,113 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import binascii
import time
import pytest
from trezorlib import messages
from trezorlib.cardano import create_input, create_output
from .common import TrezorTest
from .conftest import TREZOR_VERSION
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished")
class TestMsgCardanoSignTx(TrezorTest):
def test_cardano_sign_tx(self):
self.setup_mnemonic_allallall()
transaction = {
"inputs": [
{
"path": "m/44'/1815'/0'/0/1",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0,
"type": 0,
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112",
}
],
"transactions": [
"839f8200d818582482582008abb575fac4c39d5bf80683f7f0c37e48f4e3d96e37d1f6611919a7241b456600ff9f8282d818582183581cda4da43db3fca93695e71dab839e72271204d28b9d964d306b8800a8a0001a7a6916a51a00305becffa0"
],
}
inputs = [create_input(input) for input in transaction["inputs"]]
outputs = [create_output(output) for output in transaction["outputs"]]
transactions = transaction["transactions"]
self.client.transport.write(
messages.CardanoSignTx(
inputs=inputs, outputs=outputs, transactions_count=len(transactions)
)
)
response = self.client.transport.read()
assert isinstance(response, messages.CardanoTxRequest)
assert response.tx_index == 0
# Upload first transaction
transaction_data = binascii.unhexlify(transactions[0])
ack_message = messages.CardanoTxAck(transaction=transaction_data)
self.client.transport.write(ack_message)
# Confirm fee
response = self.client.transport.read()
assert isinstance(response, messages.ButtonRequest)
assert response.code == messages.ButtonRequestType.Other
self.client.debug.press_yes()
self.client.transport.write(messages.ButtonAck())
time.sleep(1)
# Confirm Output
response = self.client.transport.read()
assert isinstance(response, messages.ButtonRequest)
assert response.code == messages.ButtonRequestType.Other
self.client.debug.press_yes()
self.client.transport.write(messages.ButtonAck())
time.sleep(1)
self.client.debug.swipe_down()
time.sleep(1)
# Confirm amount
response = self.client.transport.read()
assert isinstance(response, messages.ButtonRequest)
assert response.code == messages.ButtonRequestType.Other
self.client.debug.press_yes()
self.client.transport.write(messages.ButtonAck())
response = self.client.transport.read()
assert isinstance(response, messages.CardanoSignedTx)
assert (
binascii.hexlify(response.tx_hash)
== b"799c65e8a2c0b1dc4232611728c09d3f3eb0d811c077f8e9798f84605ef1b23d"
)
assert (
binascii.hexlify(response.tx_body)
== b"82839f8200d81858248258201af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc00ff9f8282d818582183581c9e1c71de652ec8b85fec296f0685ca3988781c94a2e1a5d89d92f45fa0001a0d0c25611a002dd2e8ffa0818200d818588582584089053545a6c254b0d9b1464e48d2b5fcf91d4e25c128afb1fcfc61d0843338ea26308151516f3b0e02bb1638142747863c520273ce9bd3e5cd91e1d46fe2a6355840312c01c27317415b0b8acc86aa789da877fe7e15c65b7ea4c4565d8739117f5f6d9d38bf5d058f7be809b2b9b06c1d79fc6b20f9a4d76d8c89bae333edf5680c"
)

View File

@ -0,0 +1,85 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2018 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import pytest
from trezorlib.cardano import verify_message
from .common import TrezorTest
from .conftest import TREZOR_VERSION
@pytest.mark.cardano
@pytest.mark.skip_t1 # T1 support is not planned
@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished")
class TestMsgCardanoVerifyMessage(TrezorTest):
# https://github.com/trezor/trezor-core/blob/master/tests/test_apps.cardano.verify_message.py
@pytest.mark.parametrize(
"message,public_key,signature,expected",
[
(
"Test message to sign",
"2df46e04ebf0816e242bfaa1c73e5ebe8863d05d7a96c8aac16f059975e63f30",
"07f226da2a59c3083e80f01ef7e0ec46fc726ebe6bd15d5e9040031c342d8651bee9aee875019c41a7719674fd417ad43990988ffd371527604b6964df75960d",
True,
),
(
"New Test message to sign",
"7d1de3f22f53904d007ff833fadd7cd6482ea1e83918b985b4ea33e63c16d183",
"8fd3b9d8a4c30326b720de76f8de2bbf57b29b7593576eac4a3017ea23046812017136520dc2f24e9fb4da56bd87c77ea49265686653b36859b5e1e56ba9eb0f",
True,
),
(
"Another Test message to sign",
"f59a28d704df090d8fc641248bdb27d0d001da13ddb332a79cfba8a9fa7233e7",
"89d63bd32c2eb92aa418b9ce0383a7cf489bc56284876c19246b70be72070d83d361fcb136e8e257b7e66029ef4a566405cda0143d251f851debd62c3c38c302",
True,
),
(
"Just another Test message to sign",
"723fdc0eb1300fe7f2b9b6989216a831835a88695ba2c2d5c50c8470b7d1b239",
"49d948090d30e35a88a26d8fb07aca5d68936feba2d5bd49e0d0f7c027a0c8c2955b93a7c930a3b36d23c2502c18bf39cf9b17bbba1a0965090acfb4d10a9305",
True,
),
(
"Test message to sign fail",
"2df46e04ebf0816e242bfaa1c73e5ebe8863d05d7a96c8aac16f059975e63f30",
"07f226da2a59c3083e80f01ef7e0ec46fc726ebe6bd15d5e9040031c342d8651bee9aee875019c41a7719674fd417ad43990988ffd371527604b6964df75960d",
False,
),
(
"New Test message to sign",
"7d1de3f22f53904d007ff833fadd7cd6482ea1e83918b985b4ea33e63c16d183",
"20d3b9d8a4c30326b720de76f8de2bbf57b29b7593576eac4a3017ea23046812017136520dc2f24e9fb4da56bd87c77ea49265686653b36859b5e1e56ba9eb0f",
False,
),
(
"Another Test message to sign",
"209a28d704df090d8fc641248bdb27d0d001da13ddb332a79cfba8a9fa7233e7",
"89d63bd32c2eb92aa418b9ce0383a7cf489bc56284876c19246b70be72070d83d361fcb136e8e257b7e66029ef4a566405cda0143d251f851debd62c3c38c302",
False,
),
(
"Just another Test message to sign fail",
"223fdc0eb1300fe7f2b9b6989216a831835a88695ba2c2d5c50c8470b7d1b239",
"49d948090d30e35a88a26d8fb07aca5d68936feba2d5bd49e0d0f7c027a0c8c2955b93a7c930a3b36d23c2502c18bf39cf9b17bbba1a0965090acfb4d10a9305",
False,
),
],
)
def test_cardano_verify_message(self, message, public_key, signature, expected):
result = verify_message(self.client, public_key, signature, message)
assert result == expected