From 590dd833c35431bdb70dcd7bb219caca940d8ba9 Mon Sep 17 00:00:00 2001
From: 8go <8go@localhost.localdomain>
Date: Fri, 26 May 2017 22:31:58 +0200
Subject: [PATCH] port to Py3.4
---
README.md | 18 +++++++---
TrezorHash.py | 10 +++---
basics.py | 2 +-
dialogs.py | 6 ++--
encoding.py | 38 ++++++++++++++------
enter_pin_dialog.ui | 3 ++
trezor_app_generic.py | 69 +++++++++++++++++++++++--------------
trezor_app_specific.py | 22 ++++++++++++
trezor_gui.py | 2 +-
trezor_passphrase_dialog.ui | 3 ++
utils.py | 23 +++++++++----
11 files changed, 140 insertions(+), 56 deletions(-)
diff --git a/README.md b/README.md
index 3ccb0e8..e94af51 100644
--- a/README.md
+++ b/README.md
@@ -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)
@@ -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
@@ -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.
```
@@ -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?
@@ -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:
diff --git a/TrezorHash.py b/TrezorHash.py
index ed458b3..04d9387 100755
--- a/TrezorHash.py
+++ b/TrezorHash.py
@@ -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:
@@ -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)
diff --git a/basics.py b/basics.py
index 486573a..6c0139d 100644
--- a/basics.py
+++ b/basics.py
@@ -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'
diff --git a/dialogs.py b/dialogs.py
index 85782ca..46416f0 100644
--- a/dialogs.py
+++ b/dialogs.py
@@ -3,6 +3,7 @@
from __future__ import print_function
import logging
+import sys
from PyQt4 import QtGui
from PyQt4.QtGui import QPixmap
@@ -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.
" +
- "Version: " + basics.THVERSION +
- " from " + basics.THVERSIONTEXT)
+ "TrezorHash Version: " + basics.THVERSION +
+ " from " + basics.THVERSIONTEXT +
+ "
Python Version: " + sys.version.replace(" \n", "; "))
msgBox.setIconPixmap(QPixmap("icons/TrezorHash.92x128.png"))
msgBox.exec_()
diff --git a/encoding.py b/encoding.py
index f477249..956cc24 100644
--- a/encoding.py
+++ b/encoding.py
@@ -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))
@@ -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):
@@ -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])]
diff --git a/enter_pin_dialog.ui b/enter_pin_dialog.ui
index ab95da5..b53a684 100644
--- a/enter_pin_dialog.ui
+++ b/enter_pin_dialog.ui
@@ -87,6 +87,9 @@
-
+
+ Qt::ImhDigitsOnly|Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhPreferNumbers
+
QLineEdit::Password
diff --git a/trezor_app_generic.py b/trezor_app_generic.py
index 6620205..0079ce3 100644
--- a/trezor_app_generic.py
+++ b/trezor_app_generic.py
@@ -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):
"""
@@ -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()
@@ -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
@@ -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 = {}
@@ -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])
@@ -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
diff --git a/trezor_app_specific.py b/trezor_app_specific.py
index 37e3f13..b6e9a42 100644
--- a/trezor_app_specific.py
+++ b/trezor_app_specific.py
@@ -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):
diff --git a/trezor_gui.py b/trezor_gui.py
index 266161a..f22c8fa 100644
--- a/trezor_gui.py
+++ b/trezor_gui.py
@@ -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):
diff --git a/trezor_passphrase_dialog.ui b/trezor_passphrase_dialog.ui
index e0cd183..18dc56c 100644
--- a/trezor_passphrase_dialog.ui
+++ b/trezor_passphrase_dialog.ui
@@ -55,6 +55,9 @@
-
+
+ Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText
+
QLineEdit::Password
diff --git a/utils.py b/utils.py
index e2db780..3dbf685 100644
--- a/utils.py
+++ b/utils.py
@@ -375,8 +375,8 @@ def printUsage(self):
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.
@@ -443,12 +443,13 @@ def parseArgs(self, argv, settings=None, logger=None):
sys.exit(19)
settings.LArg = loglevel * 10 # https://docs.python.org/2/library/logging.html#levels
logger.setLevel(settings.LArg)
- logger.info(u'Logging level set to %s (%d).',
- logging.getLevelName(settings.LArg), settings.LArg)
- for arg in args:
- # convert all input as possible to unicode UTF-8
- settings.inputArgs.append(arg.decode('utf-8'))
+ if sys.version_info[0] > 2:
+ settings.inputArgs = args
+ else:
+ for arg in args:
+ # convert all input as possible to unicode UTF-8
+ settings.inputArgs.append(arg.decode('utf-8'))
if settings.MArg and not settings.TArg:
self.settings.mlogger.log(u"Multiple inputs can only be used "
"in terminal mode. Add '-t' or remove '-m'.",
@@ -462,4 +463,12 @@ def parseArgs(self, argv, settings=None, logger=None):
logging.CRITICAL, "Wrong arguments", True, logger)
sys.exit(2)
settings.mlogger.setTerminalMode(settings.TArg)
+ self.settings.mlogger.log(u"Version: %s (%s)" %
+ (basics.THVERSION, basics.THVERSIONTEXT),
+ logging.INFO, "Wrong arguments", True, logger)
+ self.settings.mlogger.log(u"Python: %s" % sys.version.replace(" \n", "; "),
+ logging.INFO, "Wrong arguments", True, logger)
+ self.settings.mlogger.log(u'Logging level set to %s (%d).' %
+ (logging.getLevelName(settings.LArg), settings.LArg),
+ logging.INFO, "Wrong arguments", True, logger)
settings.printSettings()