diff --git a/README.md b/README.md index 65b055d1f5..3531633a62 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ -python-trezor -============= +# python-trezor -[![image](https://travis-ci.org/trezor/python-trezor.svg?branch=master)](https://travis-ci.org/trezor/python-trezor) - -[![image](https://badges.gitter.im/trezor/community.svg)](https://gitter.im/trezor/community) +[![image](https://travis-ci.org/trezor/python-trezor.svg?branch=master)](https://travis-ci.org/trezor/python-trezor) [![repology](https://repology.org/badge/tiny-repos/python:trezor.svg)](https://repology.org/metapackage/python:trezor) [![image](https://badges.gitter.im/trezor/community.svg)](https://gitter.im/trezor/community) Python library and commandline client for communicating with TREZOR Hardware Wallet See for more information -Install -------- +## Install Python-trezor requires Python 3.3 or higher, and libusb 1.0. The easiest way to install it is with `pip`. The rest of this guide assumes you have @@ -79,8 +75,7 @@ cd /usr/ports/security/py-trezor make install clean ``` -Command line client (trezorctl) ------------------------------- +## Command line client (trezorctl) The included `trezorctl` python script can perform various tasks such as changing setting in the Trezor, signing transactions, retrieving account @@ -91,38 +86,35 @@ NOTE: An older version of the `trezorctl` command is [available for Debian Stretch](https://packages.debian.org/en/stretch/python-trezor) (and comes pre-installed on [Tails OS](https://tails.boum.org/)). -Python Library --------------- +## Python Library You can use this python library to interact with a Bitcoin Trezor and use its capabilities in your application. See examples here in the [tools/](tools/) sub folder. -PIN Entering ------------- +## PIN Entering When you are asked for PIN, you have to enter scrambled PIN. Follow the numbers shown on TREZOR display and enter the their positions using the numeric keyboard mapping: - -
789 -
456 -
123 -
+| | | | +|---|---|---| +| 7 | 8 | 9 | +| 4 | 5 | 6 | +| 1 | 2 | 3 | Example: your PIN is **1234** and TREZOR is displaying the following: - -
283 -
546 -
791 -
+| | | | +|---|---|---| +| 2 | 8 | 3 | +| 5 | 4 | 6 | +| 7 | 9 | 1 | You have to enter: **3795** -Contributing ------------- +## Contributing Python-trezor pulls coins info and protobuf messages from [trezor-common](https://github.com/trezor/trezor-common) repository. If diff --git a/setup.py b/setup.py index 2ad32c9be0..2f370cec6f 100755 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ class PrebuildCommand(Command): try: proto_srcs = glob.glob(os.path.join(TREZOR_COMMON, "protob", "*.proto")) subprocess.check_call([ + sys.executable, os.path.join(TREZOR_COMMON, "protob", "pb2py"), "-o", os.path.join(CWD, "trezorlib", "messages"), "-P", "..protobuf", diff --git a/trezorctl b/trezorctl index 01afc1e355..f42ba7dab8 100755 --- a/trezorctl +++ b/trezorctl @@ -38,6 +38,7 @@ from trezorlib import messages as proto from trezorlib import protobuf from trezorlib import stellar from trezorlib import tools +from trezorlib import ripple class ChoiceType(click.Choice): @@ -575,6 +576,10 @@ def sign_tx(connect, coin): sequence = click.prompt('Sequence Number to use (RBF opt-in enabled by default)', type=int, default=0xfffffffd) script_type = click.prompt('Input type', type=CHOICE_INPUT_SCRIPT_TYPE, default=default_script_type(address_n)) script_type = script_type if isinstance(script_type, int) else CHOICE_INPUT_SCRIPT_TYPE.typemap[script_type] + if txapi.bip115: + prev_output = txapi.get_tx(binascii.hexlify(prev_hash).decode("utf-8")).bin_outputs[prev_index] + prev_blockhash = prev_output.block_hash + prev_blockheight = prev_output.block_height inputs.append(proto.TxInputType( address_n=address_n, prev_hash=prev_hash, @@ -582,8 +587,15 @@ def sign_tx(connect, coin): amount=amount, script_type=script_type, sequence=sequence, + prev_block_hash_bip115=prev_blockhash, + prev_block_height_bip115=prev_blockheight, )) + if txapi.bip115: + current_block_height = txapi.current_height() + block_height = current_block_height - 300 # Zencash recommendation for the better protection + block_hash = txapi.get_block_hash(block_height) + outputs = [] while True: click.echo() @@ -603,6 +615,8 @@ def sign_tx(connect, coin): address=address, amount=amount, script_type=script_type, + block_hash_bip115=block_hash[::-1], # Blockhash passed in reverse order + block_height_bip115=block_height )) tx_version = click.prompt('Transaction version', type=int, default=2) @@ -1050,6 +1064,36 @@ def stellar_sign_transaction(connect, b64envelope, address, network_passphrase): return base64.b64encode(resp.signature) + +# +# Ripple functions +# +@cli.command(help='Get Ripple address') +@click.option('-n', '--address', required=True, help="BIP-32 path to key, e.g. m/44'/144'/0'/0/0") +@click.option('-d', '--show-display', is_flag=True) +@click.pass_obj +def ripple_get_address(connect, address, show_display): + client = connect() + address_n = tools.parse_path(address) + return ripple.get_address(client, address_n, show_display) + + +@cli.command(help='Sign Ripple transaction') +@click.option('-n', '--address', required=True, help="BIP-32 path to key, e.g. m/44'/144'/0'/0/0") +@click.option('-f', '--file', type=click.File('r'), default='-', help='Transaction in JSON format') +@click.pass_obj +def ripple_sign_tx(connect, address, file): + client = connect() + address_n = tools.parse_path(address) + msg = ripple.create_sign_tx_msg(json.load(file)) + + result = ripple.sign_tx(client, address_n, msg) + click.echo("Signature:") + click.echo(binascii.hexlify(result.signature)) + click.echo() + click.echo("Serialized tx including the signature:") + click.echo(binascii.hexlify(result.serialized_tx)) + # # Main # diff --git a/trezorlib/coins.py b/trezorlib/coins.py index c4818c6116..741b9bcf41 100644 --- a/trezorlib/coins.py +++ b/trezorlib/coins.py @@ -39,8 +39,9 @@ def _insight_for_coin(coin): if not url: return None zcash = coin['coin_name'].lower().startswith('zcash') + bip115 = coin['bip115'] network = 'insight_{}'.format(coin['coin_name'].lower().replace(' ', '_')) - return TxApiInsight(network=network, url=url, zcash=zcash) + return TxApiInsight(network=network, url=url, zcash=zcash, bip115=bip115) # exported variables diff --git a/trezorlib/ripple.py b/trezorlib/ripple.py new file mode 100644 index 0000000000..6f1c38802e --- /dev/null +++ b/trezorlib/ripple.py @@ -0,0 +1,59 @@ +# 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 base64 +import struct + +from . import messages +from . import tools +from .client import field +from .client import expect + + +@field('address') +@expect(messages.RippleAddress) +def get_address(client, address_n, show_display=False): + return client.call( + messages.RippleGetAddress( + address_n=address_n, show_display=show_display)) + + +@expect(messages.RippleSignedTx) +def sign_tx(client, address_n, msg: messages.RippleSignTx): + msg.address_n = address_n + return client.call(msg) + + +def create_sign_tx_msg(transaction) -> messages.RippleSignTx: + if not all(transaction.get(k) for k in ("Fee", "Sequence", "TransactionType", "Amount", "Destination")): + raise ValueError("Some of the required fields missing (Fee, Sequence, TransactionType, Amount, Destination") + if transaction["TransactionType"] != "Payment": + raise ValueError("Only Payment transaction type is supported") + + return messages.RippleSignTx( + fee=transaction.get("Fee"), + sequence=transaction.get("Sequence"), + flags=transaction.get("Flags"), + last_ledger_sequence=transaction.get("LastLedgerSequence"), + payment=_create_payment(transaction), + ) + + +def _create_payment(transaction) -> messages.RipplePayment: + return messages.RipplePayment( + amount=transaction.get("Amount"), + destination=transaction.get("Destination") + ) diff --git a/trezorlib/stellar.py b/trezorlib/stellar.py index d8635c4a64..62e0803130 100644 --- a/trezorlib/stellar.py +++ b/trezorlib/stellar.py @@ -83,9 +83,7 @@ def parse_transaction_bytes(tx_bytes): tx - a StellarSignTx describing the transaction header operations - an array of protobuf message objects for each operation """ - tx = messages.StellarSignTx( - protocol_version=1 - ) + tx = messages.StellarSignTx() unpacker = xdrlib.Unpacker(tx_bytes) tx.source_account = _xdr_read_address(unpacker) diff --git a/trezorlib/tests/device_tests/test_msg_ethereum_signtx.py b/trezorlib/tests/device_tests/test_msg_ethereum_signtx.py index ad8f20cb00..25d284a280 100644 --- a/trezorlib/tests/device_tests/test_msg_ethereum_signtx.py +++ b/trezorlib/tests/device_tests/test_msg_ethereum_signtx.py @@ -287,100 +287,3 @@ class TestMsgEthereumSigntx(TrezorTest): to=unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'), value=12345678901234567890 ) - - def test_ethereum_signtx_nodata_eip155(self): - self.setup_mnemonic_allallall() - - with self.client: - self.client.set_expected_responses([ - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.EthereumTxRequest(), - ]) - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], - nonce=0, - gas_price=20000000000, - gas_limit=21000, - to=unhexlify('8ea7a3fccc211ed48b763b4164884ddbcf3b0a98'), - value=100000000000000000, - chain_id=3) - assert sig_v == 41 - assert hexlify(sig_r) == b'a90d0bc4f8d63be69453dd62f2bb5fff53c610000abf956672564d8a654d401a' - assert hexlify(sig_s) == b'544a2e57bc8b4da18660a1e6036967ea581cc635f5137e3ba97a750867c27cf2' - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], - nonce=1, - gas_price=20000000000, - gas_limit=21000, - to=unhexlify('8ea7a3fccc211ed48b763b4164884ddbcf3b0a98'), - value=100000000000000000, - chain_id=3) - assert sig_v == 42 - assert hexlify(sig_r) == b'699428a6950e23c6843f1bf3754f847e64e047e829978df80d55187d19a401ce' - assert hexlify(sig_s) == b'087343d0a3a2f10842218ffccb146b59a8431b6245ab389fde22dc833f171e6e' - - def test_ethereum_signtx_data_eip155(self): - self.setup_mnemonic_allallall() - - with self.client: - self.client.set_expected_responses([ - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.EthereumTxRequest(), - ]) - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], - nonce=2, - gas_price=20000000000, - gas_limit=21004, - to=unhexlify('8ea7a3fccc211ed48b763b4164884ddbcf3b0a98'), - value=100000000000000000, - data=b'\0', - chain_id=3) - assert sig_v == 42 - assert hexlify(sig_r) == b'ba85b622a8bb82606ba96c132e81fa8058172192d15bc41d7e57c031bca17df4' - assert hexlify(sig_s) == b'6473b75997634b6f692f8d672193591d299d5bf1c2d6e51f1a14ed0530b91c7d' - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], - nonce=3, - gas_price=20000000000, - gas_limit=299732, - to=unhexlify('8ea7a3fccc211ed48b763b4164884ddbcf3b0a98'), - value=100000000000000000, - data=b'ABCDEFGHIJKLMNOP' * 256 + b'!!!', - chain_id=3) - assert sig_v == 42 - assert hexlify(sig_r) == b'd021c98f92859c8db5e4de2f0e410a8deb0c977eb1a631e323ebf7484bd0d79a' - assert hexlify(sig_s) == b'2c0e9defc9b1e895dc9520ff25ba3c635b14ad70aa86a5ad6c0a3acb82b569b6' - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], - nonce=4, - gas_price=20000000000, - gas_limit=21004, - to=unhexlify('8ea7a3fccc211ed48b763b4164884ddbcf3b0a98'), - value=0, - data=b'\0', - chain_id=3) - assert sig_v == 42 - assert hexlify(sig_r) == b'dd52f026972a83c56b7dea356836fcfc70a68e3b879cdc8ef2bb5fea23e0a7aa' - assert hexlify(sig_s) == b'079285fe579c9a2da25c811b1c5c0a74cd19b6301ee42cf20ef7b3b1353f7242' - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], - nonce=5, - gas_price=0, - gas_limit=21004, - to=unhexlify('8ea7a3fccc211ed48b763b4164884ddbcf3b0a98'), - value=0, - data=b'\0', - chain_id=3) - assert sig_v == 42 - assert hexlify(sig_r) == b'f7505f709d5999343aea3c384034c62d0514336ff6c6af65582006f708f81503' - assert hexlify(sig_s) == b'44e09e29a4b6247000b46ddc94fe391e94deb2b39ad6ac6398e6db5bec095ba9' diff --git a/trezorlib/tests/device_tests/test_msg_ethereum_signtx_eip155.py b/trezorlib/tests/device_tests/test_msg_ethereum_signtx_eip155.py new file mode 100644 index 0000000000..d34bd82ad3 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_ethereum_signtx_eip155.py @@ -0,0 +1,177 @@ +# 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, hexlify +import pytest + +from .common import TrezorTest +from trezorlib import messages as proto + + +@pytest.mark.ethereum +class TestMsgEthereumSigntxChainId(TrezorTest): + def test_ethereum_signtx_eip155(self): + + # chain_id, nonce, sig_v, sig_r, sig_s, value, gas_limit, data + VECTORS = [ + ( + 3, + 0, + 41, + b"a90d0bc4f8d63be69453dd62f2bb5fff53c610000abf956672564d8a654d401a", + b"544a2e57bc8b4da18660a1e6036967ea581cc635f5137e3ba97a750867c27cf2", + 100000000000000000, + 21000, + None, + ), + ( + 3, + 1, + 42, + b"699428a6950e23c6843f1bf3754f847e64e047e829978df80d55187d19a401ce", + b"087343d0a3a2f10842218ffccb146b59a8431b6245ab389fde22dc833f171e6e", + 100000000000000000, + 21000, + None, + ), + ( + 3, + 2, + 42, + b"ba85b622a8bb82606ba96c132e81fa8058172192d15bc41d7e57c031bca17df4", + b"6473b75997634b6f692f8d672193591d299d5bf1c2d6e51f1a14ed0530b91c7d", + 100000000000000000, + 21004, + b"\0", + ), + ( + 3, + 3, + 42, + b"d021c98f92859c8db5e4de2f0e410a8deb0c977eb1a631e323ebf7484bd0d79a", + b"2c0e9defc9b1e895dc9520ff25ba3c635b14ad70aa86a5ad6c0a3acb82b569b6", + 100000000000000000, + 299732, + b"ABCDEFGHIJKLMNOP" * 256 + b"!!!", + ), + ( + 3, + 4, + 42, + b"dd52f026972a83c56b7dea356836fcfc70a68e3b879cdc8ef2bb5fea23e0a7aa", + b"079285fe579c9a2da25c811b1c5c0a74cd19b6301ee42cf20ef7b3b1353f7242", + 0, + 21004, + b"\0", + ), + ( + 1, + 1, + 37, + b"bae6198fdc87ccad6256e543617a34d052bfd17ae3be0bec7fbf8ea34bf9c930", + b"7d12f625f3e54700b6ed14ab669f45a8a2b5552c39f0781b0ab3796f19e6b4d1", + 0, + 21004, + b"\0", + ), + ( + 255, + 1, + 546, + b"7597a40719509ae3850d2eba808b7b2f7d272fda316e1321e5ebcc911e9f1b0d", + b"269dd69248273820f65b43d8824bb7aff1aa4e35ee663a5433a5df8f0c47dc31", + 0, + 21004, + b"\0", + ), + ( + 256, + 1, + 547, + b"64e9821db2001ff5dff13c9d8c7fb0701ff860f5f95155d378fb9fcc06088f28", + b"4d03f339afed717e2155f044a6b0a895b5ac98343f1745e7525870c2046c36bc", + 0, + 21004, + b"\0", + ), + ( + 65535, + 1, + 131106, + b"6f2275808dc328184d7aa019d0a68f8dd8234969576a477670934145bb358969", + b"2be1ff9045bccff9ba3d6d5c7789a52c52c9679526dd3ec349caa318c3d055ff", + 0, + 21004, + b"\0", + ), + ( + 65536, + 1, + 131107, + b"e16e35afe534a46e3e4cf09f355cbf02edc01937c2b444238162c2aca79037b8", + b"1083b84e21b1cbad95c7ea9792818c18fa716aa25951c5341b48732d611a396a", + 0, + 21004, + b"\0", + ), + ( + 16777215, + 1, + 33554466, + b"f9753ee68cf2af20638cc753945d157039504f82d6d6fe0ec98806b64366c551", + b"056b57a69d88a4b71fba993c580d8bbf04f2c857f97a8b7d4b2892b5dafa9114", + 0, + 21004, + b"\0", + ), + ( + 16777216, + 1, + 33554468, + b"23a5399650c6efa46a25a0a966a29119830d9c587b6b93da43cb0be6d3c69059", + b"5a6eddffc62317a6a3801608071655a9c43423aef9705b2f5df4212942265c37", + 0, + 21004, + b"\0", + ), + ( + 2147483629, + 1, + 4294967294, + b"6a996586f1ea19afe9cb0ca44dec6bb8643cdf53b5cf148323c94a32a04b087d", + b"0d086b208df6826657edf98010972b2649b323466a7ea4b67e7285fb9e829481", + 0, + 21004, + b"\0", + ), + ] + + self.setup_mnemonic_allallall() + + for ci, n, sv, sr, ss, v, gl, d in VECTORS: + sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( + n=[0x80000000 | 44, 0x80000000 | 1, 0x80000000, 0, 0], + nonce=n, + gas_price=20000000000, + gas_limit=gl, + to=unhexlify("8ea7a3fccc211ed48b763b4164884ddbcf3b0a98"), + value=v, + chain_id=ci, + data=d, + ) + assert sig_v == sv + assert hexlify(sig_r) == sr + assert hexlify(sig_s) == ss diff --git a/trezorlib/tests/device_tests/test_msg_ripple_get_address.py b/trezorlib/tests/device_tests/test_msg_ripple_get_address.py new file mode 100644 index 0000000000..ac43065f96 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_ripple_get_address.py @@ -0,0 +1,54 @@ +# 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 .common import TrezorTest +from .conftest import TREZOR_VERSION +from binascii import hexlify +from trezorlib.client import CallException +from trezorlib.ripple import get_address +from trezorlib.tools import parse_path + + +@pytest.mark.ripple +@pytest.mark.skip_t1 # T1 support is not planned +@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished") +class TestMsgRippleGetAddress(TrezorTest): + + def test_ripple_get_address(self): + # data from https://iancoleman.io/bip39/#english + self.setup_mnemonic_allallall() + + address = get_address(self.client, parse_path("m/44'/144'/0'/0/0")) + assert address == 'rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H' + address = get_address(self.client, parse_path("m/44'/144'/0'/0/1")) + assert address == 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws' + address = get_address(self.client, parse_path("m/44'/144'/1'/0/0")) + assert address == 'rJX2KwzaLJDyFhhtXKi3htaLfaUH2tptEX' + + def test_ripple_get_address_other(self): + # data from https://github.com/you21979/node-ripple-bip32/blob/master/test/test.js + self.client.load_device_by_mnemonic( + mnemonic='armed bundle pudding lazy strategy impulse where identify submit weekend physical antenna flight social acoustic absurd whip snack decide blur unfold fiction pumpkin athlete', + pin='', + passphrase_protection=False, + label='test', + language='english') + address = get_address(self.client, parse_path("m/44'/144'/0'/0/0")) + assert address == 'r4ocGE47gm4G4LkA9mriVHQqzpMLBTgnTY' + address = get_address(self.client, parse_path("m/44'/144'/0'/0/1")) + assert address == 'rUt9ULSrUvfCmke8HTFU1szbmFpWzVbBXW' diff --git a/trezorlib/tests/device_tests/test_msg_ripple_sign_tx.py b/trezorlib/tests/device_tests/test_msg_ripple_sign_tx.py new file mode 100644 index 0000000000..43f64c7b7f --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_ripple_sign_tx.py @@ -0,0 +1,86 @@ +# 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 .common import TrezorTest +from .conftest import TREZOR_VERSION +from binascii import unhexlify +from trezorlib import messages +from trezorlib import ripple +from trezorlib.client import CallException +from trezorlib.tools import parse_path + + +@pytest.mark.ripple +@pytest.mark.skip_t1 # T1 support is not planned +@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished") +class TestMsgRippleSignTx(TrezorTest): + + def test_ripple_sign_simple_tx(self): + self.setup_mnemonic_allallall() + + msg = ripple.create_sign_tx_msg({ + "TransactionType": "Payment", + "Destination": "rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws", + "Amount": 100000000, + "Flags": 0x80000000, + "Fee": 100000, + "Sequence": 25, + }) + resp = ripple.sign_tx(self.client, parse_path("m/44'/144'/0'/0/0"), msg) + assert resp.signature == unhexlify('3045022100e243ef623675eeeb95965c35c3e06d63a9fc68bb37e17dc87af9c0af83ec057e02206ca8aa5eaab8396397aef6d38d25710441faf7c79d292ee1d627df15ad9346c0') + assert resp.serialized_tx == unhexlify('12000022800000002400000019614000000005f5e1006840000000000186a0732102131facd1eab748d6cddc492f54b04e8c35658894f4add2232ebc5afe7521dbe474473045022100e243ef623675eeeb95965c35c3e06d63a9fc68bb37e17dc87af9c0af83ec057e02206ca8aa5eaab8396397aef6d38d25710441faf7c79d292ee1d627df15ad9346c081148fb40e1ffa5d557ce9851a535af94965e0dd098883147148ebebf7304ccdf1676fefcf9734cf1e780826') + + msg = ripple.create_sign_tx_msg({ + "TransactionType": "Payment", + "Destination": "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H", + "Amount": 1, + "Fee": 10, + "Sequence": 1, + }) + resp = ripple.sign_tx(self.client, parse_path("m/44'/144'/0'/0/2"), msg) + assert resp.signature == unhexlify('3044022069900e6e578997fad5189981b74b16badc7ba8b9f1052694033fa2779113ddc002206c8006ada310edf099fb22c0c12073550c8fc73247b236a974c5f1144831dd5f') + assert resp.serialized_tx == unhexlify('1200002280000000240000000161400000000000000168400000000000000a732103dbed1e77cb91a005e2ec71afbccce5444c9be58276665a3859040f692de8fed274463044022069900e6e578997fad5189981b74b16badc7ba8b9f1052694033fa2779113ddc002206c8006ada310edf099fb22c0c12073550c8fc73247b236a974c5f1144831dd5f8114bdf86f3ae715ba346b7772ea0e133f48828b766483148fb40e1ffa5d557ce9851a535af94965e0dd0988') + + msg = ripple.create_sign_tx_msg({ + "TransactionType": "Payment", + "Destination": "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H", + "Amount": 100000009, + "Flags": 0, + "Fee": 100, + "Sequence": 100, + "LastLedgerSequence": 333111, + }) + resp = ripple.sign_tx(self.client, parse_path("m/44'/144'/0'/0/2"), msg) + assert resp.signature == unhexlify('30440220025a9cc2809527799e6ea5eb029488dc46c6632a8ca1ed7d3ca2d9211e80403a02202cfe8604e6c6d1d3c64246626cc1a1a9bd8a2163b969e561c6adda5dca8fc2a5') + assert resp.serialized_tx == unhexlify('12000022800000002400000064201b00051537614000000005f5e109684000000000000064732103dbed1e77cb91a005e2ec71afbccce5444c9be58276665a3859040f692de8fed2744630440220025a9cc2809527799e6ea5eb029488dc46c6632a8ca1ed7d3ca2d9211e80403a02202cfe8604e6c6d1d3c64246626cc1a1a9bd8a2163b969e561c6adda5dca8fc2a58114bdf86f3ae715ba346b7772ea0e133f48828b766483148fb40e1ffa5d557ce9851a535af94965e0dd0988') + + def test_ripple_sign_invalid_fee(self): + self.setup_mnemonic_allallall() + + msg = ripple.create_sign_tx_msg({ + "TransactionType": "Payment", + "Destination": "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H", + "Amount": 1, + "Flags": 1, + "Fee": 1, + "Sequence": 1, + }) + with pytest.raises(CallException) as exc: + ripple.sign_tx(self.client, parse_path("m/44'/144'/0'/0/2"), msg) + assert exc.value.args[0] == messages.FailureType.ProcessError + assert exc.value.args[1].endswith('Fee must be in the range of 10 to 10,000 drops') diff --git a/trezorlib/tests/device_tests/test_msg_stellar_get_address.py b/trezorlib/tests/device_tests/test_msg_stellar_get_address.py index a0b0d510e0..f07b32ff53 100644 --- a/trezorlib/tests/device_tests/test_msg_stellar_get_address.py +++ b/trezorlib/tests/device_tests/test_msg_stellar_get_address.py @@ -26,7 +26,6 @@ from trezorlib.tools import parse_path @pytest.mark.stellar -@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished") class TestMsgStellarGetAddress(TrezorTest): def test_stellar_get_address(self): @@ -47,7 +46,7 @@ class TestMsgStellarGetAddress(TrezorTest): address = self.client.stellar_get_address(parse_path(stellar.DEFAULT_BIP32_PATH)) assert address == 'GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6' - address = self.client.stellar_get_address(parse_path("m/44h/148h/1h")) + address = self.client.stellar_get_address(parse_path("m/44h/148h/1h"), show_display=True) assert address == 'GBAW5XGWORWVFE2XTJYDTLDHXTY2Q2MO73HYCGB3XMFMQ562Q2W2GJQX' def test_stellar_get_address_get_pubkey(self): diff --git a/trezorlib/tests/device_tests/test_msg_stellar_get_public_key.py b/trezorlib/tests/device_tests/test_msg_stellar_get_public_key.py index adfb1788ed..1442d97958 100644 --- a/trezorlib/tests/device_tests/test_msg_stellar_get_public_key.py +++ b/trezorlib/tests/device_tests/test_msg_stellar_get_public_key.py @@ -20,20 +20,19 @@ from .common import TrezorTest from .conftest import TREZOR_VERSION from binascii import hexlify from trezorlib import stellar -from trezorlib import messages as proto +from trezorlib import messages from trezorlib.client import CallException from trezorlib.tools import parse_path @pytest.mark.stellar -@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished") class TestMsgStellarGetPublicKey(TrezorTest): def test_stellar_get_public_key(self): self.setup_mnemonic_nopin_nopassphrase() # GAK5MSF74TJW6GLM7NLTL76YZJKM2S4CGP3UH4REJHPHZ4YBZW2GSBPW - response = self.client.stellar_get_public_key(parse_path(stellar.DEFAULT_BIP32_PATH)) + response = self.client.stellar_get_public_key(parse_path(stellar.DEFAULT_BIP32_PATH), show_display=True) assert hexlify(response) == b'15d648bfe4d36f196cfb5735ffd8ca54cd4b8233f743f22449de7cf301cdb469' assert stellar.address_from_public_key(response) == 'GAK5MSF74TJW6GLM7NLTL76YZJKM2S4CGP3UH4REJHPHZ4YBZW2GSBPW' @@ -44,8 +43,8 @@ class TestMsgStellarGetPublicKey(TrezorTest): self.client.stellar_get_public_key(parse_path('m/0/1')) if TREZOR_VERSION == 1: - assert exc.value.args[0] == proto.FailureType.ProcessError + assert exc.value.args[0] == messages.FailureType.ProcessError assert exc.value.args[1].endswith('Failed to derive private key') else: - assert exc.value.args[0] == proto.FailureType.FirmwareError + assert exc.value.args[0] == messages.FailureType.FirmwareError assert exc.value.args[1].endswith('Firmware error') diff --git a/trezorlib/tests/device_tests/test_msg_stellar_sign_transaction.py b/trezorlib/tests/device_tests/test_msg_stellar_sign_transaction.py index b85b598fbd..e66de3160c 100644 --- a/trezorlib/tests/device_tests/test_msg_stellar_sign_transaction.py +++ b/trezorlib/tests/device_tests/test_msg_stellar_sign_transaction.py @@ -57,7 +57,6 @@ import pytest @pytest.mark.stellar -@pytest.mark.xfail(TREZOR_VERSION == 2, reason="T2 support is not yet finished") class TestMsgStellarSignTransaction(TrezorTest): ADDRESS_N = parse_path(stellar.DEFAULT_BIP32_PATH) @@ -158,7 +157,6 @@ class TestMsgStellarSignTransaction(TrezorTest): def _create_msg(self) -> proto.StellarSignTx: tx = proto.StellarSignTx() - tx.protocol_version = 1 tx.source_account = 'GAK5MSF74TJW6GLM7NLTL76YZJKM2S4CGP3UH4REJHPHZ4YBZW2GSBPW' tx.fee = 100 tx.sequence_number = 0x100000000 diff --git a/trezorlib/tests/unit_tests/test_tx_api.py b/trezorlib/tests/unit_tests/test_tx_api.py index f67caea445..e914b82819 100644 --- a/trezorlib/tests/unit_tests/test_tx_api.py +++ b/trezorlib/tests/unit_tests/test_tx_api.py @@ -18,9 +18,11 @@ import os from trezorlib import coins from trezorlib import tx_api +import binascii TxApiBitcoin = coins.tx_api['Bitcoin'] TxApiTestnet = tx_api.TxApiInsight("insight_testnet") +TxApiZencash = coins.tx_api['Zencash'] tests_dir = os.path.dirname(os.path.abspath(__file__)) @@ -42,3 +44,13 @@ def test_tx_api_gettx(): TxApiTestnet.get_tx('6f90f3c7cbec2258b0971056ef3fe34128dbde30daa9c0639a898f9977299d54') TxApiTestnet.get_tx('d6da21677d7cca5f42fbc7631d062c9ae918a0254f7c6c22de8e8cb7fd5b8236') + + +def test_tx_api_current_block(): + height = TxApiZencash.current_height() + assert height > 347041 + + +def test_tx_api_get_block_hash(): + hash = TxApiZencash.get_block_hash(110000) + assert hash == binascii.unhexlify('000000003f5d6ba1385c6cd2d4f836dfc5adf7f98834309ad67e26faef462454') diff --git a/trezorlib/tx_api.py b/trezorlib/tx_api.py index 28bf37e439..f72ad63ab9 100644 --- a/trezorlib/tx_api.py +++ b/trezorlib/tx_api.py @@ -33,6 +33,18 @@ class TxApi(object): url = '%s%s/%s' % (self.url, resource, resourceid) return url + def current_height(self): + r = requests.get(self.url + '/status?q=getBlockCount') + j = r.json(parse_float=str) + block_height = j['info']['blocks'] + return block_height + + def get_block_hash(self, block_number): + r = requests.get(self.url + '/block-index/' + str(block_number)) + j = r.json(parse_float=str) + block_hash = binascii.unhexlify(j['blockHash']) + return block_hash + def fetch_json(self, resource, resourceid): global cache_dir if cache_dir: @@ -65,11 +77,13 @@ class TxApi(object): class TxApiInsight(TxApi): - def __init__(self, network, url=None, zcash=None): + def __init__(self, network, url=None, zcash=None, bip115=False): super().__init__(network, url) self.zcash = zcash + self.bip115 = bip115 if url: - self.pushtx_url = url.replace('/api/', '/tx/send') + prefix, suffix = url.rsplit('/', maxsplit=1) + self.pushtx_url = prefix + '/tx/send' def get_tx(self, txhash): @@ -97,6 +111,12 @@ class TxApiInsight(TxApi): o = t._add_bin_outputs() o.amount = int(Decimal(vout['value']) * 100000000) o.script_pubkey = binascii.unhexlify(vout['scriptPubKey']['hex']) + if self.bip115 and o.script_pubkey[-1] == 0xb4: + # Verify if coin implements replay protection bip115 and script includes checkblockatheight opcode. 0xb4 - is op_code (OP_CHECKBLOCKATHEIGHT) + # <32-byte block hash> <3-byte block height> + tail = o.script_pubkey[-38:] + o.block_hash = tail[1:33] # <32-byte block hash> + o.block_height = int.from_bytes(tail[34:37], byteorder='little') # <3-byte block height> if self.zcash: t.overwintered = data.get('fOverwintered', False) diff --git a/vendor/trezor-common b/vendor/trezor-common index 2301f7034c..32850a6262 160000 --- a/vendor/trezor-common +++ b/vendor/trezor-common @@ -1 +1 @@ -Subproject commit 2301f7034c1b7a3d1b0be5d4447d432593649a1e +Subproject commit 32850a62624ce1a6612ff545beb706560c3716da