Compare commits

...

22 Commits

Author SHA1 Message Date
Chakib Benziane 74424168b2 Merge pull request #1 from sp4ke/sp4ke-patch-1
Avoid repeating keyword argument
8 years ago
Chakib Benziane fc2cc7a284 Avoid repeating keyword argument
Repeating the keyword argument breaks package build with the following error: "SyntaxError: keyword argument" repeated
8 years ago
Pavol Rusnak ea98665d83
update readme 8 years ago
Jan Pochyla 4d3e4574ef add session request to TransportV2, add @session helper 8 years ago
Pavol Rusnak 5fc6dc3155 Merge pull request #69 from jhoenicke/master
Unit Test: PIN protection timeout
8 years ago
Jochen Hoenicke ad5b693ffa Unit Test: PIN protection timeout
Don't expect any minimum timeout on the first PIN entry.
8 years ago
Jochen Hoenicke 195b7e47e1
More unit tests for ethereum signing 8 years ago
Pavol Rusnak 39f8d57b7e Merge pull request #68 from jhoenicke/master
Ethereum: Allow leading 0x in data. Handle new contracts.
8 years ago
Jochen Hoenicke d9df63ad89 Allow leading 0x in data. Handle new contracts.
Specify 0x or "" as destination to create a new contract.
Leading 0x in data is just ignored (data must always be given in hex).
8 years ago
Pavol Rusnak 29154811bd Merge pull request #67 from jhoenicke/master
Fix ethereum_sign_tx
8 years ago
Jochen Hoenicke c85600b9bd
Fix ethereum_sign_tx
- Workaround bug in rlp.utils.int_to_big_endian
- Command line tool now expects data to be a hexlified string
8 years ago
Pavol Rusnak 3be88e69ff
add unit tests for ethereum messages 8 years ago
Pavol Rusnak 78c6328b36
fix ethereum_sign_tx 8 years ago
Pavol Rusnak 6a1564ba87
fix firmware_update fetching firmware 8 years ago
Jan Pochyla e9427b04f3 fixes for latest V2 format, add checksum 8 years ago
Pavol Rusnak 8bb7f550ad Merge pull request #65 from jhoenicke/master
Fix for older python version
8 years ago
Jochen Hoenicke 90122cf29a Fix for older python version 8 years ago
Jan Pochyla a129b072d3 ignore socket.recv timeout in udp transport 8 years ago
Jan Pochyla 0f48e15c48 fix v2 transport 8 years ago
Pavol Rusnak e00453661d
remove trezorctl-emu.sh script, trezorctl now uses default pipe path if not provided 8 years ago
Pavol Rusnak 16b6289c50
unclutter root dir by moving scripts into tools subdirectory 8 years ago
Pavol Rusnak ef9eaf4553
rename ethereum_send_tx to ethereum_sign_tx, add --publish option 8 years ago

