URI: 
       tMerge branch 'master' of git://github.com/spesmilo/electrum - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit aca8cf595679ac6fc9ba3ae9f60c38ea360f610c
   DIR parent 15f592f0220fa339b8bb709961a7c38ad0f56fff
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Sun,  5 Jul 2015 23:29:49 +0200
       
       Merge branch 'master' of git://github.com/spesmilo/electrum
       
       Diffstat:
         M plugins/__init__.py                 |       2 +-
         M plugins/btchipwallet.py             |     136 ++++++++++++++++++++-----------
       
       2 files changed, 90 insertions(+), 48 deletions(-)
       ---
   DIR diff --git a/plugins/__init__.py b/plugins/__init__.py
       t@@ -34,7 +34,7 @@ descriptions = [
                'requires': [('btchip', 'github.com/btchip/btchip-python')],
                'requires_wallet_type': ['btchip'],
                'registers_wallet_type': ('hardware', 'btchip', _("BTChip wallet")),
       -        'available_for': ['qt'],
       +        'available_for': ['qt', 'cmdline'],
            },
            {
                'name': 'cosigner_pool',
   DIR diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py
       t@@ -1,4 +1,4 @@
       -from PyQt4.Qt import QApplication, QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL
       +from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
        import PyQt4.QtCore as QtCore
        from binascii import unhexlify
        from binascii import hexlify
       t@@ -16,8 +16,9 @@ from electrum.plugins import BasePlugin, hook
        from electrum.transaction import deserialize
        from electrum.wallet import BIP32_HD_Wallet, BIP32_Wallet
        
       -from electrum.util import format_satoshis_plain
       +from electrum.util import format_satoshis_plain, print_error, print_msg
        import hashlib
       +import threading
        
        try:
            from btchip.btchipComm import getDongle, DongleWait
       t@@ -38,6 +39,7 @@ class Plugin(BasePlugin):
                BasePlugin.__init__(self, gui, name)
                self._is_available = self._init()
                self.wallet = None
       +        self.handler = None
        
            def constructor(self, s):
                return BTChipWallet(s)
       t@@ -72,9 +74,19 @@ class Plugin(BasePlugin):
                return True
        
            @hook
       +    def cmdline_load_wallet(self, wallet):
       +        self.wallet = wallet
       +        self.wallet.plugin = self
       +        if self.handler is None:
       +            self.handler = BTChipCmdLineHandler()
       +
       +    @hook
            def load_wallet(self, wallet, window):
                self.wallet = wallet
       +        self.wallet.plugin = self
                self.window = window
       +        if self.handler is None:
       +            self.handler = BTChipQTHandler(self.window.app)
                if self.btchip_is_connected():
                    if not self.wallet.check_proper_device():
                        QMessageBox.information(self.window, _('Error'), _("This wallet does not match your BTChip device"), _('OK'))
       t@@ -117,6 +129,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                self.force_watching_only = False
        
            def give_error(self, message, clear_client = False):
       +        print_error(message)
                if not self.signing:
                    QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK'))
                else:
       t@@ -130,6 +143,10 @@ class BTChipWallet(BIP32_HD_Wallet):
                if not self.accounts:
                    return 'create_accounts'
        
       +    def can_sign_xpubkey(self, x_pubkey):
       +        xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
       +        return xpub in self.master_public_keys.values()
       +
            def can_create_accounts(self):
                return False
        
       t@@ -150,10 +167,10 @@ class BTChipWallet(BIP32_HD_Wallet):
        
                aborted = False
                if not self.client or self.client.bad:
       -            try:
       +            try:   
                        d = getDongle(BTCHIP_DEBUG)
       -                d.setWaitImpl(DongleWaitQT(d))
                        self.client = btchip(d)
       +                self.client.handler = self.plugin.handler                
                        firmware = self.client.getFirmwareVersion()['version'].split(".")
                        if not checkFirmware(firmware):
                            d.close()
       t@@ -163,7 +180,6 @@ class BTChipWallet(BIP32_HD_Wallet):
                                aborted = True
                                raise e
                            d = getDongle(BTCHIP_DEBUG)
       -                    d.setWaitImpl(DongleWaitQT(d))
                            self.client = btchip(d)
                        try:
                            self.client.getOperationMode()
       t@@ -174,7 +190,6 @@ class BTChipWallet(BIP32_HD_Wallet):
                                dialog.exec_()
                                # Then fetch the reference again  as it was invalidated
                                d = getDongle(BTCHIP_DEBUG)
       -                        d.setWaitImpl(DongleWaitQT(d))
                                self.client = btchip(d)
                            else:
                                raise e
       t@@ -237,7 +252,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                # S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node
                # This only happens once so it's bearable
                self.get_client() # prompt for the PIN before displaying the dialog if necessary
       -        waitDialog.start("Computing master public key")
       +        self.plugin.handler.show_message("Computing master public key")
                try:
                    splitPath = bip32_path.split('/')
                    fingerprint = 0
       t@@ -260,7 +275,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                except Exception, e:
                    self.give_error(e, True)
                finally:
       -            waitDialog.emit(SIGNAL('dongle_done'))
       +            self.plugin.handler.stop()
        
                return EncodeBase58Check(xpub)
        
       t@@ -289,7 +304,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                if not self.check_proper_device():
                    self.give_error('Wrong device or password')
                address_path = self.address_id(address)
       -        waitDialog.start("Signing Message ...")
       +        self.plugin.handler.show_message("Signing message ...")
                try:
                    info = self.get_client().signMessagePrepare(address_path, message)
                    pin = ""
       t@@ -312,8 +327,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                except Exception, e:
                    self.give_error(e, True)
                finally:
       -            if waitDialog.waiting:
       -                waitDialog.emit(SIGNAL('dongle_done'))
       +            self.plugin.handler.stop()
                self.client.bad = use2FA
                self.signing = False
        
       t@@ -337,8 +351,8 @@ class BTChipWallet(BIP32_HD_Wallet):
            def sign_transaction(self, tx, password):
                if tx.is_complete():
                    return
       -        if tx.error:
       -            raise BaseException(tx.error)
       +        #if tx.error:
       +        #    raise BaseException(tx.error)
                self.signing = True
                inputs = []
                inputsPaths = []
       t@@ -382,7 +396,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                if not self.check_proper_device():
                    self.give_error('Wrong device or password')
        
       -        waitDialog.start("Signing Transaction ...")
       +        self.plugin.handler.show_message("Signing Transaction ...")
                try:
                    # Get trusted inputs from the original transactions
                    for utxo in inputs:
       t@@ -400,10 +414,9 @@ class BTChipWallet(BIP32_HD_Wallet):
                        format_satoshis_plain(self.get_tx_fee(tx)), changePath, bytearray(rawTx.decode('hex')))
                        if firstTransaction:
                            transactionOutput = outputData['outputData']
       -                if outputData['confirmationNeeded']:
       -                    use2FA = True
       +                if outputData['confirmationNeeded']:                    
                            # TODO : handle different confirmation types. For the time being only supports keyboard 2FA
       -                    waitDialog.emit(SIGNAL('dongle_done'))
       +                    self.plugin.handler.stop()
                            if 'keycardData' in outputData:
                                pin2 = ""
                                for keycardIndex in range(len(outputData['keycardData'])):
       t@@ -426,6 +439,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                                        raise Exception('Invalid PIN character')
                                pin = pin2
                            else:
       +                        use2FA = True
                                confirmed, p, pin = self.password_dialog()
                                if not confirmed:
                                    raise Exception('Aborted by user')
       t@@ -433,7 +447,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                                self.client.bad = True
                                self.device_checked = False
                                self.get_client(True)
       -                    waitDialog.start("Signing ...")
       +                    self.plugin.handler.show_message("Signing ...")
                        else:
                            # Sign input with the provided PIN
                            inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex],
       t@@ -445,8 +459,7 @@ class BTChipWallet(BIP32_HD_Wallet):
                except Exception, e:
                    self.give_error(e, True)
                finally:
       -            if waitDialog.waiting:
       -                waitDialog.emit(SIGNAL('dongle_done'))
       +            self.plugin.handler.stop()
        
                # Reformat transaction
                inputIndex = 0
       t@@ -464,13 +477,13 @@ class BTChipWallet(BIP32_HD_Wallet):
            def check_proper_device(self):
                pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:]
                if not self.device_checked:
       -            waitDialog.start("Checking device")
       +            self.plugin.handler.show_message("Checking device")
                    try:
                        nodeData = self.get_client().getWalletPublicKey("44'/0'/0'")
                    except Exception, e:
                        self.give_error(e, True)
                    finally:
       -                waitDialog.emit(SIGNAL('dongle_done'))
       +                self.plugin.handler.stop()
                    pubKeyDevice = compress_public_key(nodeData['publicKey'])
                    self.device_checked = True
                    if pubKey != pubKeyDevice:
       t@@ -488,40 +501,69 @@ class BTChipWallet(BIP32_HD_Wallet):
                            "It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor.\r\n\r\n" \
                            "Check that summary and then enter the second factor code here.\r\n" \
                            "Before clicking OK, re-plug the device once more (unplug it and plug it again if you read the second factor code on the same computer)")
       -        d = QDialog()
       -        d.setModal(1)
       -        d.setLayout( make_password_dialog(d, None, msg, False) )
       -        return run_password_dialog(d, None, None)
       +        response = self.plugin.handler.prompt_auth(msg)
       +        if response is None:
       +            return False, None, None
       +        return True, response, response
       +
       +class BTChipQTHandler:
        
       -class DongleWaitingDialog(QThread):
       -    def __init__(self):
       -        QThread.__init__(self)
       -        self.waiting = False
       +    def __init__(self, win):
       +        self.win = win
       +        self.win.connect(win, SIGNAL('btchip_done'), self.dialog_stop)
       +        self.win.connect(win, SIGNAL('message_dialog'), self.message_dialog)
       +        self.win.connect(win, SIGNAL('auth_dialog'), self.auth_dialog)
       +        self.done = threading.Event()
        
       -    def start(self, message):
       +    def stop(self):
       +        self.win.emit(SIGNAL('btchip_done'))
       +
       +    def show_message(self, msg):
       +        self.message = msg
       +        self.win.emit(SIGNAL('message_dialog'))
       +
       +    def prompt_auth(self, msg):
       +        self.done.clear()
       +        self.message = msg
       +        self.win.emit(SIGNAL('auth_dialog'))
       +        self.done.wait()
       +        return self.response
       +
       +    def auth_dialog(self):
       +        response = QInputDialog.getText(None, "BTChip Authentication", self.message, QLineEdit.Password)        
       +        if not response[1]:
       +            self.response = None
       +        else:
       +            self.response = str(response[0])
       +        self.done.set()
       +
       +    def message_dialog(self):
                self.d = QDialog()
                self.d.setModal(1)
       -        self.d.setWindowTitle('Please Wait')
       +        self.d.setWindowTitle('BTChip')
                self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
       -        l = QLabel(message)
       +        l = QLabel(self.message)
                vbox = QVBoxLayout(self.d)
                vbox.addWidget(l)
                self.d.show()
       -        if not self.waiting:
       -            self.waiting = True
       -            self.d.connect(waitDialog, SIGNAL('dongle_done'), self.stop)
        
       -    def stop(self):
       -        self.d.hide()
       -        self.waiting = False
       +    def dialog_stop(self):
       +        if self.d is not None:
       +            self.d.hide()
       +            self.d = None
        
       -if BTCHIP:
       -    waitDialog = DongleWaitingDialog()
       +class BTChipCmdLineHandler:
       +
       +    def stop(self):
       +        pass
        
       -    # Tickle the UI a bit while waiting
       -    class DongleWaitQT(DongleWait):
       -        def __init__(self, dongle):
       -            self.dongle = dongle
       +    def show_message(self, msg):
       +        print_msg(msg)
        
       -        def waitFirstResponse(self, timeout):
       -            return self.dongle.waitFirstResponse(timeout)
       +    def prompt_auth(self, msg):
       +        import getpass        
       +        print_msg(msg)
       +        response = getpass.getpass('')
       +        if len(response) == 0:
       +            return None
       +        return response