diff --git a/.flake8 b/.flake8 index 52c02f4bfe..a89b939558 100644 --- a/.flake8 +++ b/.flake8 @@ -1,17 +1,13 @@ [flake8] filename = *.py, - trezorctl + ./trezorctl exclude = .tox/, build/, dist/, - trezorlib/*_pb2.py + vendor/, ignore = - # F821 undefined name 'unicode' - F821, - # F841 local variable is assigned to but never used - F841, # F401: module imported but unused F401, # F403: used import * diff --git a/.gitignore b/.gitignore index 452facd367..4c8387bc2a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ docs/_build docs/.docs-build-environment .tox/ .cache/ +.pytest_cache/ + +trezorlib/coins.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..3680066ef4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/trezor-common"] + path = vendor/trezor-common + url = https://github.com/trezor/trezor-common.git diff --git a/.travis.yml b/.travis.yml index c82b64a02a..05602196c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,10 @@ addons: - libudev-dev - libusb-1.0-0-dev +env: + global: + PROTOBUF_VERSION=3.4.0 + python: - "3.3" - "3.4" @@ -23,14 +27,20 @@ python: install: # Optimisation: build requirements as wheels, which get cached by Travis - pip install "pip>=7.0" wheel - - pip install "setuptools>=19.0" + - pip install "setuptools>=38" - pip install tox-travis - pip install flake8 + # protobuf-related dependencies + - curl -LO "https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" + - unzip "protoc-${PROTOBUF_VERSION}-linux-x86_64.zip" -d protoc + - export PATH="$(pwd)/protoc/bin:$PATH" + - pip install "protobuf == ${PROTOBUF_VERSION}" script: + # check that generated protobuf messages are identical to in-tree ones + - ./tools/build_protobuf messages.tmp && diff -ur messages.tmp trezorlib/messages && rm -r messages.tmp - python setup.py install - flake8 - - flake8 trezorctl - tox notifications: diff --git a/MANIFEST.in b/MANIFEST.in index c245d1bec6..7e82f7c37d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,6 @@ recursive-include bash_completion.d *.sh +include tools/* include trezorlib/tests/txcache/*.json +include vendor/trezor-common/coins.json +include vendor/trezor-common/protob/*.proto include COPYING diff --git a/README.rst b/README.rst index bdf8c781d9..02f75eadab 100644 --- a/README.rst +++ b/README.rst @@ -77,3 +77,23 @@ Example: your PIN is **1234** and TREZOR is displaying the following: === === === You have to enter: **3795** + + +Contributing +------------ + +Python-trezor pulls coins info and protobuf messages from `trezor-common `_ repository. If you are +developing new features for Trezor, you will want to start there. Once your changes are accepted to ``trezor-common``, you can make a PR +against this repository. Don't forget to update the submodule with: + +.. code:: + + git submodule update --init --remote + +Then, rebuild the protobuf messages and get ``coins.json`` by running: + +.. code:: + + python3 setup.py prebuild + +To get support for BTC-like coins, these steps are enough and no further changes to the library are necessary. diff --git a/setup.py b/setup.py index 4d5f992479..17e76015af 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 -from setuptools import setup +import os.path +import shutil +import subprocess + +from setuptools import setup, Command +from setuptools.command.build_py import build_py install_requires = [ 'setuptools>=19.0', @@ -24,6 +29,44 @@ else: from trezorlib import __version__ as VERSION + +class PrebuildCommand(Command): + description = 'update vendored files (coins.json, protobuf messages)' + user_options = [] + + TREZOR_COMMON = os.path.join('vendor', 'trezor-common') + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + # check for existence of the submodule directory + coins_json = os.path.join(self.TREZOR_COMMON, 'coins.json') + if not os.path.exists(coins_json): + raise Exception('trezor-common submodule seems to be missing.\n' + + 'Use "git submodule update --init" to retrieve it.') + + # copy coins.json to the tree + shutil.copy(coins_json, 'trezorlib') + + # regenerate messages + try: + subprocess.check_call(os.path.join(os.getcwd(), 'tools', 'build_protobuf')) + except Exception as e: + print(e) + print("Generating protobuf failed. Maybe you don't have 'protoc', or maybe you are on Windows?") + print("Using pre-generated files.") + + +class CustomBuild(build_py): + def run(self): + self.run_command('prebuild') + super().run() + + setup( name='trezor', version=VERSION, @@ -39,6 +82,9 @@ setup( 'trezorlib.tests.device_tests', 'trezorlib.tests.unit_tests', ], + package_data={ + 'trezorlib': ['coins.json'], + }, scripts=['trezorctl'], install_requires=install_requires, python_requires='>=3.3', @@ -51,4 +97,8 @@ setup( 'Operating System :: MacOS :: MacOS X', 'Programming Language :: Python :: 3 :: Only', ], + cmdclass={ + 'prebuild': PrebuildCommand, + 'build_py': CustomBuild, + }, ) diff --git a/tools/build_protobuf b/tools/build_protobuf index 04ddc9824b..fd2eb5b69b 100755 --- a/tools/build_protobuf +++ b/tools/build_protobuf @@ -1,32 +1,52 @@ #!/bin/bash set -e +if [ -n "$1" ]; then + OUTDIR=`readlink -f "$1"` +fi + cd "$(dirname "$0")" -GENPATH="../trezorlib/messages" -INDEX="$GENPATH/__init__.py" -PROTO_PATH="../../trezor-common/protob" +# set up paths +INDEX="__init__.py" +GENPATH="${OUTDIR:-../trezorlib/messages}" +PROTO_PATH="../vendor/trezor-common/protob" PROTO_FILES="types messages" -PB2_OUT="pb2" -rm -f "$GENPATH/[A-Z]*.py" -mkdir -p "$GENPATH" +# set up temporary directory & cleanup +TMPDIR=$(mktemp -d) +function cleanup { + rm -r $TMPDIR +} +trap cleanup EXIT -cat > "$INDEX" << EOF +# set up pb2 outdir +PB2_OUT="$TMPDIR/pb2" +mkdir -p "$PB2_OUT" + +# compile .proto files to python2 modules using google protobuf library +for file in $PROTO_FILES; do + protoc --python_out="$PB2_OUT" -I/usr/include -I"$PROTO_PATH" "$PROTO_PATH/$file.proto" +done + +# create index (__init__.py) +cat > "$TMPDIR/$INDEX" << EOF # Automatically generated by pb2py EOF -mkdir -p "$PB2_OUT" - +# convert google protobuf library to trezor's internal format for file in $PROTO_FILES; do - # Compile .proto files to python2 modules using google protobuf library - protoc --python_out="$PB2_OUT" -I/usr/include -I"$PROTO_PATH" "$PROTO_PATH/$file.proto" + ./pb2py -P "trezorlib.protobuf" -p "$PB2_OUT" -l "$TMPDIR/$INDEX" "$file" "$TMPDIR" done -for file in $PROTO_FILES; do - # Convert google protobuf library to trezor's internal format - ./pb2py -P "trezorlib.protobuf" -p "$PB2_OUT" -l "$INDEX" "$file" "$GENPATH" -done +# ensure $GENPATH exists and is empty of messages +mkdir -p "$GENPATH" +# only remove messages - there could possibly be other files not starting with capital letter +rm -f "$GENPATH"/[A-Z]*.py -rm -rf "$PB2_OUT" +# move generated files to the destination +# (this assumes $INDEX is *.py, otherwise we'd have to add $INDEX separately) +mv "$TMPDIR"/*.py "$GENPATH" + +# the exit trap handles removing the tmp directory diff --git a/tools/deserialize_tx.py b/tools/deserialize_tx.py new file mode 100755 index 0000000000..7d91b36d4d --- /dev/null +++ b/tools/deserialize_tx.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +import binascii +import os +import sys + +try: + import construct as c +except ImportError: + sys.stderr.write("This tool requires Construct. Install it with 'pip install Construct'.\n") + sys.exit(1) + +from construct import this, len_ + +if os.isatty(sys.stdin.fileno()): + tx_hex = input("Enter transaction in hex format: ") +else: + tx_hex = sys.stdin.read().strip() + +tx_bin = binascii.unhexlify(tx_hex) + + +CompactUintStruct = c.Struct( + "base" / c.Int8ul, + "ext" / c.Switch(this.base, {0xfd: c.Int16ul, 0xfe: c.Int32ul, 0xff: c.Int64ul}), +) + + +class CompactUintAdapter(c.Adapter): + def _encode(self, obj, context, path): + if obj < 0xfd: + return {"base": obj} + if obj < 2 ** 16: + return {"base": 0xfd, "ext": obj} + if obj < 2 ** 32: + return {"base": 0xfe, "ext": obj} + if obj < 2 ** 64: + return {"base": 0xff, "ext": obj} + raise ValueError("Value too big for compact uint") + + def _decode(self, obj, context, path): + return obj["ext"] or obj["base"] + + +class ConstFlag(c.Adapter): + def __init__(self, const): + self.const = const + super().__init__(c.Optional(c.Const(const))) + + def _encode(self, obj, context, path): + return self.const if obj else None + + def _decode(self, obj, context, path): + return obj is not None + + +CompactUint = CompactUintAdapter(CompactUintStruct) + +TxInput = c.Struct( + "tx" / c.Bytes(32), + "index" / c.Int32ul, + # TODO coinbase tx + "script" / c.Prefixed(CompactUint, c.GreedyBytes), + "sequence" / c.Int32ul, +) + +TxOutput = c.Struct( + "value" / c.Int64ul, + "pk_script" / c.Prefixed(CompactUint, c.GreedyBytes), +) + +StackItem = c.Prefixed(CompactUint, c.GreedyBytes) +TxInputWitness = c.PrefixedArray(CompactUint, StackItem) + +Transaction = c.Struct( + "version" / c.Int32ul, + "segwit" / ConstFlag(b"\x00\x01"), + "inputs" / c.PrefixedArray(CompactUint, TxInput), + "outputs" / c.PrefixedArray(CompactUint, TxOutput), + "witness" / c.If(this.segwit, TxInputWitness[len_(this.inputs)]), + "lock_time" / c.Int32ul, + c.Terminated, +) + +print(Transaction.parse(tx_bin)) diff --git a/tools/encfs_aes_getpass.py b/tools/encfs_aes_getpass.py index 64ba603f80..4e4b005eda 100755 --- a/tools/encfs_aes_getpass.py +++ b/tools/encfs_aes_getpass.py @@ -102,9 +102,9 @@ def main(): data = {'label': label, 'bip32_path': bip32_path, - 'password_encrypted_hex': binascii.hexlify(passw_encrypted)} + 'password_encrypted_hex': binascii.hexlify(passw_encrypted).decode('ascii')} - json.dump(data, open(passw_file, 'wb')) + json.dump(data, open(passw_file, 'w')) # Let's load password data = json.load(open(passw_file, 'r')) diff --git a/tools/signtest.py b/tools/signtest.py index 7ec2f4f5c7..547c3c6953 100755 --- a/tools/signtest.py +++ b/tools/signtest.py @@ -111,7 +111,7 @@ class MyTxApiBitcoin(object): txser = self.serialize_tx(t) txhash = tools.Hash(txser)[::-1] - outi = self.inputs.append( + self.inputs.append( proto_types.TxInputType( address_n=self.client.expand_path("44'/0'/0'/0/%d" % idx), script_type=( @@ -178,42 +178,42 @@ def main(): # Get the first address of first BIP44 account # (should be the same address as shown in wallet.trezor.io) - outputs = [ - proto_types.TxOutputType( - amount=0, - script_type=proto_types.PAYTOADDRESS, - address='p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm' - # op_return_data=binascii.unhexlify('2890770995194662774cd192ee383b805e9a066e6a456be037727649228fb7f6') - # address_n=client.expand_path("44'/0'/0'/0/35"), - # address='3PUxV6Cc4udQZQsJhArVUzvvVoKC8ohkAj', - ), - # proto_types.TxOutputType( - # amount=0, - # script_type=proto_types.PAYTOOPRETURN, - # op_return_data=binascii.unhexlify('2890770995194662774cd192ee383b805e9a066e6a456be037727649228fb7f6') - # ), - # proto_types.TxOutputType( - # amount= 8120, - # script_type=proto_types.PAYTOADDRESS, - # address_n=client.expand_path("44'/1'/0'/1/0"), - # address='1PtCkQgyN6xHmXWzLmFFrDNA5vYhYLeNFZ', - # address='14KRxYgFc7Se8j7MDdrK5PTNv8meq4GivK', - # ), - # proto_types.TxOutputType( - # amount= 18684 - 2000, - # script_type=proto_types.PAYTOADDRESS, - # address_n=client.expand_path("44'/0'/0'/0/7"), - # # address='1PtCkQgyN6xHmXWzLmFFrDNA5vYhYLeNFZ', - # # address='1s9TSqr3PHZdXGrYws59Uaf5SPqavH43z', - # ), - # proto_types.TxOutputType( - # amount= 1000, - # script_type=proto_types.PAYTOADDRESS, - # # address_n=client.expand_path("44'/0'/0'/0/18"), - # # address='1PtCkQgyN6xHmXWzLmFFrDNA5vYhYLeNFZ', - # # address='1NcMqUvyWv1K3Zxwmx5sqfj7ZEmPCSdJFM', - # ), - ] + # outputs = [ + # proto_types.TxOutputType( + # amount=0, + # script_type=proto_types.PAYTOADDRESS, + # address='p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm' + # # op_return_data=binascii.unhexlify('2890770995194662774cd192ee383b805e9a066e6a456be037727649228fb7f6') + # # address_n=client.expand_path("44'/0'/0'/0/35"), + # # address='3PUxV6Cc4udQZQsJhArVUzvvVoKC8ohkAj', + # ), + # proto_types.TxOutputType( + # amount=0, + # script_type=proto_types.PAYTOOPRETURN, + # op_return_data=binascii.unhexlify('2890770995194662774cd192ee383b805e9a066e6a456be037727649228fb7f6') + # ), + # proto_types.TxOutputType( + # amount= 8120, + # script_type=proto_types.PAYTOADDRESS, + # address_n=client.expand_path("44'/1'/0'/1/0"), + # address='1PtCkQgyN6xHmXWzLmFFrDNA5vYhYLeNFZ', + # address='14KRxYgFc7Se8j7MDdrK5PTNv8meq4GivK', + # ), + # proto_types.TxOutputType( + # amount= 18684 - 2000, + # script_type=proto_types.PAYTOADDRESS, + # address_n=client.expand_path("44'/0'/0'/0/7"), + # # address='1PtCkQgyN6xHmXWzLmFFrDNA5vYhYLeNFZ', + # # address='1s9TSqr3PHZdXGrYws59Uaf5SPqavH43z', + # ), + # proto_types.TxOutputType( + # amount= 1000, + # script_type=proto_types.PAYTOADDRESS, + # # address_n=client.expand_path("44'/0'/0'/0/18"), + # # address='1PtCkQgyN6xHmXWzLmFFrDNA5vYhYLeNFZ', + # # address='1NcMqUvyWv1K3Zxwmx5sqfj7ZEmPCSdJFM', + # ), + # ] # (signatures, serialized_tx) = client.sign_tx('Testnet', inputs, outputs) (signatures, serialized_tx) = client.sign_tx('Bitcoin', txstore.get_inputs(), txstore.get_outputs()) diff --git a/trezorctl b/trezorctl index d089db7860..c7bb5095e7 100755 --- a/trezorctl +++ b/trezorctl @@ -30,9 +30,9 @@ import sys from trezorlib.client import TrezorClient, TrezorClientVerbose, CallException, format_protobuf from trezorlib.transport import get_transport, enumerate_devices, TransportException +from trezorlib import coins from trezorlib import messages as proto from trezorlib import protobuf -from trezorlib.coins import coins_txapi from trezorlib.ckd_public import PRIME_DERIVATION_FLAG from trezorlib import stellar @@ -88,7 +88,7 @@ def cli(ctx, path, verbose, is_json): if path is not None: click.echo("Using path: {}".format(path)) sys.exit(1) - return cls(device) + return cls(transport=device) ctx.obj = get_device @@ -211,6 +211,23 @@ def set_passphrase_source(connect, source): return connect().apply_settings(passphrase_source=source) +@cli.command(help='Set auto-lock delay (in seconds).') +@click.argument('delay', type=str) +@click.pass_obj +def set_auto_lock_delay(connect, delay): + value, unit = delay[:-1], delay[-1:] + units = { + 's': 1, + 'm': 60, + 'h': 3600, + } + if unit in units: + seconds = float(value) * units[unit] + else: + seconds = float(delay) # assume seconds if no unit is specified + return connect().apply_settings(auto_lock_delay_ms=int(seconds * 1000)) + + @cli.command(help='Set device flags.') @click.argument('flags') @click.pass_obj @@ -234,7 +251,7 @@ def set_homescreen(connect, filename): elif filename.endswith('.toif'): img = open(filename, 'rb').read() if img[:8] != b'TOIf\x90\x00\x90\x00': - raise CallException(types.Failure_DataError, 'File is not a TOIF file with size of 144x144') + raise CallException(proto.FailureType.DataError, 'File is not a TOIF file with size of 144x144') else: from PIL import Image im = Image.open(filename) @@ -471,11 +488,11 @@ def get_public_node(connect, coin, address, curve, show_display): @click.pass_obj def sign_tx(connect, coin): client = connect() - if coin in coins_txapi: - txapi = coins_txapi[coin] + if coin in coins.tx_api: + txapi = coins.tx_api[coin] else: click.echo('Coin "%s" is not recognized.' % coin, err=True) - click.echo('Supported coin types: %s' % ', '.join(coins_txapi.keys()), err=True) + click.echo('Supported coin types: %s' % ', '.join(coins.tx_api.keys()), err=True) sys.exit(1) client.set_tx_api(txapi) @@ -705,9 +722,10 @@ def ethereum_get_address(connect, address, show_display): @click.option('-i', '--nonce', type=int, help='Transaction counter - Required for offline signing') @click.option('-d', '--data', default='', help='Data as hex string, e.g. 0x12345678') @click.option('-p', '--publish', is_flag=True, help='Publish transaction via RPC') +@click.option('-x', '--tx-type', type=int, help='TX type (used only for Wanchain)') @click.argument('to') @click.pass_obj -def ethereum_sign_tx(connect, host, chain_id, address, value, gas_limit, gas_price, nonce, data, publish, to): +def ethereum_sign_tx(connect, host, chain_id, address, value, gas_limit, gas_price, nonce, data, publish, to, tx_type): from ethjsonrpc import EthJsonRpc import rlp @@ -759,7 +777,7 @@ def ethereum_sign_tx(connect, host, chain_id, address, value, gas_limit, gas_pri address_n = client.expand_path(address) address = '0x%s' % (binascii.hexlify(client.ethereum_get_address(address_n)).decode()) - if gas_price is None or gas_limit is None or nonce is None: + if gas_price is None or gas_limit is None or nonce is None or publish: host, port = host.split(':') eth = EthJsonRpc(host, int(port)) @@ -782,6 +800,7 @@ def ethereum_sign_tx(connect, host, chain_id, address, value, gas_limit, gas_pri sig = client.ethereum_sign_tx( n=address_n, + tx_type=tx_type, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, @@ -790,8 +809,12 @@ def ethereum_sign_tx(connect, host, chain_id, address, value, gas_limit, gas_pri data=data, chain_id=chain_id) - transaction = rlp.encode( - (nonce, gas_price, gas_limit, to_address, value, data) + sig) + if tx_type is None: + transaction = rlp.encode( + (nonce, gas_price, gas_limit, to_address, value, data) + sig) + else: + transaction = rlp.encode( + (tx_type, nonce, gas_price, gas_limit, to_address, value, data) + sig) tx_hex = '0x%s' % binascii.hexlify(transaction).decode() if publish: @@ -839,6 +862,79 @@ def nem_sign_tx(connect, address, file, broadcast): return payload +# +# Lisk functions +# + + +@cli.command(help='Get Lisk address for specified path.') +@click.option('-n', '--address', required=True, help="BIP-32 path, e.g. m/44'/134'/0'/0'") +@click.option('-d', '--show-display', is_flag=True) +@click.pass_obj +def lisk_get_address(connect, address, show_display): + client = connect() + address_n = client.expand_path(address) + return client.lisk_get_address(address_n, show_display) + + +@cli.command(help='Get Lisk public key for specified path.') +@click.option('-n', '--address', required=True, help="BIP-32 path, e.g. m/44'/134'/0'/0'") +@click.option('-d', '--show-display', is_flag=True) +@click.pass_obj +def lisk_get_public_key(connect, address, show_display): + client = connect() + address_n = client.expand_path(address) + res = client.lisk_get_public_key(address_n, show_display) + output = { + "public_key": binascii.hexlify(res.public_key).decode() + } + return output + + +@cli.command(help='Sign message with Lisk address.') +@click.option('-n', '--address', required=True, help="BIP-32 path, e.g. m/44'/134'/0'/0'") +@click.argument('message') +@click.pass_obj +def lisk_sign_message(connect, address, message): + client = connect() + address_n = client.expand_path(address) + res = client.lisk_sign_message(address_n, message) + output = { + 'message': message, + 'address': res.address, + 'signature': binascii.hexlify(res.signature).decode() + } + return output + + +@cli.command(help='Verify message signed with Lisk address.') +@click.argument('pubkey') +@click.argument('signature') +@click.argument('message') +@click.pass_obj +def lisk_verify_message(connect, pubkey, signature, message): + signature = bytes.fromhex(signature) + pubkey = bytes.fromhex(pubkey) + return connect().lisk_verify_message(pubkey, signature, message) + + +@cli.command(help='Sign Lisk transaction.') +@click.option('-n', '--address', required=True, help="BIP-32 path to signing key, e.g. m/44'/134'/0'/0'") +@click.option('-f', '--file', type=click.File('r'), default='-', help='Transaction in JSON format') +# @click.option('-b', '--broadcast', help='Broadcast Lisk transaction') +@click.pass_obj +def lisk_sign_tx(connect, address, file): + client = connect() + address_n = client.expand_path(address) + transaction = client.lisk_sign_tx(address_n, json.load(file)) + + payload = { + "signature": binascii.hexlify(transaction.signature).decode() + } + + return payload + + # # CoSi functions # diff --git a/trezorlib/client.py b/trezorlib/client.py index 303c4414fc..fcacf32904 100644 --- a/trezorlib/client.py +++ b/trezorlib/client.py @@ -34,8 +34,8 @@ from . import messages as proto from . import tools from . import mapping from . import nem +from .coins import slip44 from . import stellar -from .coins import coins_slip44 from .debuglink import DebugLink from .protobuf import MessageType @@ -43,11 +43,6 @@ from .protobuf import MessageType if sys.version_info.major < 3: raise Exception("Trezorlib does not support Python 2 anymore.") -# try: -# from PIL import Image -# SCREENSHOT = True -# except: -# SCREENSHOT = False SCREENSHOT = False @@ -425,6 +420,7 @@ class DebugLinkMixin(object): def call_raw(self, msg): if SCREENSHOT and self.debug: + from PIL import Image layout = self.debug.read_layout() im = Image.new("RGB", (128, 64)) pix = im.load() @@ -497,8 +493,9 @@ class ProtocolMixin(object): PRIME_DERIVATION_FLAG = 0x80000000 VENDORS = ('bitcointrezor.com', 'trezor.io') - def __init__(self, *args, **kwargs): + def __init__(self, state=None, *args, **kwargs): super(ProtocolMixin, self).__init__(*args, **kwargs) + self.state = state self.init_device() self.tx_api = None @@ -506,7 +503,10 @@ class ProtocolMixin(object): self.tx_api = tx_api def init_device(self): - self.features = expect(proto.Features)(self.call)(proto.Initialize()) + init_msg = proto.Initialize() + if self.state is not None: + init_msg.state = self.state + self.features = expect(proto.Features)(self.call)(init_msg) if str(self.features.vendor) not in self.VENDORS: raise RuntimeError("Unsupported device") @@ -531,8 +531,8 @@ class ProtocolMixin(object): n = n[1:] # coin_name/a/b/c => 44'/SLIP44_constant'/a/b/c - if n[0] in coins_slip44: - n = ["44'", "%d'" % coins_slip44[n[0]]] + n[1:] + if n[0] in slip44: + n = ["44'", "%d'" % slip44[n[0]]] + n[1:] path = [] for x in n: @@ -573,7 +573,7 @@ class ProtocolMixin(object): return self.call(proto.EthereumGetAddress(address_n=n, show_display=show_display)) @session - def ethereum_sign_tx(self, n, nonce, gas_price, gas_limit, to, value, data=None, chain_id=None): + def ethereum_sign_tx(self, n, nonce, gas_price, gas_limit, to, value, data=None, chain_id=None, tx_type=None): def int_to_big_endian(value): import rlp.utils if value == 0: @@ -600,6 +600,9 @@ class ProtocolMixin(object): if chain_id: msg.chain_id = chain_id + if tx_type is not None: + msg.tx_type = tx_type + response = self.call(msg) while response.data_length is not None: @@ -625,6 +628,78 @@ class ProtocolMixin(object): return True return False + # + # Lisk functions + # + + @field('address') + @expect(proto.LiskAddress) + def lisk_get_address(self, n, show_display=False): + n = self._convert_prime(n) + return self.call(proto.LiskGetAddress(address_n=n, show_display=show_display)) + + @expect(proto.LiskPublicKey) + def lisk_get_public_key(self, n, show_display=False): + n = self._convert_prime(n) + return self.call(proto.LiskGetPublicKey(address_n=n, show_display=show_display)) + + @expect(proto.LiskMessageSignature) + def lisk_sign_message(self, n, message): + n = self._convert_prime(n) + message = normalize_nfc(message) + return self.call(proto.LiskSignMessage(address_n=n, message=message)) + + def lisk_verify_message(self, pubkey, signature, message): + message = normalize_nfc(message) + try: + resp = self.call(proto.LiskVerifyMessage(signature=signature, public_key=pubkey, message=message)) + except CallException as e: + resp = e + return isinstance(resp, proto.Success) + + @expect(proto.LiskSignedTx) + def lisk_sign_tx(self, n, transaction): + n = self._convert_prime(n) + + def asset_to_proto(asset): + msg = proto.LiskTransactionAsset() + + if "votes" in asset: + msg.votes = asset["votes"] + if "data" in asset: + msg.data = asset["data"] + if "signature" in asset: + msg.signature = proto.LiskSignatureType() + msg.signature.public_key = binascii.unhexlify(asset["signature"]["publicKey"]) + if "delegate" in asset: + msg.delegate = proto.LiskDelegateType() + msg.delegate.username = asset["delegate"]["username"] + if "multisignature" in asset: + msg.multisignature = proto.LiskMultisignatureType() + msg.multisignature.min = asset["multisignature"]["min"] + msg.multisignature.life_time = asset["multisignature"]["lifetime"] + msg.multisignature.keys_group = asset["multisignature"]["keysgroup"] + return msg + + msg = proto.LiskTransactionCommon() + + msg.type = transaction["type"] + msg.fee = int(transaction["fee"]) # Lisk use strings for big numbers (javascript issue) + msg.amount = int(transaction["amount"]) # And we convert it back to number + msg.timestamp = transaction["timestamp"] + + if "recipientId" in transaction: + msg.recipient_id = transaction["recipientId"] + if "senderPublicKey" in transaction: + msg.sender_public_key = binascii.unhexlify(transaction["senderPublicKey"]) + if "requesterPublicKey" in transaction: + msg.requester_public_key = binascii.unhexlify(transaction["requesterPublicKey"]) + if "signature" in transaction: + msg.signature = binascii.unhexlify(transaction["signature"]) + + msg.asset = asset_to_proto(transaction["asset"]) + return self.call(proto.LiskSignTx(address_n=n, transaction=msg)) + @field('entropy') @expect(proto.Entropy) def get_entropy(self, size): @@ -644,7 +719,7 @@ class ProtocolMixin(object): @field('message') @expect(proto.Success) - def apply_settings(self, label=None, language=None, use_passphrase=None, homescreen=None, passphrase_source=None): + def apply_settings(self, label=None, language=None, use_passphrase=None, homescreen=None, passphrase_source=None, auto_lock_delay_ms=None): settings = proto.ApplySettings() if label is not None: settings.label = label @@ -656,6 +731,8 @@ class ProtocolMixin(object): settings.homescreen = homescreen if passphrase_source is not None: settings.passphrase_source = passphrase_source + if auto_lock_delay_ms is not None: + settings.auto_lock_delay_ms = auto_lock_delay_ms out = self.call(settings) self.init_device() # Reload Features @@ -801,7 +878,7 @@ class ProtocolMixin(object): @session def sign_tx(self, coin_name, inputs, outputs, version=None, lock_time=None, debug_processor=None): - start = time.time() + # start = time.time() txes = self._prepare_sign_tx(inputs, outputs) # Prepare and send initial message @@ -1151,12 +1228,15 @@ class ProtocolMixin(object): class TrezorClient(ProtocolMixin, TextUIMixin, BaseClient): - pass + def __init__(self, transport, *args, **kwargs): + super().__init__(transport=transport, *args, **kwargs) class TrezorClientVerbose(ProtocolMixin, TextUIMixin, VerboseWireMixin, BaseClient): - pass + def __init__(self, transport, *args, **kwargs): + super().__init__(transport=transport, *args, **kwargs) class TrezorClientDebugLink(ProtocolMixin, DebugLinkMixin, VerboseWireMixin, BaseClient): - pass + def __init__(self, transport, *args, **kwargs): + super().__init__(transport=transport, *args, **kwargs) diff --git a/trezorlib/coins.py b/trezorlib/coins.py index 4298e0c61b..13a7b7b298 100644 --- a/trezorlib/coins.py +++ b/trezorlib/coins.py @@ -1,31 +1,45 @@ -from .tx_api import TxApiBitcoin, TxApiTestnet, TxApiLitecoin, TxApiZcash, TxApiDash, TxApiBcash, TxApiDecredTestnet, TxApiDogecoin, TxApiMonacoin, TxApiBitcoinGold +import os.path +import json -coins_slip44 = { - 'Bitcoin': 0, - 'Testnet': 1, - 'Decred Testnet': 1, - 'Litecoin': 2, - 'Dogecoin': 3, - 'Dash': 5, - 'Namecoin': 7, - 'Monacoin': 22, - 'Decred': 42, - 'Ether': 60, - 'EtherClassic': 61, - 'Zcash': 133, - 'Bcash': 145, - 'Bitcoin Gold': 156, -} +from .tx_api import TxApiInsight, TxApiBlockCypher -coins_txapi = { - 'Bitcoin': TxApiBitcoin, - 'Testnet': TxApiTestnet, - 'Litecoin': TxApiLitecoin, - 'Dash': TxApiDash, - 'Zcash': TxApiZcash, - 'Bcash': TxApiBcash, - 'Decred Testnet': TxApiDecredTestnet, - 'Dogecoin': TxApiDogecoin, - 'Monacoin': TxApiMonacoin, - 'Bitcoin Gold': TxApiBitcoinGold, -} +COINS_JSON = os.path.join(os.path.dirname(__file__), 'coins.json') + + +def _load_coins_json(): + # Load coins.json to local variables + # NOTE: coins.json comes from 'vendor/trezor-common/coins.json', + # which is a git submodule. If you're trying to run trezorlib directly + # from the checkout (or tarball), initialize the submodule with: + # $ git submodule update --init + # and install coins.json with: + # $ python setup.py prebuild + with open(COINS_JSON) as coins_json: + coins_list = json.load(coins_json) + return {coin['coin_name']: coin for coin in coins_list} + + +def _insight_for_coin(coin): + if not coin['bitcore']: + return None + zcash = coin['coin_name'].lower().startswith('zcash') + network = 'insight_{}'.format(coin['coin_name'].lower().replace(' ', '_')) + url = coin['bitcore'][0] + 'api/' + return TxApiInsight(network=network, url=url, zcash=zcash) + + +# exported variables +__all__ = ['by_name', 'slip44', 'tx_api'] + +try: + by_name = _load_coins_json() +except Exception as e: + raise ImportError("Failed to load coins.json. Check your installation.") from e + +slip44 = {name: coin['bip44'] for name, coin in by_name.items()} +tx_api = {name: _insight_for_coin(coin) + for name, coin in by_name.items() + if coin["bitcore"]} + +# fixup for Dogecoin +tx_api['Dogecoin'] = TxApiBlockCypher(network='blockcypher_dogecoin', url='https://api.blockcypher.com/v1/doge/main/') diff --git a/trezorlib/messages/LiskAddress.py b/trezorlib/messages/LiskAddress.py new file mode 100644 index 0000000000..a565239fe2 --- /dev/null +++ b/trezorlib/messages/LiskAddress.py @@ -0,0 +1,9 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskAddress(p.MessageType): + FIELDS = { + 1: ('address', p.UnicodeType, 0), + } + MESSAGE_WIRE_TYPE = 115 diff --git a/trezorlib/messages/LiskDelegateType.py b/trezorlib/messages/LiskDelegateType.py new file mode 100644 index 0000000000..4afce0a22a --- /dev/null +++ b/trezorlib/messages/LiskDelegateType.py @@ -0,0 +1,8 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskDelegateType(p.MessageType): + FIELDS = { + 1: ('username', p.UnicodeType, 0), + } diff --git a/trezorlib/messages/LiskGetAddress.py b/trezorlib/messages/LiskGetAddress.py new file mode 100644 index 0000000000..e9d0b3fe5e --- /dev/null +++ b/trezorlib/messages/LiskGetAddress.py @@ -0,0 +1,10 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskGetAddress(p.MessageType): + FIELDS = { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('show_display', p.BoolType, 0), + } + MESSAGE_WIRE_TYPE = 114 diff --git a/trezorlib/messages/LiskGetPublicKey.py b/trezorlib/messages/LiskGetPublicKey.py new file mode 100644 index 0000000000..bb6e30f919 --- /dev/null +++ b/trezorlib/messages/LiskGetPublicKey.py @@ -0,0 +1,10 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskGetPublicKey(p.MessageType): + FIELDS = { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('show_display', p.BoolType, 0), + } + MESSAGE_WIRE_TYPE = 121 diff --git a/trezorlib/messages/LiskMessageSignature.py b/trezorlib/messages/LiskMessageSignature.py new file mode 100644 index 0000000000..46e7fc6102 --- /dev/null +++ b/trezorlib/messages/LiskMessageSignature.py @@ -0,0 +1,10 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskMessageSignature(p.MessageType): + FIELDS = { + 1: ('address', p.UnicodeType, 0), + 2: ('signature', p.BytesType, 0), + } + MESSAGE_WIRE_TYPE = 119 diff --git a/trezorlib/messages/LiskMultisignatureType.py b/trezorlib/messages/LiskMultisignatureType.py new file mode 100644 index 0000000000..223cd96ce9 --- /dev/null +++ b/trezorlib/messages/LiskMultisignatureType.py @@ -0,0 +1,10 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskMultisignatureType(p.MessageType): + FIELDS = { + 1: ('min', p.UVarintType, 0), + 2: ('life_time', p.UVarintType, 0), + 3: ('keys_group', p.UnicodeType, p.FLAG_REPEATED), + } diff --git a/trezorlib/messages/LiskPublicKey.py b/trezorlib/messages/LiskPublicKey.py new file mode 100644 index 0000000000..87cea3149d --- /dev/null +++ b/trezorlib/messages/LiskPublicKey.py @@ -0,0 +1,9 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskPublicKey(p.MessageType): + FIELDS = { + 1: ('public_key', p.BytesType, 0), + } + MESSAGE_WIRE_TYPE = 122 diff --git a/trezorlib/messages/LiskSignMessage.py b/trezorlib/messages/LiskSignMessage.py new file mode 100644 index 0000000000..9049e0ac87 --- /dev/null +++ b/trezorlib/messages/LiskSignMessage.py @@ -0,0 +1,10 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskSignMessage(p.MessageType): + FIELDS = { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('message', p.BytesType, 0), + } + MESSAGE_WIRE_TYPE = 118 diff --git a/trezorlib/messages/LiskSignTx.py b/trezorlib/messages/LiskSignTx.py new file mode 100644 index 0000000000..972268a022 --- /dev/null +++ b/trezorlib/messages/LiskSignTx.py @@ -0,0 +1,11 @@ +# Automatically generated by pb2py +from .. import protobuf as p +from .LiskTransactionCommon import LiskTransactionCommon + + +class LiskSignTx(p.MessageType): + FIELDS = { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('transaction', LiskTransactionCommon, 0), + } + MESSAGE_WIRE_TYPE = 116 diff --git a/trezorlib/messages/LiskSignatureType.py b/trezorlib/messages/LiskSignatureType.py new file mode 100644 index 0000000000..b2c8322d8e --- /dev/null +++ b/trezorlib/messages/LiskSignatureType.py @@ -0,0 +1,8 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskSignatureType(p.MessageType): + FIELDS = { + 1: ('public_key', p.BytesType, 0), + } diff --git a/trezorlib/messages/LiskSignedTx.py b/trezorlib/messages/LiskSignedTx.py new file mode 100644 index 0000000000..271349ad56 --- /dev/null +++ b/trezorlib/messages/LiskSignedTx.py @@ -0,0 +1,9 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskSignedTx(p.MessageType): + FIELDS = { + 1: ('signature', p.BytesType, 0), + } + MESSAGE_WIRE_TYPE = 117 diff --git a/trezorlib/messages/LiskTransactionAsset.py b/trezorlib/messages/LiskTransactionAsset.py new file mode 100644 index 0000000000..1b924a78e0 --- /dev/null +++ b/trezorlib/messages/LiskTransactionAsset.py @@ -0,0 +1,15 @@ +# Automatically generated by pb2py +from .. import protobuf as p +from .LiskDelegateType import LiskDelegateType +from .LiskMultisignatureType import LiskMultisignatureType +from .LiskSignatureType import LiskSignatureType + + +class LiskTransactionAsset(p.MessageType): + FIELDS = { + 1: ('signature', LiskSignatureType, 0), + 2: ('delegate', LiskDelegateType, 0), + 3: ('votes', p.UnicodeType, p.FLAG_REPEATED), + 4: ('multisignature', LiskMultisignatureType, 0), + 5: ('data', p.UnicodeType, 0), + } diff --git a/trezorlib/messages/LiskTransactionCommon.py b/trezorlib/messages/LiskTransactionCommon.py new file mode 100644 index 0000000000..159a50a535 --- /dev/null +++ b/trezorlib/messages/LiskTransactionCommon.py @@ -0,0 +1,17 @@ +# Automatically generated by pb2py +from .. import protobuf as p +from .LiskTransactionAsset import LiskTransactionAsset + + +class LiskTransactionCommon(p.MessageType): + FIELDS = { + 1: ('type', p.UVarintType, 0), + 2: ('amount', p.UVarintType, 0), # default=0 + 3: ('fee', p.UVarintType, 0), + 4: ('recipient_id', p.UnicodeType, 0), + 5: ('sender_public_key', p.BytesType, 0), + 6: ('requester_public_key', p.BytesType, 0), + 7: ('signature', p.BytesType, 0), + 8: ('timestamp', p.UVarintType, 0), + 9: ('asset', LiskTransactionAsset, 0), + } diff --git a/trezorlib/messages/LiskTransactionType.py b/trezorlib/messages/LiskTransactionType.py new file mode 100644 index 0000000000..b4e55b08db --- /dev/null +++ b/trezorlib/messages/LiskTransactionType.py @@ -0,0 +1,9 @@ +# Automatically generated by pb2py +Transfer = 0 +RegisterSecondPassphrase = 1 +RegisterDelegate = 2 +CastVotes = 3 +RegisterMultisignatureAccount = 4 +CreateDapp = 5 +TransferIntoDapp = 6 +TransferOutOfDapp = 7 diff --git a/trezorlib/messages/LiskVerifyMessage.py b/trezorlib/messages/LiskVerifyMessage.py new file mode 100644 index 0000000000..bbd1446035 --- /dev/null +++ b/trezorlib/messages/LiskVerifyMessage.py @@ -0,0 +1,11 @@ +# Automatically generated by pb2py +from .. import protobuf as p + + +class LiskVerifyMessage(p.MessageType): + FIELDS = { + 1: ('signature', p.BytesType, 0), + 2: ('public_key', p.BytesType, 0), + 3: ('message', p.BytesType, 0), + } + MESSAGE_WIRE_TYPE = 120 diff --git a/trezorlib/messages/MessageType.py b/trezorlib/messages/MessageType.py index c4a2c1ad3d..2c76d2115d 100644 --- a/trezorlib/messages/MessageType.py +++ b/trezorlib/messages/MessageType.py @@ -84,6 +84,15 @@ DebugLinkMemoryRead = 110 DebugLinkMemory = 111 DebugLinkMemoryWrite = 112 DebugLinkFlashErase = 113 +LiskGetAddress = 114 +LiskAddress = 115 +LiskSignTx = 116 +LiskSignedTx = 117 +LiskSignMessage = 118 +LiskMessageSignature = 119 +LiskVerifyMessage = 120 +LiskGetPublicKey = 121 +LiskPublicKey = 122 StellarGetPublicKey = 200 StellarPublicKey = 201 StellarSignTx = 202 diff --git a/trezorlib/messages/__init__.py b/trezorlib/messages/__init__.py index 6c69004719..663224076f 100644 --- a/trezorlib/messages/__init__.py +++ b/trezorlib/messages/__init__.py @@ -4,6 +4,11 @@ from .CoinType import CoinType from .HDNodePathType import HDNodePathType from .HDNodeType import HDNodeType from .IdentityType import IdentityType +from .LiskDelegateType import LiskDelegateType +from .LiskMultisignatureType import LiskMultisignatureType +from .LiskSignatureType import LiskSignatureType +from .LiskTransactionAsset import LiskTransactionAsset +from .LiskTransactionCommon import LiskTransactionCommon from .MultisigRedeemScriptType import MultisigRedeemScriptType from .NEMAggregateModification import NEMAggregateModification from .NEMCosignatoryModification import NEMCosignatoryModification @@ -35,6 +40,7 @@ from . import NEMMosaicLevy from . import NEMSupplyChangeType from . import NEMModificationType from . import NEMImportanceTransferMode +from . import LiskTransactionType from .Address import Address from .ApplyFlags import ApplyFlags from .ApplySettings import ApplySettings @@ -87,6 +93,15 @@ from .GetEntropy import GetEntropy from .GetFeatures import GetFeatures from .GetPublicKey import GetPublicKey from .Initialize import Initialize +from .LiskAddress import LiskAddress +from .LiskGetAddress import LiskGetAddress +from .LiskGetPublicKey import LiskGetPublicKey +from .LiskMessageSignature import LiskMessageSignature +from .LiskPublicKey import LiskPublicKey +from .LiskSignMessage import LiskSignMessage +from .LiskSignTx import LiskSignTx +from .LiskSignedTx import LiskSignedTx +from .LiskVerifyMessage import LiskVerifyMessage from .LoadDevice import LoadDevice from .MessageSignature import MessageSignature from .NEMAddress import NEMAddress diff --git a/trezorlib/nem.py b/trezorlib/nem.py index df7cb9c765..19c4f7b3a0 100644 --- a/trezorlib/nem.py +++ b/trezorlib/nem.py @@ -2,13 +2,13 @@ import binascii import json from . import messages as proto -TYPE_MOSAIC_TRANSFER = 0x0101 +TYPE_TRANSACTION_TRANSFER = 0x0101 TYPE_IMPORTANCE_TRANSFER = 0x0801 -TYPE_MULTISIG_CHANGE = 0x1001 -TYPE_MULTISIG_SIGN = 0x1002 -TYPE_MULTISIG_TX = 0x1004 +TYPE_AGGREGATE_MODIFICATION = 0x1001 +TYPE_MULTISIG_SIGNATURE = 0x1002 +TYPE_MULTISIG = 0x1004 TYPE_PROVISION_NAMESPACE = 0x2001 -TYPE_MOSAIC_DEFINITION_CREATION = 0x4001 +TYPE_MOSAIC_CREATION = 0x4001 TYPE_MOSAIC_SUPPLY_CHANGE = 0x4002 @@ -19,7 +19,7 @@ def create_transaction_common(transaction): msg.fee = transaction["fee"] msg.deadline = transaction["deadline"] - if "signed" in transaction: + if "signer" in transaction: msg.signer = binascii.unhexlify(transaction["signer"]) return msg @@ -68,6 +68,7 @@ def create_provision_namespace(transaction): msg.sink = transaction["rentalFeeSink"] msg.fee = transaction["rentalFee"] + return msg def create_mosaic_creation(transaction): @@ -113,27 +114,36 @@ def create_supply_change(transaction): return msg +def create_importance_transfer(transaction): + msg = proto.NEMImportanceTransfer() + msg.mode = transaction["importanceTransfer"]["mode"] + msg.public_key = binascii.unhexlify(transaction["importanceTransfer"]["publicKey"]) + return msg + + def create_sign_tx(transaction): msg = proto.NEMSignTx() msg.transaction = create_transaction_common(transaction) - msg.cosigning = (transaction["type"] == TYPE_MULTISIG_SIGN) + msg.cosigning = (transaction["type"] == TYPE_MULTISIG_SIGNATURE) - if transaction["type"] in (TYPE_MULTISIG_SIGN, TYPE_MULTISIG_TX): + if transaction["type"] in (TYPE_MULTISIG_SIGNATURE, TYPE_MULTISIG): transaction = transaction["otherTrans"] msg.multisig = create_transaction_common(transaction) elif "otherTrans" in transaction: raise ValueError("Transaction does not support inner transaction") - if transaction["type"] == TYPE_MOSAIC_TRANSFER: + if transaction["type"] == TYPE_TRANSACTION_TRANSFER: msg.transfer = create_transfer(transaction) - elif transaction["type"] == TYPE_MULTISIG_CHANGE: + elif transaction["type"] == TYPE_AGGREGATE_MODIFICATION: msg.aggregate_modification = create_aggregate_modification(transaction) elif transaction["type"] == TYPE_PROVISION_NAMESPACE: msg.provision_namespace = create_provision_namespace(transaction) - elif transaction["type"] == TYPE_MOSAIC_DEFINITION_CREATION: + elif transaction["type"] == TYPE_MOSAIC_CREATION: msg.mosaic_creation = create_mosaic_creation(transaction) elif transaction["type"] == TYPE_MOSAIC_SUPPLY_CHANGE: - msg.mosaic_supply_change = create_supply_change(transaction) + msg.supply_change = create_supply_change(transaction) + elif transaction["type"] == TYPE_IMPORTANCE_TRANSFER: + msg.importance_transfer = create_importance_transfer(transaction) else: raise ValueError("Unknown transaction type") diff --git a/trezorlib/tests/device_tests/common.py b/trezorlib/tests/device_tests/common.py index 0fffb7ae9f..8681d947f6 100644 --- a/trezorlib/tests/device_tests/common.py +++ b/trezorlib/tests/device_tests/common.py @@ -22,9 +22,10 @@ from binascii import hexlify, unhexlify import pytest import os +from trezorlib import coins +from trezorlib import tx_api from trezorlib.client import TrezorClient, TrezorClientDebugLink from trezorlib.transport import get_transport -from trezorlib import tx_api tests_dir = os.path.dirname(os.path.abspath(__file__)) tx_api.cache_dir = os.path.join(tests_dir, '../txcache') @@ -42,7 +43,7 @@ class TrezorTest: debuglink = wirelink.find_debug() self.client = TrezorClientDebugLink(wirelink) self.client.set_debuglink(debuglink) - self.client.set_tx_api(tx_api.TxApiBitcoin) + self.client.set_tx_api(coins.tx_api['Bitcoin']) # self.client.set_buttonwait(3) # 1 2 3 4 5 6 7 8 9 10 11 12 diff --git a/trezorlib/tests/device_tests/conftest.py b/trezorlib/tests/device_tests/conftest.py new file mode 100644 index 0000000000..6cb9d410a5 --- /dev/null +++ b/trezorlib/tests/device_tests/conftest.py @@ -0,0 +1,34 @@ +import pytest + +from . import common +from trezorlib.client import TrezorClient + + +def device_version(): + device = common.get_device() + if not device: + raise RuntimeError() + client = TrezorClient(device) + if client.features.model == "T": + return 2 + else: + return 1 + + +try: + TREZOR_VERSION = device_version() +except: + raise + TREZOR_VERSION = None + + +def pytest_runtest_setup(item): + ''' + Called for each test item (class, individual tests). + Ensures that 'skip_t2' tests are skipped on T2 + and 'skip_t1' tests are skipped on T1. + ''' + if item.get_marker("skip_t2") and TREZOR_VERSION == 2: + pytest.skip("Test excluded on Trezor T") + if item.get_marker("skip_t1") and TREZOR_VERSION == 1: + pytest.skip("Test excluded on Trezor 1") diff --git a/trezorlib/tests/device_tests/test_msg_applysettings.py b/trezorlib/tests/device_tests/test_msg_applysettings.py index cf57378fe0..07bf805d47 100644 --- a/trezorlib/tests/device_tests/test_msg_applysettings.py +++ b/trezorlib/tests/device_tests/test_msg_applysettings.py @@ -18,6 +18,7 @@ from .common import * +import time from trezorlib import messages as proto @@ -96,3 +97,60 @@ class TestMsgApplysettings(TrezorTest): proto.Success(), proto.Features()]) self.client.apply_settings(homescreen=img) + + @pytest.mark.skip_t2 + def test_apply_auto_lock_delay(self): + self.setup_mnemonic_pin_passphrase() + + with self.client: + self.client.set_expected_responses([proto.PinMatrixRequest(), + proto.ButtonRequest(), + proto.Success(), + proto.Features()]) + self.client.apply_settings(auto_lock_delay_ms=int(10e3)) # 10 secs + + time.sleep(0.1) # sleep less than auto-lock delay + with self.client: + # No PIN protection is required. + self.client.set_expected_responses([proto.Success()]) + self.client.ping(msg='', pin_protection=True) + + time.sleep(10.1) # sleep more than auto-lock delay + with self.client: + self.client.set_expected_responses([proto.PinMatrixRequest(), + proto.Success()]) + self.client.ping(msg='', pin_protection=True) + + @pytest.mark.skip_t2 + def test_apply_minimal_auto_lock_delay(self): + """ + Verify that the delay is not below the minimal auto-lock delay (10 secs) + otherwise the device may auto-lock before any user interaction. + """ + self.setup_mnemonic_pin_passphrase() + + with self.client: + self.client.set_expected_responses([proto.PinMatrixRequest(), + proto.ButtonRequest(), + proto.Success(), + proto.Features()]) + # Note: the actual delay will be 10 secs (see above). + self.client.apply_settings(auto_lock_delay_ms=int(1e3)) + + time.sleep(0.1) # sleep less than auto-lock delay + with self.client: + # No PIN protection is required. + self.client.set_expected_responses([proto.Success()]) + self.client.ping(msg='', pin_protection=True) + + time.sleep(2) # sleep less than the minimal auto-lock delay + with self.client: + # No PIN protection is required. + self.client.set_expected_responses([proto.Success()]) + self.client.ping(msg='', pin_protection=True) + + time.sleep(10.1) # sleep more than the minimal auto-lock delay + with self.client: + self.client.set_expected_responses([proto.PinMatrixRequest(), + proto.Success()]) + self.client.ping(msg='', pin_protection=True) diff --git a/trezorlib/tests/device_tests/test_msg_getaddress.py b/trezorlib/tests/device_tests/test_msg_getaddress.py index 2f9f1e3c9f..e850e0393b 100644 --- a/trezorlib/tests/device_tests/test_msg_getaddress.py +++ b/trezorlib/tests/device_tests/test_msg_getaddress.py @@ -43,12 +43,14 @@ class TestMsgGetaddress(TrezorTest): self.setup_mnemonic_nopin_nopassphrase() assert self.client.get_address('Testnet', [111, 42]) == 'moN6aN6NP1KWgnPSqzrrRPvx2x1UtZJssa' + @pytest.mark.skip_t2 def test_bch(self): self.setup_mnemonic_allallall() - assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/0'/0/0")) == '1MH9KKcvdCTY44xVDC2k3fjBbX5Cz29N1q' - assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/0'/0/1")) == '1LRspCZNFJcbuNKQkXgHMDucctFRQya5a3' - assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/0'/1/0")) == '1HADRPJpgqBzThepERpVXNi6qRgiLQRNoE' + assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/0'/0/0")) == 'bitcoincash:qr08q88p9etk89wgv05nwlrkm4l0urz4cyl36hh9sv' + assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/0'/0/1")) == 'bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4' + assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/0'/1/0")) == 'bitcoincash:qzc5q87w069lzg7g3gzx0c8dz83mn7l02scej5aluw' + @pytest.mark.skip_t2 def test_bch_multisig(self): self.setup_mnemonic_allallall() xpubs = [] @@ -62,8 +64,8 @@ class TestMsgGetaddress(TrezorTest): m=2, ) for nr in range(1, 4): - assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/" + str(nr) + "'/0/0"), show_display=(nr == 1), multisig=getmultisig(0, 0)) == '33Ju286QvonBz5N1V754ZekQv4GLJqcc5R' - assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/" + str(nr) + "'/1/0"), show_display=(nr == 1), multisig=getmultisig(1, 0)) == '3CPtPpL5mGAPdxUeUDfm2RNdWoSN9dKpXE' + assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/" + str(nr) + "'/0/0"), show_display=(nr == 1), multisig=getmultisig(0, 0)) == 'bitcoincash:pqguz4nqq64jhr5v3kvpq4dsjrkda75hwy86gq0qzw' + assert self.client.get_address('Bcash', self.client.expand_path("44'/145'/" + str(nr) + "'/1/0"), show_display=(nr == 1), multisig=getmultisig(1, 0)) == 'bitcoincash:pp6kcpkhua7789g2vyj0qfkcux3yvje7euhyhltn0a' def test_public_ckd(self): self.setup_mnemonic_nopin_nopassphrase() diff --git a/trezorlib/tests/device_tests/test_msg_lisk_getaddress.py b/trezorlib/tests/device_tests/test_msg_lisk_getaddress.py new file mode 100644 index 0000000000..5ad308fe37 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_lisk_getaddress.py @@ -0,0 +1,33 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2012-2016 Marek Palatinus +# Copyright (C) 2012-2016 Pavol Rusnak +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +import pytest + +from .common import TrezorTest + + +@pytest.mark.xfail # drop when trezor-core PR #90 is merged +@pytest.mark.skip_t1 +class TestMsgLiskGetaddress(TrezorTest): + + def test_lisk_getaddress(self): + self.setup_mnemonic_nopin_nopassphrase() + assert self.client.lisk_get_address([2147483692, 2147483782]) == '1431530009238518937L' + assert self.client.lisk_get_address([2147483692, 2147483782, 2147483648]) == '17563781916205589679L' + assert self.client.lisk_get_address([2147483692, 2147483782, 2147483648, 2147483649]) == '1874186517773691964L' + assert self.client.lisk_get_address([2147483692, 2147483782, 2147484647, 2147484647]) == '16295203558710684671L' diff --git a/trezorlib/tests/device_tests/test_msg_lisk_getpublickey.py b/trezorlib/tests/device_tests/test_msg_lisk_getpublickey.py new file mode 100644 index 0000000000..1683be4efd --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_lisk_getpublickey.py @@ -0,0 +1,31 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2016-2017 Pavol Rusnak +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from binascii import hexlify +import pytest + +from .common import TrezorTest + + +@pytest.mark.xfail # drop when trezor-core PR #90 is merged +@pytest.mark.skip_t1 +class TestMsgLiskGetPublicKey(TrezorTest): + + def test_lisk_get_public_key(self): + self.setup_mnemonic_nopin_nopassphrase() + sig = self.client.lisk_get_public_key([2147483692, 2147483782, 2147483648, 2147483648]) + assert hexlify(sig.public_key) == b'eb56d7bbb5e8ea9269405f7a8527fe126023d1db2c973cfac6f760b60ae27294' diff --git a/trezorlib/tests/device_tests/test_msg_lisk_signmessage.py b/trezorlib/tests/device_tests/test_msg_lisk_signmessage.py new file mode 100644 index 0000000000..022898eb9d --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_lisk_signmessage.py @@ -0,0 +1,39 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2016-2017 Pavol Rusnak +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from binascii import hexlify +import pytest + +from .common import TrezorTest + + +@pytest.mark.xfail # drop when trezor-core PR #90 is merged +@pytest.mark.skip_t1 +class TestMsgLiskSignmessage(TrezorTest): + + def test_sign(self): + self.setup_mnemonic_nopin_nopassphrase() + sig = self.client.lisk_sign_message([2147483692, 2147483782, 2147483648, 2147483648], 'This is an example of a signed message.') + assert sig.address == '7623396847864198749L' + assert hexlify(sig.signature) == b'af1d384cce25354b5af129662caed6f3514c6f1f6a206662d301fd56aa5549aa23c3f82009f213a7a4d9297015c2e5b06584273df7c42d78b4e531fe4d4fc80e' + + def test_sign_long(self): + self.setup_mnemonic_nopin_nopassphrase() + sig = self.client.lisk_sign_message([2147483692, 2147483782, 2147483648], 'VeryLongMessage!' * 64) + assert sig.address == '17563781916205589679L' + print(hexlify(sig.signature)) + assert hexlify(sig.signature) == b'a675152c2af34e85dbd75740681efb7d67bf910561d6c9d1e075be2f99d9bc544d62c52f6619756b0e329a2f2d82756ced53b4261a028fcee0d37d7e641ef404' diff --git a/trezorlib/tests/device_tests/test_msg_lisk_signtx.py b/trezorlib/tests/device_tests/test_msg_lisk_signtx.py new file mode 100644 index 0000000000..a1d36385c1 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_lisk_signtx.py @@ -0,0 +1,176 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2012-2016 Marek Palatinus +# Copyright (C) 2012-2016 Pavol Rusnak +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from binascii import unhexlify +import pytest + +from .common import TrezorTest +from trezorlib import messages as proto + +PUBLIC_KEY = unhexlify('eb56d7bbb5e8ea9269405f7a8527fe126023d1db2c973cfac6f760b60ae27294') + + +@pytest.mark.xfail # drop when trezor-core PR #90 is merged +@pytest.mark.skip_t1 +class TestMsgLiskSignTx(TrezorTest): + + def test_lisk_sign_tx_send(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.LiskSignedTx( + signature=unhexlify('b62717d581e5713bca60b758b661e6cfa091addc6caedd57534e06cda805943ee80797b9fb9a1e1b2bd584e292d2a7f832a4d1b3f15f00e1ee1b72de7e195a08') + ) + ]) + + self.client.lisk_sign_tx(self.client.expand_path("m/44'/134'/0'/0'"), { + "amount": "10000000", + "recipientId": "9971262264659915921L", + "timestamp": 57525937, + "type": 0, + "fee": "10000000", + "asset": {} + }) + + def test_lisk_sign_tx_send_with_data(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.LiskSignedTx( + signature=unhexlify('5dd0dbb87ee46f3e985b1ef2df85cb0bec481e8601d150388f73e198cdd57a698eab076c7cd5b281fbb6a83dd3dc64d91a6eccd1614dffd46f101194ffa3a004') + ) + ]) + + self.client.lisk_sign_tx(self.client.expand_path("m/44'/134'/0'/0'"), { + "amount": "10000000", + "recipientId": "9971262264659915921L", + "timestamp": 57525937, + "type": 0, + "fee": "20000000", + "asset": { + "data": "Test data" + } + }) + + def test_lisk_sign_tx_second_signature(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.PublicKey), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.LiskSignedTx( + signature=unhexlify('f02bdc40a7599c21d29db4080ff1ff8934f76eedf5b0c4fa695c8a64af2f0b40a5c4f92db203863eebbbfad8f0611a23f451ed8bb711490234cdfb034728fd01') + ) + ]) + + self.client.lisk_sign_tx(self.client.expand_path("m/44'/134'/0'/0'"), { + "amount": "0", + "timestamp": 57525937, + "type": 1, + "fee": "500000000", + "asset": { + "signature": { + "publicKey": "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09" + } + } + }) + + def test_lisk_sign_tx_delegate_registration(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.LiskSignedTx( + signature=unhexlify('5ac02b2882b9d7d0f944e48baadc27de1296cc08c3533f7c8e380fbbb9fb4a6ac81b5dc57060d7d8c68912eea24eb6e39024801bccc0d55020e2052b0c2bb701') + ) + ]) + + self.client.lisk_sign_tx(self.client.expand_path("m/44'/134'/0'/0'"), { + "amount": "0", + "timestamp": 57525937, + "type": 2, + "fee": "2500000000", + "asset": { + "delegate": { + "username": "trezor_t" + } + } + }) + + def test_lisk_sign_tx_cast_votes(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.LiskSignedTx( + signature=unhexlify('1d0599a8387edaa4a6d309b8a78accd1ceaff20ff9d87136b01cba0efbcb9781c13dc2b0bab5a1ea4f196d8dcc9dbdbd2d56dbffcc088fc77686b2e2c2fe560f') + ) + ]) + + self.client.lisk_sign_tx(self.client.expand_path("m/44'/134'/0'/0'"), { + "amount": "0", + "timestamp": 57525937, + "type": 3, + "fee": "100000000", + "asset": { + "votes": [ + "+b002f58531c074c7190714523eec08c48db8c7cfc0c943097db1a2e82ed87f84", + "-ec111c8ad482445cfe83d811a7edd1f1d2765079c99d7d958cca1354740b7614" + ] + } + }) + + def test_lisk_sign_tx_multisignature(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.LiskSignedTx( + signature=unhexlify('88923866c2d500a6927715699ab41a0f58ea4b52e552d90e923bc24ac9da240f2328c93f9ce043a1da4937d4b61c7f57c02fc931f9824d06b24731e7be23c506') + ) + ]) + + self.client.lisk_sign_tx(self.client.expand_path("m/44'/134'/0'/0'"), { + "amount": "0", + "timestamp": 57525937, + "type": 4, + "fee": "1500000000", + "asset": { + "multisignature": { + "min": 2, + "lifetime": 5, + "keysgroup": [ + "+5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", + "+922fbfdd596fa78269bbcadc67ec2a1cc15fc929a19c462169568d7a3df1a1aa" + ] + } + } + }) diff --git a/trezorlib/tests/device_tests/test_msg_lisk_verifymessage.py b/trezorlib/tests/device_tests/test_msg_lisk_verifymessage.py new file mode 100644 index 0000000000..46efbb7777 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_lisk_verifymessage.py @@ -0,0 +1,55 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2016-2017 Pavol Rusnak +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from binascii import unhexlify +import pytest + +from .common import TrezorTest +from trezorlib import messages as proto + + +@pytest.mark.xfail # drop when trezor-core PR #90 is merged +@pytest.mark.skip_t1 +class TestMsgLiskVerifymessage(TrezorTest): + + def test_verify(self): + self.setup_mnemonic_nopin_nopassphrase() + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.Other), + proto.ButtonRequest(code=proto.ButtonRequestType.Other), + proto.Success(message='Message verified') + ]) + self.client.lisk_verify_message( + unhexlify('eb56d7bbb5e8ea9269405f7a8527fe126023d1db2c973cfac6f760b60ae27294'), + unhexlify('af1d384cce25354b5af129662caed6f3514c6f1f6a206662d301fd56aa5549aa23c3f82009f213a7a4d9297015c2e5b06584273df7c42d78b4e531fe4d4fc80e'), + 'This is an example of a signed message.' + ) + + def test_verify_long(self): + self.setup_mnemonic_nopin_nopassphrase() + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto.ButtonRequestType.Other), + proto.ButtonRequest(code=proto.ButtonRequestType.Other), + proto.Success(message='Message verified') + ]) + self.client.lisk_verify_message( + unhexlify('eb56d7bbb5e8ea9269405f7a8527fe126023d1db2c973cfac6f760b60ae27294'), + unhexlify('7b4b481f6a07a874bdd1b590cd2b933c8b571c721484d9dc303f81b22d1f3c5f55ffe0704dbfd543ff9ea3e795facda871ddb422522257d33a8fe16ab4169601'), + 'VeryLongMessage!' * 64 + ) diff --git a/trezorlib/tests/device_tests/test_msg_nem_getaddress.py b/trezorlib/tests/device_tests/test_msg_nem_getaddress.py index 4c64ff8342..305be56a05 100644 --- a/trezorlib/tests/device_tests/test_msg_nem_getaddress.py +++ b/trezorlib/tests/device_tests/test_msg_nem_getaddress.py @@ -18,7 +18,7 @@ from .common import * -@pytest.mark.skip_t2 +@pytest.mark.xfail # to be removed when nem is merged class TestMsgNEMGetaddress(TrezorTest): def test_nem_getaddress(self): diff --git a/trezorlib/tests/device_tests/test_msg_nem_signtx.py b/trezorlib/tests/device_tests/test_msg_nem_signtx.py deleted file mode 100644 index cec01124d0..0000000000 --- a/trezorlib/tests/device_tests/test_msg_nem_signtx.py +++ /dev/null @@ -1,139 +0,0 @@ -# This file is part of the TREZOR project. -# -# Copyright (C) 2017 Saleem Rashid -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# 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 GNU Lesser General Public License -# along with this library. If not, see . - -from .common import * - -from trezorlib import messages as proto - - -# assertion data from T1 -@pytest.mark.skip_t2 -class TestMsgNEMSigntx(TrezorTest): - - def test_nem_signtx_simple(self): - # tx hash: 209368053ac61969b6838ceb7e31badeb622ed6aa42d6c58365c42ad1a11e19d - signature = unhexlify( - "9cda2045324d05c791a4fc312ecceb62954e7740482f8df8928560d63cf273dea595023640179f112de755c79717757ef76962175378d6d87360ddb3f3e5f70f" - ) - - self.setup_mnemonic_nopin_nopassphrase() - - with self.client: - self.client.set_expected_responses([ - # Confirm transfer and network fee - proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), - # Unencrypted message - proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), - # Confirm recipient - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.NEMSignedTx(signature=signature), - ]) - - tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { - "timeStamp": 74649215, - "amount": 2000000, - "fee": 2000000, - "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", - "type": 257, - "deadline": 74735615, - "message": { - "payload": hexlify(b"test_nem_transaction_transfer"), - "type": 1, - }, - "version": (0x98 << 24), - }) - - assert hexlify(tx.data) == b'01010000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a80841e000000000025000000010000001d000000746573745f6e656d5f7472616e73616374696f6e5f7472616e73666572' - assert tx.signature == signature - - def test_nem_signtx_encrypted_payload(self): - self.setup_mnemonic_nopin_nopassphrase() - - with self.client: - self.client.set_expected_responses([ - # Confirm transfer and network fee - proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), - # Ask for encryption - proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), - # Confirm recipient - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.NEMSignedTx(), - ]) - - tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { - "timeStamp": 74649215, - "amount": 2000000, - "fee": 2000000, - "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", - "type": 257, - "deadline": 74735615, - "message": { - # plain text is 32B long => cipher text is 48B - # as per PKCS#7 another block containing padding is added - "payload": hexlify(b"this message should be encrypted"), - "publicKey": "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", - "type": 2, - }, - "version": (0x98 << 24), - }) - - assert hexlify(tx.data[:124]) == b'01010000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a80841e0000000000680000000200000060000000' - # after 124th byte comes iv (16B) salt (32B) and encrypted payload (48B) - assert len(tx.data[124:]) == 16 + 32 + 48 - # because IV and salt are random (therefore the encrypted payload as well) those data can't be asserted - assert len(tx.signature) == 64 - - def test_nem_signtx_xem_as_mosaic(self): - # tx hash: 9f8741194576a090bc71a3f43a03855950f94278fa121e99203e45967e19a7d0 - signature = unhexlify( - "1bca7b1b9ffb16d2c2adffa665be072bd2d7a0eafe4a9911dc473500c272905edf3d626274deb52aa490137a276d1fca67ee487079ebf9c09f9faa414f8e7c02" - ) - - self.setup_mnemonic_nopin_nopassphrase() - - with self.client: - self.client.set_expected_responses([ - # Confirm transfer and network fee - proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), - # Confirm recipient - proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), - proto.NEMSignedTx(signature=signature), - ]) - - tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { - "timeStamp": 76809215, - "amount": 1000000, - "fee": 1000000, - "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", - "type": 257, - "deadline": 76895615, - "version": (0x98 << 24), - "message": { - }, - "mosaics": [ - { - "mosaicId": { - "namespaceId": "nem", - "name": "xem", - }, - "quantity": 1000000, - }, - ], - }) - - assert hexlify(tx.data) == b'0101000002000098ff03940420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208440420f00000000007f5595042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a40420f000000000000000000010000001a0000000e000000030000006e656d0300000078656d40420f0000000000' - assert tx.signature == signature diff --git a/trezorlib/tests/device_tests/test_msg_nem_signtx_mosaics.py b/trezorlib/tests/device_tests/test_msg_nem_signtx_mosaics.py new file mode 100644 index 0000000000..e9f8836b95 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_nem_signtx_mosaics.py @@ -0,0 +1,171 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2017 Saleem Rashid +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from .common import * +from trezorlib import nem + + +# assertion data from T1 +@pytest.mark.skip_t2 +class TestMsgNEMSignTxMosaics(TrezorTest): + + def test_nem_signtx_mosaic_supply_change(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_SUPPLY_CHANGE, + "deadline": 74735615, + "message": { + }, + "mosaicId": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "supplyType": 1, + "delta": 1, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + }) + + assert hexlify(tx.data) == b'02400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74041a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963010000000100000000000000' + assert hexlify(tx.signature) == b'928b03c4a69fff35ecf0912066ea705895b3028fad141197d7ea2b56f1eef2a2516455e6f35d318f6fa39e2bb40492ac4ae603260790f7ebc7ea69feb4ca4c0a' + + def test_nem_signtx_mosaic_creation(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_CREATION, + "deadline": 74735615, + "message": { + }, + "mosaicDefinition": { + "id": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "levy": {}, + "properties": {}, + "description": "lorem" + }, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + }) + + assert hexlify(tx.data) == b'01400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f7404c100000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963050000006c6f72656d04000000150000000c00000064697669736962696c6974790100000030160000000d000000696e697469616c537570706c7901000000301a0000000d000000737570706c794d757461626c650500000066616c7365190000000c0000007472616e7366657261626c650500000066616c7365000000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000' + assert hexlify(tx.signature) == b'537adf4fd9bd5b46e204b2db0a435257a951ed26008305e0aa9e1201dafa4c306d7601a8dbacabf36b5137724386124958d53202015ab31fb3d0849dfed2df0e' + + def test_nem_signtx_mosaic_creation_properties(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_CREATION, + "deadline": 74735615, + "message": { + }, + "mosaicDefinition": { + "id": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "levy": {}, + "properties": [ + { + "name": "divisibility", + "value": "4" + }, + { + "name": "initialSupply", + "value": "200" + }, + { + "name": "supplyMutable", + "value": "false" + }, + { + "name": "transferable", + "value": "true" + } + ], + "description": "lorem" + }, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + }) + + assert hexlify(tx.data) == b'01400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f7404c200000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963050000006c6f72656d04000000150000000c00000064697669736962696c6974790100000034180000000d000000696e697469616c537570706c79030000003230301a0000000d000000737570706c794d757461626c650500000066616c7365180000000c0000007472616e7366657261626c650400000074727565000000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000' + assert hexlify(tx.signature) == b'f17c859710060f2ea9a0ab740ef427431cf36bdc7d263570ca282bd66032e9f5737a921be9839429732e663be2bb74ccc16f34f5157ff2ef00a65796b54e800e' + + def test_nem_signtx_mosaic_creation_levy(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_CREATION, + "deadline": 74735615, + "message": { + }, + "mosaicDefinition": { + "id": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "properties": [ + { + "name": "divisibility", + "value": "4" + }, + { + "name": "initialSupply", + "value": "200" + }, + { + "name": "supplyMutable", + "value": "false" + }, + { + "name": "transferable", + "value": "true" + } + ], + "levy": { + "type": 1, + "fee": 2, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "mosaicId": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + }, + "description": "lorem" + }, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + }) + + assert hexlify(tx.data) == b'01400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74041801000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963050000006c6f72656d04000000150000000c00000064697669736962696c6974790100000034180000000d000000696e697469616c537570706c79030000003230301a0000000d000000737570706c794d757461626c650500000066616c7365180000000c0000007472616e7366657261626c65040000007472756556000000010000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a1a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f7361696302000000000000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000' + assert hexlify(tx.signature) == b'b87aac1ddf146d35e6a7f3451f57e2fe504ac559031e010a51261257c37bd50fcfa7b2939dd7a3203b54c4807d458475182f5d3dc135ec0d1d4a9cd42159fd0a' diff --git a/trezorlib/tests/device_tests/test_msg_nem_signtx_mosaics_t2.py b/trezorlib/tests/device_tests/test_msg_nem_signtx_mosaics_t2.py new file mode 100644 index 0000000000..9619533985 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_nem_signtx_mosaics_t2.py @@ -0,0 +1,215 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2017 Saleem Rashid +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from .common import * + +from trezorlib import messages as proto +from trezorlib import nem +import time + + +# assertion data from T1 +@pytest.mark.skip_t1 +@pytest.mark.xfail # to be removed when nem is merged +class TestMsgNEMSignTxMosaics(TrezorTest): + + def test_nem_signtx_mosaic_supply_change(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_SUPPLY_CHANGE, + "deadline": 74735615, + "message": { + }, + "mosaicId": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "supplyType": 1, + "delta": 1, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + }) + + assert hexlify(tx.data) == b'02400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74041a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963010000000100000000000000' + assert hexlify(tx.signature) == b'928b03c4a69fff35ecf0912066ea705895b3028fad141197d7ea2b56f1eef2a2516455e6f35d318f6fa39e2bb40492ac4ae603260790f7ebc7ea69feb4ca4c0a' + + def test_nem_signtx_mosaic_creation(self): + self.setup_mnemonic_nopin_nopassphrase() + + test_suite = { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_CREATION, + "deadline": 74735615, + "message": { + }, + "mosaicDefinition": { + "id": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "levy": {}, + "properties": {}, + "description": "lorem" + }, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + } + + # not using client.nem_sign_tx() because of swiping + tx = self._nem_sign(2, test_suite) + assert hexlify(tx.data) == b'01400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f7404c100000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963050000006c6f72656d04000000150000000c00000064697669736962696c6974790100000030160000000d000000696e697469616c537570706c7901000000301a0000000d000000737570706c794d757461626c650500000066616c7365190000000c0000007472616e7366657261626c650500000066616c7365000000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000' + assert hexlify(tx.signature) == b'537adf4fd9bd5b46e204b2db0a435257a951ed26008305e0aa9e1201dafa4c306d7601a8dbacabf36b5137724386124958d53202015ab31fb3d0849dfed2df0e' + + def test_nem_signtx_mosaic_creation_properties(self): + self.setup_mnemonic_nopin_nopassphrase() + + test_suite = { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_CREATION, + "deadline": 74735615, + "message": { + }, + "mosaicDefinition": { + "id": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "levy": {}, + "properties": [ + { + "name": "divisibility", + "value": "4" + }, + { + "name": "initialSupply", + "value": "200" + }, + { + "name": "supplyMutable", + "value": "false" + }, + { + "name": "transferable", + "value": "true" + } + ], + "description": "lorem" + }, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + } + + # not using client.nem_sign_tx() because of swiping + tx = self._nem_sign(2, test_suite) + assert hexlify(tx.data) == b'01400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f7404c200000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963050000006c6f72656d04000000150000000c00000064697669736962696c6974790100000034180000000d000000696e697469616c537570706c79030000003230301a0000000d000000737570706c794d757461626c650500000066616c7365180000000c0000007472616e7366657261626c650400000074727565000000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000' + assert hexlify(tx.signature) == b'f17c859710060f2ea9a0ab740ef427431cf36bdc7d263570ca282bd66032e9f5737a921be9839429732e663be2bb74ccc16f34f5157ff2ef00a65796b54e800e' + + def test_nem_signtx_mosaic_creation_levy(self): + self.setup_mnemonic_nopin_nopassphrase() + + test_suite = { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_MOSAIC_CREATION, + "deadline": 74735615, + "message": { + }, + "mosaicDefinition": { + "id": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "properties": [ + { + "name": "divisibility", + "value": "4" + }, + { + "name": "initialSupply", + "value": "200" + }, + { + "name": "supplyMutable", + "value": "false" + }, + { + "name": "transferable", + "value": "true" + } + ], + "levy": { + "type": 1, + "fee": 2, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "mosaicId": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + }, + "description": "lorem" + }, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + } + + tx = self._nem_sign(6, test_suite) + assert hexlify(tx.data) == b'01400000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74041801000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f73616963050000006c6f72656d04000000150000000c00000064697669736962696c6974790100000034180000000d000000696e697469616c537570706c79030000003230301a0000000d000000737570706c794d757461626c650500000066616c7365180000000c0000007472616e7366657261626c65040000007472756556000000010000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a1a0000000600000068656c6c6f6d0c00000048656c6c6f206d6f7361696302000000000000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000' + assert hexlify(tx.signature) == b'b87aac1ddf146d35e6a7f3451f57e2fe504ac559031e010a51261257c37bd50fcfa7b2939dd7a3203b54c4807d458475182f5d3dc135ec0d1d4a9cd42159fd0a' + + def _nem_sign(self, num_of_swipes, test_suite): + n = self.client.expand_path("m/44'/1'/0'/0'/0'") + n = self.client._convert_prime(n) + msg = nem.create_sign_tx(test_suite) + assert msg.transaction is not None + msg.transaction.address_n = n + + # Sending NEMSignTx message + self.client.transport.write(msg) + ret = self.client.transport.read() + + # Confirm action + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + self.client.transport.write(proto.ButtonAck()) + time.sleep(1) + for i in range(num_of_swipes): + self.client.debug.swipe_down() + time.sleep(1) + self.client.debug.press_yes() + ret = self.client.transport.read() + + # Confirm action + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + self.client.transport.write(proto.ButtonAck()) + ret = self.client.transport.read() + + # Confirm tx + assert isinstance(ret, proto.ButtonRequest) + self.client.debug.press_yes() + self.client.transport.write(proto.ButtonAck()) + return self.client.transport.read() diff --git a/trezorlib/tests/device_tests/test_msg_nem_signtx_multisig.py b/trezorlib/tests/device_tests/test_msg_nem_signtx_multisig.py new file mode 100644 index 0000000000..b41f34ad2b --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_nem_signtx_multisig.py @@ -0,0 +1,159 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2017 Saleem Rashid +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from .common import * + +from trezorlib import nem + + +# assertion data from T1 +@pytest.mark.xfail # to be removed when nem is merged +class TestMsgNEMSignTxMultisig(TrezorTest): + + def test_nem_signtx_aggregate_modification(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_AGGREGATE_MODIFICATION, + "deadline": 74735615, + "message": { + }, + "modifications": [ + { + "modificationType": 1, # Add + "cosignatoryAccount": "c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844" + }, + ], + "minCosignatories": { + "relativeChange": 3 + }, + "version": (0x98 << 24), + }) + assert hexlify(tx.data) == b'01100000020000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f740401000000280000000100000020000000c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f878440400000003000000' + assert hexlify(tx.signature) == b'1200e552d8732ce3eae96719731194abfc5a09d98f61bb35684f4eeaeff15b1bdf326ee7b1bbbe89d3f68c8e07ad3daf72e4c7f031094ad2236b97918ad98601' + + def test_nem_signtx_multisig(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 1, + "fee": 10000, + "type": nem.TYPE_MULTISIG, + "deadline": 74735615, + "otherTrans": { # simple transaction transfer + "timeStamp": 2, + "amount": 2000000, + "fee": 15000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 67890, + "message": { + "payload": hexlify(b"test_nem_transaction_transfer"), + "type": 1, + }, + "version": (0x98 << 24), + "signer": 'c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844', + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'04100000010000980100000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620841027000000000000ff5f74049900000001010000010000980200000020000000c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844983a000000000000320901002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a80841e000000000025000000010000001d000000746573745f6e656d5f7472616e73616374696f6e5f7472616e73666572' + assert hexlify(tx.signature) == b'0cab2fddf2f02b5d7201675b9a71869292fe25ed33a366c7d2cbea7676fed491faaa03310079b7e17884b6ba2e3ea21c4f728d1cca8f190b8288207f6514820a' + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 150, + "type": nem.TYPE_MULTISIG, + "deadline": 789, + "otherTrans": { + "timeStamp": 123456, + "fee": 2000, + "type": nem.TYPE_PROVISION_NAMESPACE, + "deadline": 100, + "message": { + }, + "newPart": "ABCDE", + "rentalFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "rentalFee": 1500, + "parent": None, + "version": (0x98 << 24), + "signer": 'c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844', + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'04100000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b40620849600000000000000150300007d000000012000000100009840e2010020000000c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844d007000000000000640000002800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000050000004142434445ffffffff' + assert hexlify(tx.signature) == b'c915ca3332380925f4050301cdc62269cf29437ac5955321b18da34e570c7fdbb1aec2940a2a553a2a5c90950a4db3c8d3ef899c1a108582e0657f66fbbb0b04' + + def test_nem_signtx_multisig_signer(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 333, + "fee": 200, + "type": nem.TYPE_MULTISIG_SIGNATURE, + "deadline": 444, + "otherTrans": { # simple transaction transfer + "timeStamp": 555, + "amount": 2000000, + "fee": 2000000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 666, + "message": { + "payload": hexlify(b"test_nem_transaction_transfer"), + "type": 1, + }, + "version": (0x98 << 24), + "signer": 'c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844', + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'02100000010000984d01000020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b4062084c800000000000000bc010000240000002000000087923cd4805f3babe6b5af9cbb2b08be4458e39531618aed73c911f160c8e38528000000544444324354364c514c49595135364b49584933454e544d36454b3344343450354b5a50464d4b32' + assert hexlify(tx.signature) == b'286358a16ae545bff798feab93a713440c7c2f236d52ac0e995669d17a1915b0903667c97fa04418eccb42333cba95b19bccc8ac1faa8224dcfaeb41890ae807' + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 900000, + "fee": 200000, + "type": nem.TYPE_MULTISIG_SIGNATURE, + "deadline": 100, + "otherTrans": { # simple transaction transfer + "timeStamp": 101111, + "fee": 1000, + "type": nem.TYPE_MOSAIC_SUPPLY_CHANGE, + "deadline": 13123, + "message": { + }, + "mosaicId": { + "namespaceId": "hellom", + "name": "Hello mosaic" + }, + "supplyType": 1, + "delta": 1, + "version": (0x98 << 24), + "creationFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "creationFee": 1500, + "signer": 'c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844', + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'0210000001000098a0bb0d0020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b4062084400d030000000000640000002400000020000000c51395626a89a71c1ed785fb5974307a049b3b9e2165d56ed0302fe6b4f02a0128000000544444324354364c514c49595135364b49584933454e544d36454b3344343450354b5a50464d4b32' + assert hexlify(tx.signature) == b'32b1fdf788c4a90c01eedf5972b7709745831d620c13e1e97b0de6481837e162ee551573f2409822754ae940731909ec4b79cf836487e898df476adb10467506' diff --git a/trezorlib/tests/device_tests/test_msg_nem_signtx_others.py b/trezorlib/tests/device_tests/test_msg_nem_signtx_others.py new file mode 100644 index 0000000000..50812c1fb0 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_nem_signtx_others.py @@ -0,0 +1,67 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2017 Saleem Rashid +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from .common import * + +from trezorlib import nem + + +# assertion data from T1 +@pytest.mark.xfail # to be removed when nem is merged +class TestMsgNEMSignTxOther(TrezorTest): + + def test_nem_signtx_importance_transfer(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 12349215, + "fee": 9900, + "type": nem.TYPE_IMPORTANCE_TRANSFER, + "deadline": 99, + "message": { + }, + "importanceTransfer": { + "mode": 1, # activate + "publicKey": "c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844", + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'01080000010000981f6fbc0020000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b4062084ac26000000000000630000000100000020000000c5f54ba980fcbb657dbaaa42700539b207873e134d2375efeab5f1ab52f87844' + assert hexlify(tx.signature) == b'b6d9434ec5df80e65e6e45d7f0f3c579b4adfe8567c42d981b06e8ac368b1aad2b24eebecd5efd41f4497051fca8ea8a5e77636a79afc46ee1a8e0fe9e3ba90b' + + def test_nem_signtx_provision_namespace(self): + + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "fee": 2000000, + "type": nem.TYPE_PROVISION_NAMESPACE, + "deadline": 74735615, + "message": { + }, + "newPart": "ABCDE", + "rentalFeeSink": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "rentalFee": 1500, + "parent": None, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'01200000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324adc05000000000000050000004142434445ffffffff' + assert hexlify(tx.signature) == b'f047ae7987cd3a60c0d5ad123aba211185cb6266a7469dfb0491a0df6b5cd9c92b2e2b9f396cc2a3146ee185ba02df4f9e7fb238fe479917b3d274d97336640d' diff --git a/trezorlib/tests/device_tests/test_msg_nem_signtx_transfers.py b/trezorlib/tests/device_tests/test_msg_nem_signtx_transfers.py new file mode 100644 index 0000000000..0a710fbea4 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_nem_signtx_transfers.py @@ -0,0 +1,240 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2017 Saleem Rashid +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from .common import * + +from trezorlib import messages as proto +from trezorlib import nem + + +# assertion data from T1 +@pytest.mark.xfail # to be removed when nem is merged +class TestMsgNEMSignTx(TrezorTest): + + def test_nem_signtx_simple(self): + # tx hash: 209368053ac61969b6838ceb7e31badeb622ed6aa42d6c58365c42ad1a11e19d + signature = unhexlify( + "9cda2045324d05c791a4fc312ecceb62954e7740482f8df8928560d63cf273dea595023640179f112de755c79717757ef76962175378d6d87360ddb3f3e5f70f" + ) + + self.setup_mnemonic_nopin_nopassphrase() + with self.client: + self.client.set_expected_responses([ + # Confirm transfer and network fee + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + # Unencrypted message + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + # Confirm recipient + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.NEMSignedTx(), + ]) + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "amount": 2000000, + "fee": 2000000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 74735615, + "message": { + "payload": hexlify(b"test_nem_transaction_transfer"), + "type": 1, + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data) == b'01010000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a80841e000000000025000000010000001d000000746573745f6e656d5f7472616e73616374696f6e5f7472616e73666572' + assert tx.signature == signature + + def test_nem_signtx_encrypted_payload(self): + self.setup_mnemonic_nopin_nopassphrase() + + with self.client: + self.client.set_expected_responses([ + # Confirm transfer and network fee + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + # Ask for encryption + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + # Confirm recipient + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.NEMSignedTx(), + ]) + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 74649215, + "amount": 2000000, + "fee": 2000000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 74735615, + "message": { + # plain text is 32B long => cipher text is 48B + # as per PKCS#7 another block containing padding is added + "payload": hexlify(b"this message should be encrypted"), + "publicKey": "5a5e14c633d7d269302849d739d80344ff14db51d7bcda86045723f05c4e4541", + "type": 2, + }, + "version": (0x98 << 24), + }) + + assert hexlify(tx.data[:124]) == b'01010000010000987f0e730420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208480841e0000000000ff5f74042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a80841e0000000000680000000200000060000000' + # after 124th byte comes iv (16B) salt (32B) and encrypted payload (48B) + assert len(tx.data[124:]) == 16 + 32 + 48 + # because IV and salt are random (therefore the encrypted payload as well) those data can't be asserted + assert len(tx.signature) == 64 + + def test_nem_signtx_xem_as_mosaic(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 76809215, + "amount": 5000000, + "fee": 1000000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 76895615, + "version": (0x98 << 24), + "message": { + }, + "mosaics": [ + { + "mosaicId": { + "namespaceId": "nem", + "name": "xem", + }, + "quantity": 9000000, + }, + ], + }) + + # trezor should display 45 XEM (multiplied by amount) + assert hexlify(tx.data) == b'0101000002000098ff03940420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208440420f00000000007f5595042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a404b4c000000000000000000010000001a0000000e000000030000006e656d0300000078656d4054890000000000' + assert hexlify(tx.signature) == b'7b25a84b65adb489ea55739f1ca2d83a0ae069c3c58d0ea075fc30bfe8f649519199ad2324ca229c6c3214191469f95326e99712124592cae7cd3a092c93ac0c' + + def test_nem_signtx_unknown_mosaic(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 76809215, + "amount": 2000000, + "fee": 1000000, + "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 76895615, + "version": (0x98 << 24), + "message": { + }, + "mosaics": [ + { + "mosaicId": { + "namespaceId": "xxx", + "name": "aa", + }, + "quantity": 3500000, + }, + ], + }) + + # trezor should display warning about unknown mosaic and then dialog for 7000000 raw units of xxx.aa and 0 XEM + assert hexlify(tx.data) == b'0101000002000098ff03940420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208440420f00000000007f5595042800000054414c49434532474d4133344358484437584c4a513533364e4d35554e4b5148544f524e4e54324a80841e00000000000000000001000000190000000d00000003000000787878020000006161e067350000000000' + assert hexlify(tx.signature) == b'2f0280420eceb41ef9e5d94fa44ddda9cdc70b8f423ae18af577f6d85df64bb4aaf40cf24fc6eef47c63b0963611f8682348cecdc49a9b64eafcbe7afcb49102' + + def test_nem_signtx_known_mosaic(self): + + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 76809215, + "amount": 3000000, + "fee": 1000000, + "recipient": "NDMYSLXI4L3FYUQWO4MJOVL6BSTJJXKDSZRMT4LT", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 76895615, + "version": (0x68 << 24), + "message": { + }, + "mosaics": [ + { + "mosaicId": { + "namespaceId": "dim", + "name": "token", + }, + "quantity": 111000, + }, + ], + }) + + # trezor should display 0 XEM and 0.333 DIMTOK + assert hexlify(tx.data) == b'0101000002000068ff03940420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208440420f00000000007f559504280000004e444d59534c5849344c3346595551574f344d4a4f564c364253544a4a584b44535a524d54344c54c0c62d000000000000000000010000001c000000100000000300000064696d05000000746f6b656e98b1010000000000' + assert hexlify(tx.signature) == b'e7f14ef8c39727bfd257e109cd5acac31542f2e41f2e5deb258fc1db602b690eb1cabca41a627fe2adc51f3193db85c76b41c80bb60161eb8738ebf20b507104' + + def test_nem_signtx_multiple_mosaics(self): + self.setup_mnemonic_nopin_nopassphrase() + + tx = self.client.nem_sign_tx(self.client.expand_path("m/44'/1'/0'/0'/0'"), { + "timeStamp": 76809215, + "amount": 2000000, + "fee": 1000000, + "recipient": "NDMYSLXI4L3FYUQWO4MJOVL6BSTJJXKDSZRMT4LT", + "type": nem.TYPE_TRANSACTION_TRANSFER, + "deadline": 76895615, + "version": (0x68 << 24), + "message": { + }, + "mosaics": [ + { + "mosaicId": { + "namespaceId": "nem", + "name": "xem", + }, + "quantity": 3000000, + }, + { + "mosaicId": { + "namespaceId": "abc", + "name": "mosaic", + }, + "quantity": 200, + }, + { + "mosaicId": { + "namespaceId": "nem", + "name": "xem", + }, + "quantity": 30000, + }, + { + "mosaicId": { + "namespaceId": "abc", + "name": "mosaic", + }, + "quantity": 2000000, + }, + { + "mosaicId": { + "namespaceId": "breeze", + "name": "breeze-token", + }, + "quantity": 111000, + } + ] + }) + + # trezor should display warning, 6.06 XEM, 4000400 raw units of abc.mosaic (mosaics are merged) + # and 222000 BREEZE + assert hexlify(tx.data) == b'0101000002000068ff03940420000000edfd32f6e760648c032f9acb4b30d514265f6a5b5f8a7154f2618922b406208440420f00000000007f559504280000004e444d59534c5849344c3346595551574f344d4a4f564c364253544a4a584b44535a524d54344c5480841e000000000000000000030000001d0000001100000003000000616263060000006d6f7361696348851e0000000000260000001a00000006000000627265657a650c000000627265657a652d746f6b656e98b10100000000001a0000000e000000030000006e656d0300000078656df03b2e0000000000' + assert hexlify(tx.signature) == b'b2b9319fca87a05bee17108edd9a8f78aeffef74bf6b4badc6da5d46e8ff4fe82e24bf69d8e6c4097d072adf39d0c753e7580f8afb21e3288ebfb7c4d84e470d' diff --git a/trezorlib/tests/device_tests/test_msg_recoverydevice_t2.py b/trezorlib/tests/device_tests/test_msg_recoverydevice_t2.py new file mode 100644 index 0000000000..88240219a3 --- /dev/null +++ b/trezorlib/tests/device_tests/test_msg_recoverydevice_t2.py @@ -0,0 +1,105 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2012-2016 Marek Palatinus +# Copyright (C) 2012-2016 Pavol Rusnak +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 GNU Lesser General Public License +# along with this library. If not, see . + +from __future__ import print_function + +import time + +from .common import * +from trezorlib import messages as proto + + +@pytest.mark.skip_t1 +class TestMsgRecoverydeviceT2(TrezorTest): + + def test_pin_passphrase(self): + mnemonic = self.mnemonic12.split(' ') + ret = self.client.call_raw(proto.RecoveryDevice( + passphrase_protection=True, + pin_protection=True, + label='label', + enforce_wordlist=True)) + + # Enter word count + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.MnemonicWordCount) + self.client.debug.input(str(len(mnemonic))) + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter mnemonic words + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.MnemonicInput) + self.client.transport.write(proto.ButtonAck()) + for word in mnemonic: + time.sleep(1) + self.client.debug.input(word) + ret = self.client.transport.read() + + # Enter PIN for first time + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.Other) + self.client.debug.input('654') + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter PIN for second time + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.Other) + self.client.debug.input('654') + ret = self.client.call_raw(proto.ButtonAck()) + + # Workflow succesfully ended + assert ret == proto.Success(message='Device recovered') + + # Mnemonic is the same + self.client.init_device() + assert self.client.debug.read_mnemonic() == self.mnemonic12 + + assert self.client.features.pin_protection is True + assert self.client.features.passphrase_protection is True + + def test_nopin_nopassphrase(self): + mnemonic = self.mnemonic12.split(' ') + ret = self.client.call_raw(proto.RecoveryDevice( + passphrase_protection=False, + pin_protection=False, + label='label', + enforce_wordlist=True)) + + # Enter word count + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.MnemonicWordCount) + self.client.debug.input(str(len(mnemonic))) + ret = self.client.call_raw(proto.ButtonAck()) + + # Enter mnemonic words + assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.MnemonicInput) + self.client.transport.write(proto.ButtonAck()) + for word in mnemonic: + time.sleep(1) + self.client.debug.input(word) + ret = self.client.transport.read() + + # Workflow succesfully ended + assert ret == proto.Success(message='Device recovered') + + # Mnemonic is the same + self.client.init_device() + assert self.client.debug.read_mnemonic() == self.mnemonic12 + + assert self.client.features.pin_protection is False + assert self.client.features.passphrase_protection is False + + def test_already_initialized(self): + self.setup_mnemonic_nopin_nopassphrase() + with pytest.raises(Exception): + self.client.recovery_device(12, False, False, 'label', 'english') diff --git a/trezorlib/tests/device_tests/test_msg_signtx.py b/trezorlib/tests/device_tests/test_msg_signtx.py index 1237bb3d50..7b92325aa6 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx.py +++ b/trezorlib/tests/device_tests/test_msg_signtx.py @@ -18,9 +18,11 @@ from .common import * +from trezorlib import coins from trezorlib import messages as proto from trezorlib.client import CallException -from trezorlib.tx_api import TxApiTestnet + +TxApiTestnet = coins.tx_api['Testnet'] TXHASH_157041 = unhexlify('1570416eb4302cf52979afd5e6909e37d8fdd874301f7cc87e547e509cb1caa6') diff --git a/trezorlib/tests/device_tests/test_msg_signtx_bcash.py b/trezorlib/tests/device_tests/test_msg_signtx_bcash.py index 61a27c0dd0..f397d5b6ad 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx_bcash.py +++ b/trezorlib/tests/device_tests/test_msg_signtx_bcash.py @@ -16,12 +16,15 @@ # along with this library. If not, see . from .common import * +from trezorlib import coins from trezorlib import messages as proto -from trezorlib.tx_api import TxApiBcash from trezorlib.ckd_public import deserialize from trezorlib.client import CallException +TxApiBcash = coins.tx_api['Bcash'] + +@pytest.mark.skip_t2 class TestMsgSigntxBch(TrezorTest): def test_send_bch_change(self): @@ -29,7 +32,7 @@ class TestMsgSigntxBch(TrezorTest): self.client.set_tx_api(TxApiBcash) inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/0'/0/0"), - # 1MH9KKcvdCTY44xVDC2k3fjBbX5Cz29N1q + # bitcoincash:qr08q88p9etk89wgv05nwlrkm4l0urz4cyl36hh9sv amount=1995344, prev_hash=unhexlify('bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78'), prev_index=0, @@ -41,7 +44,7 @@ class TestMsgSigntxBch(TrezorTest): script_type=proto.OutputScriptType.PAYTOADDRESS, ) out2 = proto.TxOutputType( - address='1LRspCZNFJcbuNKQkXgHMDucctFRQya5a3', + address='bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4', amount=73452, script_type=proto.OutputScriptType.PAYTOADDRESS, ) @@ -66,7 +69,7 @@ class TestMsgSigntxBch(TrezorTest): self.client.set_tx_api(TxApiBcash) inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/0'/1/0"), - # 1HADRPJpgqBzThepERpVXNi6qRgiLQRNoE + # bitcoincash:qzc5q87w069lzg7g3gzx0c8dz83mn7l02scej5aluw amount=1896050, prev_hash=unhexlify('502e8577b237b0152843a416f8f1ab0c63321b1be7a8cad7bf5c5c216fcf062c'), prev_index=0, @@ -74,7 +77,47 @@ class TestMsgSigntxBch(TrezorTest): ) inp2 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/0'/0/1"), - # 1LRspCZNFJcbuNKQkXgHMDucctFRQya5a3 + # bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4 + amount=73452, + prev_hash=unhexlify('502e8577b237b0152843a416f8f1ab0c63321b1be7a8cad7bf5c5c216fcf062c'), + prev_index=1, + script_type=proto.InputScriptType.SPENDADDRESS, + ) + out1 = proto.TxOutputType( + address='bitcoincash:qq6wnnkrz7ykaqvxrx4hmjvayvzjzml54uyk76arx4', + amount=1934960, + script_type=proto.OutputScriptType.PAYTOADDRESS, + ) + with self.client: + self.client.set_expected_responses([ + proto.TxRequest(request_type=proto.RequestType.TXINPUT, details=proto.TxRequestDetailsType(request_index=0)), + proto.TxRequest(request_type=proto.RequestType.TXINPUT, details=proto.TxRequestDetailsType(request_index=1)), + proto.TxRequest(request_type=proto.RequestType.TXOUTPUT, details=proto.TxRequestDetailsType(request_index=0)), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + proto.TxRequest(request_type=proto.RequestType.TXINPUT, details=proto.TxRequestDetailsType(request_index=0)), + proto.TxRequest(request_type=proto.RequestType.TXINPUT, details=proto.TxRequestDetailsType(request_index=1)), + proto.TxRequest(request_type=proto.RequestType.TXOUTPUT, details=proto.TxRequestDetailsType(request_index=0)), + proto.TxRequest(request_type=proto.RequestType.TXFINISHED), + ]) + (signatures, serialized_tx) = self.client.sign_tx('Bcash', [inp1, inp2], [out1]) + + assert hexlify(serialized_tx) == b'01000000022c06cf6f215c5cbfd7caa8e71b1b32630cabf1f816a4432815b037b277852e50000000006a47304402207a2a955f1cb3dc5f03f2c82934f55654882af4e852e5159639f6349e9386ec4002205fb8419dce4e648eae8f67bc4e369adfb130a87d2ea2d668f8144213b12bb457412103174c61e9c5362507e8061e28d2c0ce3d4df4e73f3535ae0b12f37809e0f92d2dffffffff2c06cf6f215c5cbfd7caa8e71b1b32630cabf1f816a4432815b037b277852e50010000006a473044022062151cf960b71823bbe68c7ed2c2a93ad1b9706a30255fddb02fcbe056d8c26102207bad1f0872bc5f0cfaf22e45c925c35d6c1466e303163b75cb7688038f1a5541412102595caf9aeb6ffdd0e82b150739a83297358b9a77564de382671056ad9e5b8c58ffffffff0170861d00000000001976a91434e9cec317896e818619ab7dc99d2305216ff4af88ac00000000' + + def test_send_bch_oldaddr(self): + self.setup_mnemonic_allallall() + self.client.set_tx_api(TxApiBcash) + inp1 = proto.TxInputType( + address_n=self.client.expand_path("44'/145'/0'/1/0"), + # bitcoincash:qzc5q87w069lzg7g3gzx0c8dz83mn7l02scej5aluw + amount=1896050, + prev_hash=unhexlify('502e8577b237b0152843a416f8f1ab0c63321b1be7a8cad7bf5c5c216fcf062c'), + prev_index=0, + script_type=proto.InputScriptType.SPENDADDRESS, + ) + inp2 = proto.TxInputType( + address_n=self.client.expand_path("44'/145'/0'/0/1"), + # bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4 amount=73452, prev_hash=unhexlify('502e8577b237b0152843a416f8f1ab0c63321b1be7a8cad7bf5c5c216fcf062c'), prev_index=1, @@ -106,7 +149,7 @@ class TestMsgSigntxBch(TrezorTest): self.client.set_tx_api(TxApiBcash) inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/0'/1/0"), - # 1HADRPJpgqBzThepERpVXNi6qRgiLQRNoE + # bitcoincash:qzc5q87w069lzg7g3gzx0c8dz83mn7l02scej5aluw amount=300, prev_hash=unhexlify('502e8577b237b0152843a416f8f1ab0c63321b1be7a8cad7bf5c5c216fcf062c'), prev_index=0, @@ -114,14 +157,14 @@ class TestMsgSigntxBch(TrezorTest): ) inp2 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/0'/0/1"), - # 1LRspCZNFJcbuNKQkXgHMDucctFRQya5a3 + # bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4 amount=70, prev_hash=unhexlify('502e8577b237b0152843a416f8f1ab0c63321b1be7a8cad7bf5c5c216fcf062c'), prev_index=1, script_type=proto.InputScriptType.SPENDADDRESS, ) out1 = proto.TxOutputType( - address='15pnEDZJo3ycPUamqP3tEDnEju1oW5fBCz', + address='bitcoincash:qq6wnnkrz7ykaqvxrx4hmjvayvzjzml54uyk76arx4', amount=200, script_type=proto.OutputScriptType.PAYTOADDRESS, ) @@ -192,7 +235,7 @@ class TestMsgSigntxBch(TrezorTest): self.client.set_tx_api(TxApiBcash) inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/1000'/0/0"), - # 1MH9KKcvdCTY44xVDC2k3fjBbX5Cz29N1q + # bitcoincash:qr08q88p9etk89wgv05nwlrkm4l0urz4cyl36hh9sv amount=1995344, prev_hash=unhexlify('bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78'), prev_index=0, @@ -204,7 +247,7 @@ class TestMsgSigntxBch(TrezorTest): script_type=proto.OutputScriptType.PAYTOADDRESS, ) out2 = proto.TxOutputType( - address='1LRspCZNFJcbuNKQkXgHMDucctFRQya5a3', + address='bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4', amount=73452, script_type=proto.OutputScriptType.PAYTOADDRESS, ) @@ -263,7 +306,7 @@ class TestMsgSigntxBch(TrezorTest): inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/1'/1/0"), multisig=getmultisig(1, 0, [b'', sig, b'']), - # 3CPtPpL5mGAPdxUeUDfm2RNdWoSN9dKpXE + # bitcoincash:pp6kcpkhua7789g2vyj0qfkcux3yvje7euhyhltn0a amount=24000, prev_hash=unhexlify('f68caf10df12d5b07a34601d88fa6856c6edcbf4d05ebef3486510ae1c293d5f'), prev_index=1, @@ -311,14 +354,14 @@ class TestMsgSigntxBch(TrezorTest): inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/3'/0/0"), multisig=getmultisig(0, 0), - # 33Ju286QvonBz5N1V754ZekQv4GLJqcc5R + # bitcoincash:pqguz4nqq64jhr5v3kvpq4dsjrkda75hwy86gq0qzw amount=48490, prev_hash=unhexlify('8b6db9b8ba24235d86b053ea2ccb484fc32b96f89c3c39f98d86f90db16076a0'), prev_index=0, script_type=proto.InputScriptType.SPENDMULTISIG, ) out1 = proto.TxOutputType( - address='113Q5hHQNQ3bc1RpPX6UNw4GAXstyeA3Dk', + address='bitcoincash:qqq8gx2j76nw4dfefumxmdwvtf2tpsjznusgsmzex9', amount=24000, script_type=proto.OutputScriptType.PAYTOADDRESS, ) @@ -347,7 +390,7 @@ class TestMsgSigntxBch(TrezorTest): inp1 = proto.TxInputType( address_n=self.client.expand_path("44'/145'/1'/0/0"), multisig=getmultisig(0, 0, [b'', b'', signatures1[0]]), - # 33Ju286QvonBz5N1V754ZekQv4GLJqcc5R + # bitcoincash:pqguz4nqq64jhr5v3kvpq4dsjrkda75hwy86gq0qzw amount=48490, prev_hash=unhexlify('8b6db9b8ba24235d86b053ea2ccb484fc32b96f89c3c39f98d86f90db16076a0'), prev_index=0, diff --git a/trezorlib/tests/device_tests/test_msg_signtx_bitcoin_gold.py b/trezorlib/tests/device_tests/test_msg_signtx_bitcoin_gold.py index 6f033b8cc0..99b5082c00 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx_bitcoin_gold.py +++ b/trezorlib/tests/device_tests/test_msg_signtx_bitcoin_gold.py @@ -17,11 +17,13 @@ # along with this library. If not, see . from .common import * +from trezorlib import coins from trezorlib import messages as proto -from trezorlib.tx_api import TxApiBitcoinGold from trezorlib.ckd_public import deserialize from trezorlib.client import CallException +TxApiBitcoinGold = coins.tx_api["Bitcoin Gold"] + # All data taken from T1 class TestMsgSigntxBitcoinGold(TrezorTest): diff --git a/trezorlib/tests/device_tests/test_msg_signtx_decred.py b/trezorlib/tests/device_tests/test_msg_signtx_decred.py index 80a183e8be..77b0040a5b 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx_decred.py +++ b/trezorlib/tests/device_tests/test_msg_signtx_decred.py @@ -17,8 +17,10 @@ from .common import * +from trezorlib import coins from trezorlib import messages as proto -from trezorlib.tx_api import TxApiDecredTestnet + +TxApiDecredTestnet = coins.tx_api['Decred Testnet'] TXHASH_e16248 = unhexlify("e16248f0b39a0a0c0e53d6f2f84c2a944f0d50e017a82701e8e02e46e979d5ed") diff --git a/trezorlib/tests/device_tests/test_msg_signtx_segwit.py b/trezorlib/tests/device_tests/test_msg_signtx_segwit.py index 5d35a07cd9..65136c3a3f 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx_segwit.py +++ b/trezorlib/tests/device_tests/test_msg_signtx_segwit.py @@ -17,11 +17,13 @@ from .common import * +from trezorlib import coins from trezorlib import messages as proto -from trezorlib.tx_api import TxApiTestnet from trezorlib.ckd_public import deserialize from trezorlib.client import CallException +TxApiTestnet = coins.tx_api["Testnet"] + class TestMsgSigntxSegwit(TrezorTest): diff --git a/trezorlib/tests/device_tests/test_msg_signtx_segwit_native.py b/trezorlib/tests/device_tests/test_msg_signtx_segwit_native.py index 4c4300aa92..f82f93ff82 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx_segwit_native.py +++ b/trezorlib/tests/device_tests/test_msg_signtx_segwit_native.py @@ -17,10 +17,12 @@ from .common import * +from trezorlib import coins from trezorlib import messages as proto -from trezorlib.tx_api import TxApiTestnet from trezorlib.ckd_public import deserialize +TxApiTestnet = coins.tx_api['Testnet'] + class TestMsgSigntxSegwitNative(TrezorTest): diff --git a/trezorlib/tests/device_tests/test_msg_signtx_zcash.py b/trezorlib/tests/device_tests/test_msg_signtx_zcash.py index e45a01a97a..e4dac926e2 100644 --- a/trezorlib/tests/device_tests/test_msg_signtx_zcash.py +++ b/trezorlib/tests/device_tests/test_msg_signtx_zcash.py @@ -18,8 +18,11 @@ from .common import * + +from trezorlib import coins from trezorlib import messages as proto -from trezorlib.tx_api import TxApiZcash + +TxApiZcash = coins.tx_api["Zcash"] TXHASH_93373e = unhexlify('93373e63cc626c4a7d049ad775d6511bb5eba985f142db660c9b9f955c722f5c') diff --git a/trezorlib/tests/device_tests/test_multisig_change.py b/trezorlib/tests/device_tests/test_multisig_change.py index ed9607622f..2d2d1c4dd2 100644 --- a/trezorlib/tests/device_tests/test_multisig_change.py +++ b/trezorlib/tests/device_tests/test_multisig_change.py @@ -19,14 +19,14 @@ from .common import * from trezorlib import messages as proto import trezorlib.ckd_public as bip32 -from trezorlib import tx_api +from trezorlib.coins import tx_api class TestMultisigChange(TrezorTest): def setup_method(self, method): super(TestMultisigChange, self).setup_method(method) - self.client.set_tx_api(tx_api.TxApiTestnet) + self.client.set_tx_api(tx_api['Testnet']) node_ext1 = bip32.deserialize('tpubDADHV9u9Y6gkggintTdMjJE3be58zKNLhpxBQyuEM6Pwx3sN9JVLmMCMN4DNVwL9AKec27z5TaWcWuHzMXiGAtcra5DjwWbvppGX4gaEGVN') # m/1 => 02c0d0c5fee952620757c6128dbf327c996cd72ed3358d15d6518a1186099bc15e diff --git a/trezorlib/tests/unit_tests/test_nem.py b/trezorlib/tests/unit_tests/test_nem.py index d3da852b73..2662677c69 100644 --- a/trezorlib/tests/unit_tests/test_nem.py +++ b/trezorlib/tests/unit_tests/test_nem.py @@ -8,7 +8,7 @@ def test_nem_basic(): "amount": 1000000, "fee": 1000000, "recipient": "TALICE2GMA34CXHD7XLJQ536NM5UNKQHTORNNT2J", - "type": nem.TYPE_MOSAIC_TRANSFER, + "type": nem.TYPE_TRANSACTION_TRANSFER, "deadline": 76895615, "version": (0x98 << 24), "message": { diff --git a/trezorlib/tests/unit_tests/test_tx_api.py b/trezorlib/tests/unit_tests/test_tx_api.py index 888874dd0d..6111b15d83 100644 --- a/trezorlib/tests/unit_tests/test_tx_api.py +++ b/trezorlib/tests/unit_tests/test_tx_api.py @@ -18,8 +18,11 @@ import os +from trezorlib import coins from trezorlib import tx_api -from trezorlib.tx_api import TxApiBitcoin, TxApiTestnet + +TxApiBitcoin = coins.tx_api['Bitcoin'] +TxApiTestnet = coins.tx_api['Testnet'] tests_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/trezorlib/transport/__init__.py b/trezorlib/transport/__init__.py index b720c2e3d4..e6a4bca13e 100644 --- a/trezorlib/transport/__init__.py +++ b/trezorlib/transport/__init__.py @@ -115,4 +115,4 @@ def get_transport(path=None, prefix_search=False): if transports: return transports[0].find_by_path(path, prefix_search=prefix_search) - raise Exception("Unknown path prefix '%s'" % prefix) + raise Exception("Could not find device by path: {}".format(path)) diff --git a/trezorlib/transport/hid.py b/trezorlib/transport/hid.py index 3a6884ca2b..9a47354ab8 100644 --- a/trezorlib/transport/hid.py +++ b/trezorlib/transport/hid.py @@ -19,6 +19,7 @@ import time import hid import os +import sys from ..protocol_v1 import ProtocolV1 from ..protocol_v2 import ProtocolV2 @@ -39,7 +40,12 @@ class HidHandle: def open(self): if self.count == 0: self.handle = hid.device() - self.handle.open_path(self.path) + try: + self.handle.open_path(self.path) + except (IOError, OSError) as e: + if sys.platform.startswith('linux'): + e.args = e.args + ('Do you have udev rules installed? https://github.com/trezor/trezor-common/blob/master/udev/51-trezor.rules', ) + raise e self.handle.set_nonblocking(True) self.count += 1 diff --git a/trezorlib/transport/udp.py b/trezorlib/transport/udp.py index 7bbad169c1..ffcfb250f6 100644 --- a/trezorlib/transport/udp.py +++ b/trezorlib/transport/udp.py @@ -67,7 +67,6 @@ class UdpTransport(Transport): @classmethod def enumerate(cls): - devices = [] default_path = '{}:{}'.format(cls.DEFAULT_HOST, cls.DEFAULT_PORT) try: return [cls._try_path(default_path)] diff --git a/trezorlib/transport/webusb.py b/trezorlib/transport/webusb.py index 44b5dafb53..b9782f748d 100644 --- a/trezorlib/transport/webusb.py +++ b/trezorlib/transport/webusb.py @@ -20,6 +20,7 @@ import time import os import atexit import usb1 +import sys from ..protocol_v1 import ProtocolV1 from ..protocol_v2 import ProtocolV2 @@ -46,7 +47,11 @@ class WebUsbHandle: if self.count == 0: self.handle = self.device.open() if self.handle is None: - raise Exception('Cannot open device') + if sys.platform.startswith('linux'): + args = ('Do you have udev rules installed? https://github.com/trezor/trezor-common/blob/master/udev/51-trezor.rules', ) + else: + args = () + raise IOError('Cannot open device', *args) self.handle.claimInterface(interface) self.count += 1 diff --git a/trezorlib/tx_api.py b/trezorlib/tx_api.py index b06e9e7766..e7e8a02d06 100644 --- a/trezorlib/tx_api.py +++ b/trezorlib/tx_api.py @@ -115,40 +115,6 @@ class TxApiInsight(TxApi): return t -class TxApiSmartbit(TxApi): - - def get_tx(self, txhash): - - data = self.fetch_json('tx', txhash) - - data = data['transaction'] - - t = proto.TransactionType() - t.version = int(data['version']) - t.lock_time = data['locktime'] - - for vin in data['inputs']: - i = t.inputs.add() - if 'coinbase' in vin.keys(): - i.prev_hash = b"\0" * 32 - i.prev_index = 0xffffffff # signed int -1 - i.script_sig = binascii.unhexlify(vin['coinbase']) - i.sequence = vin['sequence'] - - else: - i.prev_hash = binascii.unhexlify(vin['txid']) - i.prev_index = vin['vout'] - i.script_sig = binascii.unhexlify(vin['script_sig']['hex']) - i.sequence = vin['sequence'] - - for vout in data['outputs']: - o = t.bin_outputs.add() - o.amount = int(Decimal(vout['value']) * 100000000) - o.script_pubkey = binascii.unhexlify(vout['script_pub_key']['hex']) - - return t - - class TxApiBlockCypher(TxApi): def __init__(self, network, url, zcash=None): @@ -182,16 +148,3 @@ class TxApiBlockCypher(TxApi): o.script_pubkey = binascii.unhexlify(vout['script']) return t - - -TxApiBitcoin = TxApiInsight(network='insight_bitcoin', url='https://btc-bitcore1.trezor.io/api/') -TxApiTestnet = TxApiInsight(network='insight_testnet', url='https://testnet-bitcore3.trezor.io/api/') -TxApiLitecoin = TxApiInsight(network='insight_litecoin', url='https://ltc-bitcore1.trezor.io/api/') -TxApiDash = TxApiInsight(network='insight_dash', url='https://dash-bitcore1.trezor.io/api/') -TxApiZcash = TxApiInsight(network='insight_zcash', url='https://zec-bitcore1.trezor.io/api/', zcash=True) -TxApiBcash = TxApiInsight(network='insight_bcash', url='https://bch-bitcore2.trezor.io/api/') -TxApiBitcoinGold = TxApiInsight(network='insight_bitcoin_gold', url='https://btg-bitcore2.trezor.io/api/') -TxApiDecredTestnet = TxApiInsight(network='insight_decred_testnet', url='https://testnet.decred.org/api/') -TxApiDogecoin = TxApiBlockCypher(network='blockcypher_dogecoin', url='https://api.blockcypher.com/v1/doge/main/') -TxApiSegnet = TxApiSmartbit(network='smartbit_segnet', url='https://segnet-api.smartbit.com.au/v1/blockchain/') -TxApiMonacoin = TxApiInsight(network='insight_monacoin', url='https://mona.insight.monaco-ex.org/insight-api-monacoin/') diff --git a/vendor/trezor-common b/vendor/trezor-common new file mode 160000 index 0000000000..9abe3a7c69 --- /dev/null +++ b/vendor/trezor-common @@ -0,0 +1 @@ +Subproject commit 9abe3a7c69000cc7ee3cda2ec940193fa9d62e6c