@ -11,10 +11,25 @@ Client side implementation for TREZOR-compatible Bitcoin hardware wallets.
See http://bitcointrezor.com for more information.
Install
-------
(Run with sudo if not running in superuser mode under Linux)
.. code::
pip install trezor
On Linux you might need to run these commands first:
.. code::
sudo apt-get install python-dev cython libusb-1.0-0-dev libudev-dev git
sudo pip install setuptools
Example
-------
also found in ``helloworld.py``
also found in ``tools/helloworld.py``
.. code:: python
@ -72,28 +87,3 @@ Example: your PIN is **1234** and TREZOR is displaying the following:
=== === ===
You have to enter: **3795**
Install
-------
(Run with sudo if not running in superuser mode)
.. code::
pip install trezor
How to install from source (Windows)
------------------------------------
* Install Python 2.7 (http://python.org)
* Install Cython (Windows binaries on http://cython.org/#download)
* Install Microsoft Visual Studio 2008 Express
* Add "C:\\Program Files (x86)\\Microsoft Visual Studio 9.0" to system PATH
* Clone repository (using TortoiseGit) to local directory
* Run C:\\python27\\python.exe setup.py install (or develop)
How to install from source (Debian/Ubuntu)
------------------------------------------
* sudo apt-get install python-dev python-setuptools cython libusb-1.0-0-dev libudev-dev git
* git clone https://github.com/trezor/python-trezor.git
* cd python-trezor
* python setup.py install (or develop)

@ -29,7 +29,7 @@ setup(
],
scripts = ['trezorctl'],
test_suite='tests',
install_requires=['ecdsa>=0.9', 'protobuf>=2.6.1', 'mnemonic>=0.8', 'hidapi>=0.7.99'],
install_requires=['ecdsa>=0.9', 'protobuf>=2.6.1', 'mnemonic>=0.8', 'hidapi>=0.7.99', 'setuptools>=19.0'],
include_package_data=True,
zip_safe=False,
classifiers=[

@ -5,8 +5,6 @@ sys.path = ['../',] + sys.path
from trezorlib.transport_pipe import PipeTransport
from trezorlib.transport_hid import HidTransport
from trezorlib.transport_socket import SocketTransportClient
#from trezorlib.transport_bridge import BridgeTransport
devices = HidTransport.enumerate()

@ -0,0 +1,17 @@
import unittest
import common
import trezorlib.ckd_public as bip32
import binascii
class TestMsgEthereumGetaddress(common.TrezorTest):
def test_ethereum_getaddress(self):
self.setup_mnemonic_nopin_nopassphrase()
self.assertEqual(binascii.hexlify(self.client.ethereum_get_address([])), '1d1c328764a41bda0492b66baa30c4a339ff85ef')
self.assertEqual(binascii.hexlify(self.client.ethereum_get_address([1])), '437207ca3cf43bf2e47dea0756d736c5df4f597a')
self.assertEqual(binascii.hexlify(self.client.ethereum_get_address([0, -1])), 'e5d96dfa07bcf1a3ae43677840c31394258861bf')
self.assertEqual(binascii.hexlify(self.client.ethereum_get_address([-9, 0])), 'f68804ac9eca9483ab4241d3e4751590d2c05102')
self.assertEqual(binascii.hexlify(self.client.ethereum_get_address([0, 9999999])), '7a6366ecfcaf0d5dcc1539c171696c6cdd1eb8ed')
if __name__ == '__main__':
unittest.main()

@ -0,0 +1,139 @@
import unittest
import common
import binascii
import trezorlib.messages_pb2 as proto
import trezorlib.types_pb2 as proto_types
from rlp.utils import int_to_big_endian
class TestMsgEthereumSigntx(common.TrezorTest):
def test_ethereum_signtx_nodata(self):
self.setup_mnemonic_nopin_nopassphrase()
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=[0, 0],
nonce=0,
gas_price=20,
gas_limit=20,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=10)
self.assertEqual(sig_v, 27)
self.assertEqual(binascii.hexlify(sig_r), '9b61192a161d056c66cfbbd331edb2d783a0193bd4f65f49ee965f791d898f72')
self.assertEqual(binascii.hexlify(sig_s), '49c0bbe35131592c6ed5c871ac457feeb16a1493f64237387fab9b83c1a202f7')
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=[0, 0],
nonce=123456,
gas_price=20000,
gas_limit=20000,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=12345678901234567890)
self.assertEqual(sig_v, 28)
self.assertEqual(binascii.hexlify(sig_r), '6de597b8ec1b46501e5b159676e132c1aa78a95bd5892ef23560a9867528975a')
self.assertEqual(binascii.hexlify(sig_s), '6e33c4230b1ecf96a8dbb514b4aec0a6d6ba53f8991c8143f77812aa6daa993f')
def test_ethereum_signtx_data(self):
self.setup_mnemonic_nopin_nopassphrase()
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=[0, 0],
nonce=0,
gas_price=20,
gas_limit=20,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=10,
data='abcdefghijklmnop' * 16)
self.assertEqual(sig_v, 28)
self.assertEqual(binascii.hexlify(sig_r), '6da89ed8627a491bedc9e0382f37707ac4e5102e25e7a1234cb697cedb7cd2c0')
self.assertEqual(binascii.hexlify(sig_s), '691f73b145647623e2d115b208a7c3455a6a8a83e3b4db5b9c6d9bc75825038a')
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=[0, 0],
nonce=123456,
gas_price=20000,
gas_limit=20000,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=12345678901234567890,
data='ABCDEFGHIJKLMNOP' * 256 + '!!!')
self.assertEqual(sig_v, 28)
self.assertEqual(binascii.hexlify(sig_r), '4e90b13c45c6a9bf4aaad0e5427c3e62d76692b36eb727c78d332441b7400404')
self.assertEqual(binascii.hexlify(sig_s), '3ff236e7d05f0f9b1ee3d70599bb4200638f28388a8faf6bb36db9e04dc544be')
def test_ethereum_signtx_message(self):
self.setup_mnemonic_nopin_nopassphrase()
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=[0, 0],
nonce=0,
gas_price=20000,
gas_limit=20000,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=0,
data='ABCDEFGHIJKLMNOP' * 256 + '!!!')
self.assertEqual(sig_v, 28)
self.assertEqual(binascii.hexlify(sig_r), '070e9dafda4d9e733fa7b6747a75f8a4916459560efb85e3e73cd39f31aa160d')
self.assertEqual(binascii.hexlify(sig_s), '7842db33ef15c27049ed52741db41fe3238a6fa3a6a0888fcfb74d6917600e41')
def test_ethereum_signtx_newcontract(self):
self.setup_mnemonic_nopin_nopassphrase()
# contract creation without data should fail.
self.assertRaises(Exception, self.client.ethereum_sign_tx,
n=[0, 0],
nonce=123456,
gas_price=20000,
gas_limit=20000,
to='',
value=12345678901234567890)
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=[0, 0],
nonce=0,
gas_price=20000,
gas_limit=20000,
to='',
value=12345678901234567890,
data='ABCDEFGHIJKLMNOP' * 256 + '!!!')
self.assertEqual(sig_v, 28)
self.assertEqual(binascii.hexlify(sig_r), 'b401884c10ae435a2e792303b5fc257a09f94403b2883ad8c0ac7a7282f5f1f9')
self.assertEqual(binascii.hexlify(sig_s), '4742fc9e6a5fa8db3db15c2d856914a7f3daab21603a6c1ce9e9927482f8352e')
def test_ethereum_sanity_checks(self):
# gas overflow
self.assertRaises(Exception, self.client.ethereum_sign_tx,
n=[0, 0],
nonce=123456,
gas_price=0xffffffffffffffffffffffffffffffff,
gas_limit=0xffffffffffffffffffffffffffffff,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=12345678901234567890)
# no gas price
self.assertRaises(Exception, self.client.ethereum_sign_tx,
n=[0, 0],
nonce=123456,
gas_limit=10000,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=12345678901234567890)
# no gas limit
self.assertRaises(Exception, self.client.ethereum_sign_tx,
n=[0, 0],
nonce=123456,
gas_price=10000,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=12345678901234567890)
# no nonce
self.assertRaises(Exception, self.client.ethereum_sign_tx,
n=[0, 0],
gas_price=10000,
gas_limit=123456,
to=binascii.unhexlify('1d1c328764a41bda0492b66baa30c4a339ff85ef'),
value=12345678901234567890)
if __name__ == '__main__':
unittest.main()

