Skip to content

Commit

Permalink
port to Py3.4
Browse files Browse the repository at this point in the history
  • Loading branch information
8go committed May 26, 2017
1 parent 3378b61 commit 590dd83
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 56 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Below a sample screenshot. More screenshots [here](screenshots).
# Build and runtime requirements

* [Trezor](https://www.trezor.io) device
* [Python](https://www.python.org/) v2.7
* [Python](https://www.python.org/) v2.7 or 3.4+
* PyCrypto
* PyQt4
* [trezorlib from python-trezor](https://github.com/trezor/python-trezor)
Expand All @@ -70,6 +70,8 @@ like `pyqt4-dev-tools` or `PyQt4-devel`).
Run:

python TrezorHash.py
or
python3 TrezorHash.py

Run-time command line options are

Expand Down Expand Up @@ -142,8 +144,8 @@ Apply, Hash: Control-A, Control-S
Cancel, Quit: Esc, Control-Q
Version, About: Control-T
Requires: python 2.7 and PyQt4 and trezorlib library.
Tested on Linux on Python 2.7.
Requires: python 2.7 or 3.4+ and PyQt4 and trezorlib library.
Tested on Linux on Python 2.7 and 3.4.
BTW, for testing 'xsel -bi', 'xsel -bo' and 'xsel -bc' set, write and clear the clipboard on Linux.
```
Expand Down Expand Up @@ -184,7 +186,7 @@ single-file-executablefile.
- - -
**Question:** In which language is TrezorHash written?

**Answer:** [Python](https://www.python.org/) 2.7. It will currently not run on Python 3.
**Answer:** [Python](https://www.python.org/). It will currently run on Python 2.7 and 3.4+.
- - -
**Question:** Do I need to have a [Trezor](https://www.trezor.io/) in
order to use TrezorHash?
Expand Down Expand Up @@ -274,4 +276,12 @@ Testing has only been done on Linux.
**Answer:** Let us know.
- - -

# To-do List

[ ] There is a bug in Qt4 not allowing foreign characters to be entered
via the Alt-Gr keys from
the keyboard in the password field (used to read Trezor passphrase).
A work around could be written. Immediate work around is copy-paste.


</> on :octocat: with :heart:
10 changes: 5 additions & 5 deletions TrezorHash.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ def useTerminal(teh, settings):
for ii in range(settings.inputArgs.count('')):
settings.inputArgs.remove('') # get rid of all empty strings
if len(settings.inputArgs) == 0:
settings.input = raw_input("Please provide an input string to be hashed: "
settings.input = raw_input(u"Please provide an input string to be hashed: "
"(Carriage return to quit) ")
# convert all input as possible to unicode UTF-8
settings.input = settings.input.decode('utf-8')
if settings.input == "":
settings.mlogger.log("User decided to abandon.", logging.DEBUG,
"Trezor IO")
settings.mlogger.log(u"User decided to abandon.", logging.DEBUG,
u"Trezor IO")
sys.exit(3)
settings.inputArgs.append(settings.input)
for item in settings.inputArgs:
Expand All @@ -82,8 +82,8 @@ def main():
trezor.prefillPassphrase(u'')

if settings.TArg:
settings.mlogger.log("Terminal mode --terminal was set. Avoiding GUI.",
logging.INFO, "Arguments")
settings.mlogger.log(u"Terminal mode --terminal was set. Avoiding GUI.",
logging.INFO, u"Arguments")
dialog = None
else:
dialog = Dialog(trezor, settings)
Expand Down
2 changes: 1 addition & 1 deletion basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging

# Name of software version
THVERSION = u'v0.2.0-alpha'
THVERSION = u'v0.3.0-alpha'

# Date of software version, only used in GUI
THVERSIONTEXT = u'May 2017'
Expand Down
6 changes: 4 additions & 2 deletions dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import print_function

import logging
import sys

from PyQt4 import QtGui
from PyQt4.QtGui import QPixmap
Expand Down Expand Up @@ -77,8 +78,9 @@ def printAbout(self):
"computes the encrypted hash (digest) of an input string "
"(message) using a Trezor hardware "
"device for safety and security.<br><br>" +
"<b>Version: </b>" + basics.THVERSION +
" from " + basics.THVERSIONTEXT)
"<b>TrezorHash Version: </b>" + basics.THVERSION +
" from " + basics.THVERSIONTEXT +
"<br><br><b>Python Version: </b>" + sys.version.replace(" \n", "; "))
msgBox.setIconPixmap(QPixmap("icons/TrezorHash.92x128.png"))
msgBox.exec_()

Expand Down
38 changes: 28 additions & 10 deletions encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,27 @@

def q2s(q):
"""Convert QString to UTF-8 string object"""
return str(q.toUtf8()).decode('utf-8')
if sys.version_info[0] > 2:
return q
else:
return str(q.toUtf8()).decode('utf-8')


def s2q(s):
"""Convert UTF-8 encoded string to QString"""
return QtCore.QString.fromUtf8(s)
if sys.version_info[0] > 2:
return s
else:
return QtCore.QString.fromUtf8(s)


def u(fmt, s):
def unpack(fmt, s):
# u = lambda fmt, s: struct.unpack(fmt, s)[0]
return(struct.unpack(fmt, s)[0])


def p(fmt, s):
# u = lambda fmt, s: struct.unpack(fmt, s)[0]
def pack(fmt, s):
# p = lambda fmt, s: struct.pack(fmt, s)[0]
return(struct.pack(fmt, s))


Expand All @@ -49,11 +55,11 @@ class Magic(object):
"""

headerStr = b'TRZR'
hdr = u("!I", headerStr)
hdr = unpack("!I", headerStr)

# to encrypt hash
hashNode = [hdr, u("!I", b'HASH')]
hashKey = "Allow HASH encryption?" # string to encrypt hash
hashNode = [hdr, unpack("!I", b'HASH')]
hashKey = b"Allow HASH encryption?" # string to encrypt hash


class Padding(object):
Expand All @@ -65,8 +71,20 @@ def __init__(self, blocksize):
self.blocksize = blocksize

def pad(self, s):
"""
In Python 2 input s is a string, a char list.
Python 2 returns a string.
In Python 3 input s is bytes.
Python 3 returns bytes.
"""
BS = self.blocksize
return s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
if sys.version_info[0] > 2:
return s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
else:
return s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def unpad(self, s):
return s[0:-ord(s[-1])]
if sys.version_info[0] > 2:
return s[0:-s[-1]]
else:
return s[0:-ord(s[-1])]
3 changes: 3 additions & 0 deletions enter_pin_dialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
</item>
<item row="4" column="0" colspan="3">
<widget class="QLineEdit" name="pinEdit">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly|Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhPreferNumbers</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
Expand Down
69 changes: 43 additions & 26 deletions trezor_app_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@

from trezor_gui import TrezorPassphraseDialog, EnterPinDialog, TrezorChooserDialog

"""
This code is written specifically such that both terminal-only as well as
GUI mode are supported for all 3 operations: Trezor choser, PIN entry,
Passphrase entry.
Each of the windows can be turned on or off individually with the 3 flags:
readpinfromstdin, readpassphrasefromstdin, and readdevicestringfromstdin.
Code should work on both Python 2.7 as well as 3.4.
Requires PyQt4.
"""


class QtTrezorMixin(object):
"""
Expand All @@ -37,37 +48,36 @@ def callback_PassphraseRequest(self, msg):
if self.readpassphrasefromstdin:
# read passphrase from stdin
try:
passphrase = getpass.getpass("Please enter passphrase: ")
passphrase = getpass.getpass(u"Please enter passphrase: ")
passphrase = str(passphrase)
except KeyboardInterrupt:
sys.stderr.write("\nKeyboard interrupt: passphrase not read. Aborting.\n")
sys.stderr.write(u"\nKeyboard interrupt: passphrase not read. Aborting.\n")
sys.exit(3)
except Exception as e:
sys.stderr.write("Critical error: Passphrase not read. Aborting. (%s)" % e)
sys.stderr.write(u"Critical error: Passphrase not read. Aborting. (%s)" % e)
sys.exit(3)
else:
dialog = TrezorPassphraseDialog()
if not dialog.exec_():
sys.exit(3)
else:
passphrase = dialog.passphraseEdit.text()
passphrase = str(passphrase)
passphrase = dialog.passphrase()

return proto.PassphraseAck(passphrase=passphrase)

def callback_PinMatrixRequest(self, msg):
if self.readpinfromstdin:
# read PIN from stdin
print(" 7 8 9")
print(" 4 5 6")
print(" 1 2 3")
print(u" 7 8 9")
print(u" 4 5 6")
print(u" 1 2 3")
try:
pin = getpass.getpass("Please enter PIN: ")
pin = getpass.getpass(u"Please enter PIN: ")
except KeyboardInterrupt:
sys.stderr.write("\nKeyboard interrupt: PIN not read. Aborting.\n")
sys.stderr.write(u"\nKeyboard interrupt: PIN not read. Aborting.\n")
sys.exit(7)
except Exception as e:
sys.stderr.write("Critical error: PIN not read. Aborting. (%s)" % e)
sys.stderr.write(u"Critical error: PIN not read. Aborting. (%s)" % e)
sys.exit(7)
else:
dialog = EnterPinDialog()
Expand All @@ -82,7 +92,10 @@ def prefillPassphrase(self, passphrase):
Instead of asking for passphrase, use this one
"""
if passphrase is not None:
self.passphrase = passphrase.decode("utf-8")
if sys.version_info[0] > 2:
self.passphrase = passphrase
else:
self.passphrase = passphrase.decode("utf-8")
else:
self.passphrase = None

Expand Down Expand Up @@ -146,13 +159,13 @@ def chooseDevice(self, devices):
@returns HidTransport object of selected device
"""
if not len(devices):
raise RuntimeError("No Trezor connected!")
raise RuntimeError(u"No Trezor connected!")

if len(devices) == 1:
try:
return HidTransport(devices[0])
except IOError:
raise RuntimeError("Trezor is currently in use")
raise RuntimeError(u"Trezor is currently in use")

# maps deviceId string to device label
deviceMap = {}
Expand All @@ -169,33 +182,37 @@ def chooseDevice(self, devices):
continue

if not deviceMap:
raise RuntimeError("All connected Trezors are in use!")
raise RuntimeError(u"All connected Trezors are in use!")

if self.readdevicestringfromstdin:
print('Chose your Trezor device please. Devices currently in use are not listed:')
print(u'Chose your Trezor device please. '
'Devices currently in use are not listed:')
ii = 0
for device in deviceMap:
print('%d %s' % (ii, deviceMap[device]))
ii += 1
ii -= 1
while True:
inputstr = raw_input("Please provide the number of the device chosen: (%d-%d, Carriage return to quit) " % (0, ii))
inputstr = raw_input(u"Please provide the number of the device "
"chosen: (%d-%d, Carriage return to quit) " % (0, ii))
if inputstr == '':
raise RuntimeError("No Trezors device chosen! Quitting.")
raise RuntimeError(u"No Trezors device chosen! Quitting.")
try:
inputint = int(inputstr)
except Exception:
print('Wrong input. You must enter a number between %d and %d. Try again.' % (0, ii))
print(u'Wrong input. You must enter a number '
'between %d and %d. Try again.' % (0, ii))
continue
if inputint < 0 or inputint > ii:
print('Wrong input. You must enter a number between %d and %d. Try again.' % (0, ii))
print(u'Wrong input. You must enter a number '
'between %d and %d. Try again.' % (0, ii))
continue
break
deviceStr = deviceMap.keys()[ii]
else:
dialog = TrezorChooserDialog(deviceMap)
if not dialog.exec_():
raise RuntimeError("No Trezors device chosen! Quitting.")
raise RuntimeError(u"No Trezors device chosen! Quitting.")
deviceStr = dialog.chosenDeviceStr()

return HidTransport([deviceStr, None])
Expand All @@ -208,18 +225,18 @@ def setupTrezor(readdevicestringfromstdin=False, mlogger=None):
"""
try:
if mlogger is not None:
mlogger.log("Starting Trezor initialization", logging.DEBUG, "Trezor Info")
mlogger.log(u"Starting Trezor initialization", logging.DEBUG, u"Trezor Info")
trezorChooser = TrezorChooser(readdevicestringfromstdin)
trezor = trezorChooser.getDevice()
except (ConnectionError, RuntimeError) as e:
if mlogger is not None:
mlogger.log("Connection to Trezor failed: %s" % e.message,
logging.CRITICAL, "Trezor Error")
mlogger.log(u"Connection to Trezor failed: %s" % e.message,
logging.CRITICAL, u"Trezor Error")
sys.exit(1)

if trezor is None:
if mlogger is not None:
mlogger.log("No available Trezor found, quitting.",
logging.CRITICAL, "Trezor Error")
mlogger.log(u"No available Trezor found, quitting.",
logging.CRITICAL, u"Trezor Error")
sys.exit(1)
return trezor
22 changes: 22 additions & 0 deletions trezor_app_specific.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@
class TrezorEncryptedHash(object):
"""
class that does the Trezor hash encryption
Methods setAddress() and setIV() are usually called only at initialization.
They create an address and an AES-CBS initialization vector.
Address and IV are stored internally in the class to make the
trezorEncryptHash() call simpler and with fewer parameters.
In summary the encrypted hash is computed as follows:
The 999th public receiving BTC address of account 0 of the Trezor wallet without
passphrase is computed. From this address the MD5 hash is derived. This is a
16-byte digest which will later be used as initial vector IV in the AES
encryption.
The input is a message in unicode UTF-8.
A SHA256 hash is computed from this message.
The resulting 32-byte digest is encrypted on the Trezor with AES CBC using
the IV mentioned before. This encryption uses some static magic numbers
as keys. The encryption is also influenced by the `confirm-on-Trezor-button`
flags. This is why the output for running the program is different
depending whether a `confirm-button` press is required or not.
This encrypted byte array is also 32-bytes. It will be again hashed with
SHA256 resulting in the final 32-byte, i.e. 256-bit, digest which is the
final output. This outbut is converted into a hex string of 64 letters
in the alphabet [0-9a-f]. This 64-letter string is returned to the caller.
"""

def __init__(self, trezor, settings):
Expand Down
2 changes: 1 addition & 1 deletion trezor_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self):
self.setupUi(self)

def passphrase(self):
return self.passphraseEdit.text()
return q2s(self.passphraseEdit.text())


class EnterPinDialog(QtGui.QDialog, Ui_EnterPinDialog):
Expand Down
3 changes: 3 additions & 0 deletions trezor_passphrase_dialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
</item>
<item>
<widget class="QLineEdit" name="passphraseEdit">
<property name="inputMethodHints">
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText</set>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
Expand Down
Loading

0 comments on commit 590dd83

Please sign in to comment.