From 15df848b05843a5e7d032669361f9ef4ec702fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Matejov?= Date: Mon, 10 Sep 2018 16:40:31 +0200 Subject: [PATCH] added Tezos support (#302) * also added device tests for tezos Signed-off-by: Adrian Matejov We also pushed signed operations to betanet network and they got accepted. Here's the list of them * transaction tz1 http://tzscan.io/ong8MQBX595Ld4dteHCbPqd6uzmEorEft6ZvfiX5vQMX4WHRh7K * transaction tz2 http://tzscan.io/ooHZ2vbFX1M1fmr9KpLPWQ8r6oZB2RikpGBXsoQSZfbV1hWVjCP * transaction tz3 http://tzscan.io/op79uLwaFqNB6SgcaAaJtoHFBQk3mL5bC1BQTvYzwX2DVSr8RJG * origination tz1 http://tzscan.io/ooJg7qXtUjh16AtdSvv5kgaQnxjtAiBysFkG3EaG5qPAuDT1Cym contract http://tzscan.io/KT1VakcExcig27ZAuoLTExgGXtvUsykQSf5R * origination tz2 http://tzscan.io/opDrUvCGUwKeu86b7GbRLeVzAkyKkdfnV9aSGAzGDLYYS11m6Qr contract http://tzscan.io/KT1LdQeHBSpvvzdRH495qHmtagavZcZ9Z5GW * origination tz3 http://tzscan.io/opVWtfwvSvHzC6hsSGG31G23Q6nBov8SSvmA2nYXBNc5fWGk3Bg contract http://tzscan.io/KT1VfKfsgKFEfZ5vZUaZNaZyGrQf1gr1MEqr * delegation tz1 http://tzscan.io/onws37YkYRnUvwYiPqYATZFtBVhBwbS2rdPkQjFGbAfLzt82MGa * delegation tz2 http://tzscan.io/oocKfbotJwN5Zvmoybhb9LUJ6VhmeS42G2oZHaXQ2mXm98Rzx8r * delegation tz3 http://tzscan.io/ooFQBcoMzwseJHnFgQ9fEPxAXhtgifuvvEH1CtmyAtnK5R7Drik * transaction+reveal tz2 http://tzscan.io/ooEyN4FxP8RYh98RJgQxposDUuWHLWT8fUzWawNRCsMGxaNAN9c * transaction+reveal tz3 http://tzscan.io/ooLaWQzZj1cbkMjZAifV71QCu5bdbMtET5CabrzLWQmnGMMhwmE --- trezorctl | 41 ++++ .../device_tests/test_msg_tezos_getaddress.py | 38 ++++ .../test_msg_tezos_getpublickey.py | 38 ++++ .../device_tests/test_msg_tezos_sign_tx.py | 195 ++++++++++++++++++ trezorlib/tezos.py | 38 ++++ 5 files changed, 350 insertions(+) create mode 100644 trezorlib/tests/device_tests/test_msg_tezos_getaddress.py create mode 100644 trezorlib/tests/device_tests/test_msg_tezos_getpublickey.py create mode 100644 trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py create mode 100644 trezorlib/tezos.py diff --git a/trezorctl b/trezorctl index 52c60b8891..1f6e2aa2c5 100755 --- a/trezorctl +++ b/trezorctl @@ -48,6 +48,7 @@ from trezorlib import ( protobuf, ripple, stellar, + tezos, tools, ) from trezorlib.client import TrezorClient @@ -1388,6 +1389,46 @@ def ripple_sign_tx(connect, address, file): click.echo(binascii.hexlify(result.serialized_tx)) +# +# Tezos functions +# +@cli.command(help="Get Tezos address for specified path.") +@click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/44'/1729'/0'") +@click.option("-d", "--show-display", is_flag=True) +@click.pass_obj +def tezos_get_address(connect, address, show_display): + client = connect() + address_n = tools.parse_path(address) + return tezos.get_address(client, address_n, show_display) + + +@cli.command(help="Get Tezos public key.") +@click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/44'/1729'/0'") +@click.option("-d", "--show-display", is_flag=True) +@click.pass_obj +def tezos_get_public_key(connect, address, show_display): + client = connect() + address_n = tools.parse_path(address) + return tezos.get_public_key(client, address_n, show_display) + + +@cli.command(help="Sign Tezos transaction.") +@click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/44'/1729'/0'") +@click.option( + "-f", + "--file", + type=click.File("r"), + default="-", + help="Transaction in JSON format (byte fields should be hexlified)", +) +@click.pass_obj +def tezos_sign_tx(connect, address, file): + client = connect() + address_n = tools.parse_path(address) + msg = protobuf.dict_to_proto(proto.TezosSignTx, json.load(file)) + return tezos.sign_tx(client, address_n, msg) + + # # Ontology functions # diff --git a/trezorlib/tests/device_tests/test_msg_tezos_getaddress.py b/trezorlib/tests/device_tests/test_msg_tezos_getaddress.py new file mode 100644 index 0000000000..24348c97be --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_tezos_getaddress.py @@ -0,0 +1,38 @@ +# 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 . + +import pytest + +from trezorlib.tezos import get_address +from trezorlib.tools import parse_path + +from .common import TrezorTest + + +@pytest.mark.tezos +@pytest.mark.skip_t1 +@pytest.mark.xfail +class TestMsgTezosGetAddress(TrezorTest): + def test_tezos_get_address(self): + self.setup_mnemonic_allallall() + + path = parse_path("m/44'/1729'/0'") + address = get_address(self.client, path, show_display=True) + assert address == "tz1Kef7BSg6fo75jk37WkKRYSnJDs69KVqt9" + + path = parse_path("m/44'/1729'/1'") + address = get_address(self.client, path, show_display=True) + assert address == "tz1ekQapZCX4AXxTJhJZhroDKDYLHDHegvm1" diff --git a/trezorlib/tests/device_tests/test_msg_tezos_getpublickey.py b/trezorlib/tests/device_tests/test_msg_tezos_getpublickey.py new file mode 100644 index 0000000000..fa0b9cd183 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_tezos_getpublickey.py @@ -0,0 +1,38 @@ +# 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 . + +import pytest + +from trezorlib.tezos import get_public_key +from trezorlib.tools import parse_path + +from .common import TrezorTest + + +@pytest.mark.tezos +@pytest.mark.skip_t1 +@pytest.mark.xfail +class TestMsgTezosGetPublicKey(TrezorTest): + def test_tezos_get_public_key(self): + self.setup_mnemonic_allallall() + + path = parse_path("m/44'/1729'/0'") + pk = get_public_key(self.client, path) + assert pk == "edpkttLhEbVfMC3DhyVVFzdwh8ncRnEWiLD1x8TAuPU7vSJak7RtBX" + + path = parse_path("m/44'/1729'/1'") + pk = get_public_key(self.client, path) + assert pk == "edpkuTPqWjcApwyD3VdJhviKM5C13zGk8c4m87crgFarQboF3Mp56f" diff --git a/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py b/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py new file mode 100644 index 0000000000..916b2115c2 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_tezos_sign_tx.py @@ -0,0 +1,195 @@ +# 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 . + +from binascii import unhexlify + +import pytest + +from trezorlib import messages, tezos +from trezorlib.protobuf import dict_to_proto +from trezorlib.tools import parse_path + +from .common import TrezorTest + +TEZOS_PATH = parse_path("m/44'/1729'/0'") + + +@pytest.mark.tezos +@pytest.mark.skip_t1 +@pytest.mark.xfail +class TestMsgTezosSignTx(TrezorTest): + def test_tezos_sign_tx_transaction(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "f2ae0c72fdd41d7a89bebfe8d6dd6d38e0fcd0782adb8194717176eb70366f64", + "transaction": { + "source": { + "tag": 0, + "hash": "00001e65c88ae6317cd62a638c8abd1e71c83c8475", + }, + "fee": 0, + "counter": 108925, + "gas_limit": 200, + "storage_limit": 0, + "amount": 10000, + "destination": { + "tag": 0, + "hash": "0004115bce5af2f977acbb900f449c14c53e1d89cf", + }, + }, + }, + ), + ) + assert ( + resp.signature + == "edsigtfmAbUJtZMAJRGMppvDzPtiWBBQiZKf7G15dV9tgkHQefwiV4JeSw5Rj57ZK54FHEthpyzCpfGvAjU8YqhHxMwZP9Z2Jmt" + ) + assert resp.sig_op_contents == unhexlify( + "f2ae0c72fdd41d7a89bebfe8d6dd6d38e0fcd0782adb8194717176eb70366f64080000001e65c88ae6317cd62a638c8abd1e71c83c847500fdd206c80100904e000004115bce5af2f977acbb900f449c14c53e1d89cf003cce7e6dfe3f79a8bd39f77d738fd79140da1a9e762b7d156eca2cf945aae978436cf68c1ec11889e4f2cf074c9642e05b3d65cc2896809af1fbdab0b126f90c" + ) + assert ( + resp.operation_hash == "opNeGBdgbM5jN2ykz4o8NdsCuJfqNZ6WBEFVbBUmYH8gp45CJvH" + ) + + def test_tezos_sign_reveal_transaction(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "03cbce9a5ea1fae2566f7f244a01edc5869f5ada9d0bf21c1098017c59be98e0", + "reveal": { + "source": { + "tag": 0, + "hash": "00001e65c88ae6317cd62a638c8abd1e71c83c8475", + }, + "fee": 0, + "counter": 108923, + "gas_limit": 200, + "storage_limit": 0, + "public_key": "00200da2c0200927dd8168b2b62e1322637521fcefb3184e61c1c3123c7c00bb95", + }, + "transaction": { + "source": { + "tag": 0, + "hash": "00001e65c88ae6317cd62a638c8abd1e71c83c8475", + }, + "fee": 0, + "counter": 108924, + "gas_limit": 200, + "storage_limit": 0, + "amount": 10000, + "destination": { + "tag": 0, + "hash": "0004115bce5af2f977acbb900f449c14c53e1d89cf", + }, + }, + }, + ), + ) + assert ( + resp.signature + == "edsigtheQQ78dZM9Sir78T3TNdfnyHrbFw8w3hiGMaLD5mPbGrUiD1jvy5fpsNJW9T5o7qrWBe7y7bai6vZ5KhwJ5HKZ8UnoCbh" + ) + assert resp.sig_op_contents == unhexlify( + "03cbce9a5ea1fae2566f7f244a01edc5869f5ada9d0bf21c1098017c59be98e0070000001e65c88ae6317cd62a638c8abd1e71c83c847500fbd206c8010000200da2c0200927dd8168b2b62e1322637521fcefb3184e61c1c3123c7c00bb95080000001e65c88ae6317cd62a638c8abd1e71c83c847500fcd206c80100904e000004115bce5af2f977acbb900f449c14c53e1d89cf004b33e241c90b828c31cf44a28c123aee3f161049c3cb4c42ec71dd96fbbf8dae9963bdadb33f51d7c6f11ff0e74f0baad742352d980a1899f69c3c65c70fe40f" + ) + assert ( + resp.operation_hash == "opQHu93L8juNm2VjmsMKioFowWNyMvGzopcuoVcuzFV1bJMhJef" + ) + + def test_tezos_sign_tx_origination(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "5e556181029c4ce5e54c9ffcbba2fc0966ed4d880ddeb0849bf6387438a7a877", + "origination": { + "source": { + "tag": 0, + "hash": "00001e65c88ae6317cd62a638c8abd1e71c83c8475", + }, + "fee": 0, + "counter": 108929, + "gas_limit": 10000, + "storage_limit": 100, + "manager_pubkey": "00001e65c88ae6317cd62a638c8abd1e71c83c8475", + "balance": 2000000, + "spendable": True, + "delegatable": True, + "delegate": "0049a35041e4be130977d51419208ca1d487cfb2e7", + }, + }, + ), + ) + assert ( + resp.signature + == "edsigu46YtcVthQQQ2FTcuayNwTcYY1Mpo6BmwCu83qGovi4kHM9CL5h4NaV4NQw8RTEP1VgraR6Kiv5J6RQsDLMzG17V6fcYwp" + ) + assert resp.sig_op_contents == unhexlify( + "5e556181029c4ce5e54c9ffcbba2fc0966ed4d880ddeb0849bf6387438a7a877090000001e65c88ae6317cd62a638c8abd1e71c83c84750081d306904e6400001e65c88ae6317cd62a638c8abd1e71c83c847580897affffff0049a35041e4be130977d51419208ca1d487cfb2e700e785342fd2258277741f93c17c5022ea1be059f47f3e343600e83c50ca191e8318da9e5ec237be9657d0fc6aba654f476c945430239a3c6dfeca21e06be98706" + ) + assert ( + resp.operation_hash == "onuKkBtP4K2JMGg7YMv7qs869B8aHCEUQecvuiL71aKkY8iPCb6" + ) + + def test_tezos_sign_tx_delegation(self): + self.setup_mnemonic_allallall() + + resp = tezos.sign_tx( + self.client, + TEZOS_PATH, + dict_to_proto( + messages.TezosSignTx, + { + "branch": "9b8b8bc45d611a3ada20ad0f4b6f0bfd72ab395cc52213a57b14d1fb75b37fd0", + "delegation": { + "source": { + "tag": 0, + "hash": "00001e65c88ae6317cd62a638c8abd1e71c83c8475", + }, + "fee": 0, + "counter": 108927, + "gas_limit": 200, + "storage_limit": 0, + "delegate": "0049a35041e4be130977d51419208ca1d487cfb2e7", + }, + }, + ), + ) + assert ( + resp.signature + == "edsigu3qGseaB2MghcGQWNWUhPtWgM9rC62FTEVrYWGtzFTHShDxGGmLFfEpJyToRCeRqcgGm3pyXY3NdyATkjmFTtUvJKvb3rX" + ) + assert resp.sig_op_contents == unhexlify( + "9b8b8bc45d611a3ada20ad0f4b6f0bfd72ab395cc52213a57b14d1fb75b37fd00a0000001e65c88ae6317cd62a638c8abd1e71c83c847500ffd206c80100ff0049a35041e4be130977d51419208ca1d487cfb2e7e581d41daf8cab833d5b99151a0303fd04472eb990f7338d7be57afe21c26e779ff4341511694aebd901a0d74d183bbcb726a9be4b873d3b47298f99f2b7e80c" + ) + assert ( + resp.operation_hash == "oocgc3hyKsGHPsw6WFWJpWT8jBwQLtebQAXF27KNisThkzoj635" + ) diff --git a/trezorlib/tezos.py b/trezorlib/tezos.py new file mode 100644 index 0000000000..db0a6114d4 --- /dev/null +++ b/trezorlib/tezos.py @@ -0,0 +1,38 @@ +# 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 . + +from . import messages +from .tools import expect + + +@expect(messages.TezosAddress, field="address") +def get_address(client, address_n, show_display=False): + return client.call( + messages.TezosGetAddress(address_n=address_n, show_display=show_display) + ) + + +@expect(messages.TezosPublicKey, field="public_key") +def get_public_key(client, address_n, show_display=False): + return client.call( + messages.TezosGetPublicKey(address_n=address_n, show_display=show_display) + ) + + +@expect(messages.TezosSignedTx) +def sign_tx(client, address_n, sign_tx_msg): + sign_tx_msg.address_n = address_n + return client.call(sign_tx_msg)