@ -99,7 +99,7 @@ class TestProtectCall(common.TrezorTest):
def test_backoff(attempts, start):
if attempts <= 1:
expected = 0.2
expected = 0
else:
expected = (2 ** (attempts - 2))
got = time.time() - start

@ -88,7 +88,7 @@ def main():
if not os.path.exists(passw_file):
# New encfs drive, let's generate password
sys.stderr.write('Please provide label for new drive:')
sys.stderr.write('Please provide label for new drive: ')
label = input()
sys.stderr.write('Computer asked TREZOR for new strong password.\n')

@ -78,6 +78,8 @@ def get_transport(transport_string, path, **kwargs):
if transport_string == 'pipe':
from trezorlib.transport_pipe import PipeTransport
if path == '':
path = '/tmp/pipe.trezor'
return PipeTransport(path, is_device=False, **kwargs)
if transport_string == 'bridge':
@ -113,14 +115,11 @@ class Commands(object):
address = self.client.ethereum_get_address(address_n, args.show_display)
return "0x%s" % (binascii.hexlify(address),)
def ethereum_send_tx(self, args):
def ethereum_sign_tx(self, args):
from ethjsonrpc import EthJsonRpc
from ethjsonrpc.utils import hex_to_dec
import rlp
if not args.to:
raise Exception("Please provide to address in hex format")
value = args.value
if ' ' in value:
value, unit = value.split(' ', 1)
@ -143,12 +142,15 @@ class Commands(object):
gas_price = eth.eth_gasPrice()
gas_limit = args.gas
if args.data.startswith('0x'):
args.data = args.data[2:]
data = binascii.unhexlify(args.data)
if not gas_limit:
gas_limit = hex_to_dec(eth.eth_estimateGas(
to_address=args.to,
from_address=address,
value=value,
data=args.data))
data="0x"+args.data))
nonce = eth.eth_getTransactionCount(address)
sig = self.client.ethereum_sign_tx(
@ -158,12 +160,17 @@ class Commands(object):
gas_limit=gas_limit,
to=to_address,
value=value,
data=args.data)
data=data)
transaction = rlp.encode(
(nonce, gas_price, gas_limit, hex_to_dec(args.to), value, args.data) + sig)
tx_hash = eth.eth_sendRawTransaction("0x%s" % (binascii.hexlify(transaction),))
return "Transaction sent with ID %s" % (tx_hash,)
(nonce, gas_price, gas_limit, to_address, value, data) + sig)
tx_hex = '0x%s' % binascii.hexlify(transaction)
if args.publish:
tx_hash = eth.eth_sendRawTransaction(tx_hex)
return 'Transaction published with ID: %s' % tx_hash
else:
return 'Signed raw transaction: %s' % tx_hex
def get_entropy(self, args):
return binascii.hexlify(self.client.get_entropy(args.size))
@ -289,7 +296,7 @@ class Commands(object):
r = requests.get(args.url)
fp = r.content
else:
r = requests.get('https://mytrezor.com/data/firmware/releases.json')
r = requests.get('https://wallet.trezor.io/data/firmware/releases.json')
releases = r.json()
version = lambda r: r['version']
version_string = lambda r: ".".join(map(str, version(r)))
@ -299,7 +306,7 @@ class Commands(object):
release = max(releases, key=version)
print("No file, url, or version given. Fetching latest version: %s" % version_string(release))
print("Firmware fingerprint: %s" % release['fingerprint'])
args.url = release['url']
args.url = 'https://wallet.trezor.io/' + release['url']
return self.firmware_update(args)
if fp[:8] == b'54525a52':
@ -316,7 +323,7 @@ class Commands(object):
ping.help = 'Send ping message'
get_address.help = 'Get bitcoin address in base58 encoding'
ethereum_get_address.help = 'Get Ethereum address in hex encoding'
ethereum_send_tx.help = 'Sign and publish Ethereum transaction'
ethereum_sign_tx.help = 'Sign (and optionally publish) Ethereum transaction'
get_entropy.help = 'Get example entropy'
get_features.help = 'Retrieve device features and settings'
get_public_node.help = 'Get public node of given path'
@ -349,12 +356,13 @@ class Commands(object):
(('-d', '--show-display'), {'action': 'store_true', 'default': False}),
)
ethereum_send_tx.arguments = (
ethereum_sign_tx.arguments = (
(('-a', '--host'), {'type': str, 'default': 'localhost:8545'}),
(('-n', '-address'), {'type': str}),
(('-v', '--value'), {'type': str, 'default': "0"}),
(('-g', '--gas'), {'type': int}),
(('-d', '--data'), {'type': str, 'default': ''}),
(('-p', '--publish'), {'action': 'store_true', 'default': False}),
(('to',), {'type': str}),
)

@ -1,2 +0,0 @@
#!/bin/sh
./trezorctl -t pipe -p /tmp/pipe.trezor $*

@ -88,6 +88,18 @@ class expect(object):
return ret
return wrapped_f
def session(f):
# Decorator wraps a BaseClient method
# with session activation / deactivation
def wrapped_f(*args, **kwargs):
client = args[0]
try:
client.transport.session_begin()
return f(*args, **kwargs)
finally:
client.transport.session_end()
return wrapped_f
def normalize_nfc(txt):
if sys.version_info[0] < 3:
if isinstance(txt, unicode):
@ -112,33 +124,22 @@ class BaseClient(object):
def cancel(self):
self.transport.write(proto.Cancel())
@session
def call_raw(self, msg):
try:
self.transport.session_begin()
self.transport.write(msg)
resp = self.transport.read_blocking()
finally:
self.transport.session_end()
return resp
self.transport.write(msg)
return self.transport.read_blocking()
@session
def call(self, msg):
try:
self.transport.session_begin()
resp = self.call_raw(msg)
handler_name = "callback_%s" % resp.__class__.__name__
handler = getattr(self, handler_name, None)
resp = self.call_raw(msg)
handler_name = "callback_%s" % resp.__class__.__name__
handler = getattr(self, handler_name, None)
if handler != None:
msg = handler(resp)
if msg == None:
raise Exception("Callback %s must return protobuf message, not None" % handler)
resp = self.call(msg)
finally:
self.transport.session_end()
if handler != None:
msg = handler(resp)
if msg == None:
raise Exception("Callback %s must return protobuf message, not None" % handler)
resp = self.call(msg)
return resp
@ -423,33 +424,40 @@ class ProtocolMixin(object):
n = self._convert_prime(n)
return self.call(proto.EthereumGetAddress(address_n=n, show_display=show_display))
def ethereum_sign_tx(self, n, nonce, gas_price, gas_limit, to, value, data=''):
from rlp.utils import int_to_big_endian
@session
def ethereum_sign_tx(self, n, nonce, gas_price, gas_limit, to, value, data=None):
def int_to_big_endian(value):
import rlp.utils
if value == 0:
return b''
return rlp.utils.int_to_big_endian(value)
n = self._convert_prime(n)
try:
self.transport.session_begin()
response = self.call(proto.EthereumSignTx(
address_n=n,
nonce=int_to_big_endian(nonce),
gas_price=int_to_big_endian(gas_price),
gas_limit=int_to_big_endian(gas_limit),
to=to,
value=int_to_big_endian(value)))
if data:
data, chunk = data[1024:], data[:1024]
response.data_initial_chunk = chunk
response.data_length = len(data)
while response.HasField('data_length'):
data, chunk = data[1024:], data[:1024]
response = self.call(proto.EthereumTxAck(data_chunk=chunk))
return response.signature_v, response.signature_r, response.signature_s
finally:
self.transport.session_end()
msg = proto.EthereumSignTx(
address_n=n,
nonce=int_to_big_endian(nonce),
gas_price=int_to_big_endian(gas_price),
gas_limit=int_to_big_endian(gas_limit),
value=int_to_big_endian(value))
if to:
msg.to = to
if data:
msg.data_length = len(data)
data, chunk = data[1024:], data[:1024]
msg.data_initial_chunk = chunk
response = self.call(msg)
while response.HasField('data_length'):
data_length = response.data_length
data, chunk = data[data_length:], data[:data_length]
response = self.call(proto.EthereumTxAck(data_chunk=chunk))
return response.signature_v, response.signature_r, response.signature_s
@field('entropy')
@expect(proto.Entropy)
@ -623,88 +631,83 @@ class ProtocolMixin(object):
return txes
@session
def sign_tx(self, coin_name, inputs, outputs, debug_processor=None):
start = time.time()
txes = self._prepare_sign_tx(coin_name, inputs, outputs)
try:
self.transport.session_begin()
# Prepare and send initial message
tx = proto.SignTx()
tx.inputs_count = len(inputs)
tx.outputs_count = len(outputs)
tx.coin_name = coin_name
res = self.call(tx)
# Prepare structure for signatures
signatures = [None] * len(inputs)
serialized_tx = b''
counter = 0
while True:
counter += 1
if isinstance(res, proto.Failure):
raise CallException("Signing failed")
if not isinstance(res, proto.TxRequest):
raise CallException("Unexpected message")
# If there's some part of signed transaction, let's add it
if res.HasField('serialized') and res.serialized.HasField('serialized_tx'):
log("RECEIVED PART OF SERIALIZED TX (%d BYTES)" % len(res.serialized.serialized_tx))
serialized_tx += res.serialized.serialized_tx
if res.HasField('serialized') and res.serialized.HasField('signature_index'):
if signatures[res.serialized.signature_index] != None:
raise Exception("Signature for index %d already filled" % res.serialized.signature_index)
signatures[res.serialized.signature_index] = res.serialized.signature
if res.request_type == types.TXFINISHED:
# Device didn't ask for more information, finish workflow
break
# Device asked for one more information, let's process it.
current_tx = txes[res.details.tx_hash]
if res.request_type == types.TXMETA:
msg = types.TransactionType()
msg.version = current_tx.version
msg.lock_time = current_tx.lock_time
msg.inputs_cnt = len(current_tx.inputs)
if res.details.tx_hash:
msg.outputs_cnt = len(current_tx.bin_outputs)
else:
msg.outputs_cnt = len(current_tx.outputs)
res = self.call(proto.TxAck(tx=msg))
continue
elif res.request_type == types.TXINPUT:
msg = types.TransactionType()
msg.inputs.extend([current_tx.inputs[res.details.request_index], ])
res = self.call(proto.TxAck(tx=msg))
continue
elif res.request_type == types.TXOUTPUT:
msg = types.TransactionType()
if res.details.tx_hash:
msg.bin_outputs.extend([current_tx.bin_outputs[res.details.request_index], ])
else:
msg.outputs.extend([current_tx.outputs[res.details.request_index], ])
if debug_processor != None:
# If debug_processor function is provided,
# pass thru it the request and prepared response.
# This is useful for unit tests, see test_msg_signtx
msg = debug_processor(res, msg)
res = self.call(proto.TxAck(tx=msg))
continue
# Prepare and send initial message
tx = proto.SignTx()
tx.inputs_count = len(inputs)
tx.outputs_count = len(outputs)
tx.coin_name = coin_name
res = self.call(tx)
# Prepare structure for signatures
signatures = [None] * len(inputs)
serialized_tx = b''
counter = 0
while True:
counter += 1
if isinstance(res, proto.Failure):
raise CallException("Signing failed")
if not isinstance(res, proto.TxRequest):
raise CallException("Unexpected message")
# If there's some part of signed transaction, let's add it
if res.HasField('serialized') and res.serialized.HasField('serialized_tx'):
log("RECEIVED PART OF SERIALIZED TX (%d BYTES)" % len(res.serialized.serialized_tx))
serialized_tx += res.serialized.serialized_tx
if res.HasField('serialized') and res.serialized.HasField('signature_index'):
if signatures[res.serialized.signature_index] != None:
raise Exception("Signature for index %d already filled" % res.serialized.signature_index)
signatures[res.serialized.signature_index] = res.serialized.signature
if res.request_type == types.TXFINISHED:
# Device didn't ask for more information, finish workflow
break
# Device asked for one more information, let's process it.
current_tx = txes[res.details.tx_hash]
if res.request_type == types.TXMETA:
msg = types.TransactionType()
msg.version = current_tx.version
msg.lock_time = current_tx.lock_time
msg.inputs_cnt = len(current_tx.inputs)
if res.details.tx_hash:
msg.outputs_cnt = len(current_tx.bin_outputs)
else:
msg.outputs_cnt = len(current_tx.outputs)
res = self.call(proto.TxAck(tx=msg))
continue
finally:
self.transport.session_end()
elif res.request_type == types.TXINPUT:
msg = types.TransactionType()
msg.inputs.extend([current_tx.inputs[res.details.request_index], ])
res = self.call(proto.TxAck(tx=msg))
continue
elif res.request_type == types.TXOUTPUT:
msg = types.TransactionType()
if res.details.tx_hash:
msg.bin_outputs.extend([current_tx.bin_outputs[res.details.request_index], ])
else:
msg.outputs.extend([current_tx.outputs[res.details.request_index], ])
if debug_processor != None:
# If debug_processor function is provided,
# pass thru it the request and prepared response.
# This is useful for unit tests, see test_msg_signtx
msg = debug_processor(res, msg)
res = self.call(proto.TxAck(tx=msg))
continue
if None in signatures:
raise Exception("Some signatures are missing!")
@ -742,6 +745,7 @@ class ProtocolMixin(object):
@field('message')
@expect(proto.Success)
@session
def reset_device(self, display_random, strength, passphrase_protection, pin_protection, label, language):
if self.features.initialized:
raise Exception("Device is initialized already. Call wipe_device() and try again.")
@ -832,6 +836,7 @@ class ProtocolMixin(object):
self.init_device()
return resp
@session
def firmware_update(self, fp):
if self.features.bootloader_mode == False:
raise Exception("Device must be in bootloader mode")

@ -71,9 +71,10 @@ class Transport(object):
def _parse_message(self, data):
(session_id, msg_type, data) = data
# Raise exception if we get the response with
# unexpected session ID
self._check_session_id(session_id)
# Raise exception if we get the response with unexpected session ID
if session_id != self.session_id:
raise Exception("Session ID mismatch. Have %d, got %d" %
(self.session_id, session_id))
if msg_type == 'protobuf':
return data
@ -82,14 +83,6 @@ class Transport(object):
inst.ParseFromString(bytes(data))
return inst
def _check_session_id(self, session_id):
if self.session_id == 0:
# Let the device set the session ID
self.session_id = session_id
elif session_id != self.session_id:
# Session ID has been already set, but it differs from response
raise Exception("Session ID mismatch. Have %d, got %d" % (self.session_id, session_id))
# Functions to be implemented in specific transports:
def _open(self):
raise NotImplementedException("Not implemented")
@ -145,7 +138,7 @@ class TransportV1(Transport):
try:
headerlen = struct.calcsize(">HL")
(msg_type, datalen) = struct.unpack(">HL", chunk[3:3 + headerlen])
(msg_type, datalen) = struct.unpack(">HL", bytes(chunk[3:3 + headerlen]))
except:
raise Exception("Cannot parse header")
@ -162,21 +155,27 @@ class TransportV2(Transport):
def write(self, msg):
data = bytearray(msg.SerializeToString())
# Convert to unsigned in python2
checksum = 0 # binascii.crc32(data) & 0xffffffff
header1 = struct.pack(">L", self.session_id)
header2 = struct.pack(">LL", mapping.get_type(msg), len(data))
footer = struct.pack(">L", checksum)
data = header2 + data
data = header2 + data + footer
first = True
while len(data):
if first:
# Magic characters, header1, header2, data padded to 64 bytes
datalen = 62 - len(header1)
chunk = b'?!' + header1 + data[:datalen] + b'\0' * (datalen - len(data[:datalen]))
# Magic character, header1, header2, data padded to 64 bytes
datalen = 63 - len(header1)
chunk = b'!' + header1 + \
data[:datalen] + b'\0' * (datalen - len(data[:datalen]))
else:
# Magic characters, header1, data padded to 64 bytes
# Magic character, header1, data padded to 64 bytes
datalen = 63 - len(header1)
chunk = b'?' + header1 + data[:datalen] + b'\0' * (datalen - len(data[:datalen]))
chunk = b'#' + header1 + \
data[:datalen] + b'\0' * (datalen - len(data[:datalen]))
self._write_chunk(chunk)
data = data[datalen:]
@ -185,46 +184,74 @@ class TransportV2(Transport):
def _read(self):
chunk = self._read_chunk()
(session_id, msg_type, datalen, data) = self.parse_first(chunk)
payloadlen = datalen + 4 # For the checksum
while len(data) < datalen:
while len(data) < payloadlen:
chunk = self._read_chunk()
(session_id2, data) = self.parse_next(chunk)
(next_session_id, next_data) = self.parse_next(chunk)
if session_id != session_id2:
if next_session_id != session_id:
raise Exception("Session id mismatch")
data.extend(data)
data.extend(next_data)
data = data[:payloadlen] # Strip padding zeros
footer = data[-4:]
data = data[:-4]
# csum = struct.unpack('>L', footer)
# assert csum == binascii.crc32(data), "Checksum mismatch"
# Strip padding zeros
data = data[:datalen]
return (session_id, msg_type, data)
def parse_first(self, chunk):
if chunk[:2] != b"?!":
raise Exception("Unexpected magic characters")
if chunk[0:1] != b"!":
raise Exception("Unexpected magic character")
try:
headerlen = struct.calcsize(">LLL")
(session_id, msg_type, datalen) = struct.unpack(">LLL", chunk[2:2 + headerlen])
(session_id, msg_type, datalen) = struct.unpack(">LLL", bytes(chunk[1:1 + headerlen]))
except:
raise Exception("Cannot parse header")
data = chunk[2 + headerlen:]
data = chunk[1 + headerlen:]
return (session_id, msg_type, datalen, data)
def parse_next(self, chunk):
if chunk[0:1] != b"?":
if chunk[0:1] != b"#":
raise Exception("Unexpected magic characters")
try:
headerlen = struct.calcsize(">L")
session_id = struct.unpack(">L", chunk[1:1 + headerlen])
(session_id,) = struct.unpack(">L", bytes(chunk[1:1 + headerlen]))
except:
raise Exception("Cannot parse header")
data = chunk[1 + headerlen:]
return (session_id, data)
def parse_session(self, chunk):
if chunk[0:1] != b"!":
raise Exception("Unexpected magic character")
try:
headerlen = struct.calcsize(">LL")
(null_session_id, new_session_id) = struct.unpack(
">LL", bytes(chunk[1:1 + headerlen]))
except:
raise Exception("Cannot parse header")
if null_session_id != 0:
raise Exception("Session response needs to use session ID 0")
return new_session_id
def _session_begin(self):
self._write_chunk(b'!' + b'\0' * 63)
self.session_id = self.parse_session(self._read_chunk())
def _session_end(self):
pass
'''
def read_headers(self, read_f):
c = read_f.read(2)

@ -2,7 +2,6 @@
import socket
from select import select
import time
from .transport import TransportV2, ConnectionError
class UdpTransport(TransportV2):
@ -40,7 +39,12 @@ class UdpTransport(TransportV2):
self.socket.sendall(chunk)
def _read_chunk(self):
data = self.socket.recv(64)
while True:
try:
data = self.socket.recv(64)
break
except socket.timeout:
continue
if len(data) != 64:
raise Exception("Unexpected chunk size: %d" % len(data))

Loading…
Cancel
Save