URI: 
       tledger: remove mobile pairing 2FA support for Ledger Nano - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit be4cf321e06b8b48373ed42d2105a111d4f31b10
   DIR parent ab95eff5aa7a9d4626c9c18743d93da6500060c9
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Fri,  9 Aug 2019 19:54:09 +0200
       
       ledger: remove mobile pairing 2FA support for Ledger Nano
       
       service no longer provided by Ledger; app not in Google Play Store any more
       
       based on Electron-Cash/Electron-Cash#1298
       
       Diffstat:
         M contrib/requirements/requirements-… |       1 -
         M electrum/plugins/ledger/auth2fa.py  |     215 +------------------------------
         M electrum/plugins/ledger/ledger.py   |      11 ++++-------
       
       3 files changed, 11 insertions(+), 216 deletions(-)
       ---
   DIR diff --git a/contrib/requirements/requirements-hw.txt b/contrib/requirements/requirements-hw.txt
       t@@ -3,5 +3,4 @@ safet[hidapi]>=0.1.0
        keepkey>=6.0.3
        btchip-python>=0.1.26
        ckcc-protocol>=0.7.2
       -websocket-client
        hidapi
   DIR diff --git a/electrum/plugins/ledger/auth2fa.py b/electrum/plugins/ledger/auth2fa.py
       t@@ -1,19 +1,10 @@
       -import os
       -import hashlib
       -import logging
       -import json
        import copy
       -from binascii import hexlify, unhexlify
       -
       -import websocket
        
        from PyQt5.QtWidgets import (QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel,
       -                             QWidget, QHBoxLayout, QComboBox, QPushButton)
       -from PyQt5.QtCore import QThread, pyqtSignal
       +                             QWidget, QHBoxLayout, QComboBox)
        
        from btchip.btchip import BTChipException
        
       -from electrum.gui.qt.qrcodewidget import QRCodeWidget
        from electrum.i18n import _
        from electrum import constants, bitcoin
        from electrum.logging import get_logger
       t@@ -31,17 +22,12 @@ helpTxt = [_("Your Ledger Wallet wants to tell you a one-time PIN code.<br><br>"
                    "Verify the transaction summary and type the PIN code here.<br><br>" \
                    "Before pressing enter, plug the device back into this computer.<br>" ),
                _("Verify the address below.<br>Type the character from your security card corresponding to the <u><b>BOLD</b></u> character."),
       -        _("Waiting for authentication on your mobile phone"),
       -        _("Transaction accepted by mobile phone. Waiting for confirmation."),
       -        _("Click Pair button to begin pairing a mobile phone."),
       -        _("Scan this QR code with your Ledger Wallet phone app to pair it with this Ledger device.<br>" 
       -            "To complete pairing you will need your security card to answer a challenge." )
                ]
        
        class LedgerAuthDialog(QDialog):
            def __init__(self, handler, data):
       -        '''Ask user for 2nd factor authentication. Support text, security card and paired mobile methods.
       -        Use last method from settings, but support new pairing and downgrade.
       +        '''Ask user for 2nd factor authentication. Support text and security card methods.
       +        Use last method from settings, but support downgrade.
                '''
                QDialog.__init__(self, handler.top_level_window())
                self.handler = handler
       t@@ -51,7 +37,6 @@ class LedgerAuthDialog(QDialog):
                self.setWindowTitle(_("Ledger Wallet Authentication"))
                self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
                self.dongle = self.handler.win.wallet.get_keystore().get_client().dongle
       -        self.ws = None
                self.pin = ''
                
                self.devmode = self.getDevice2FAMode()
       t@@ -62,18 +47,11 @@ class LedgerAuthDialog(QDialog):
                self.setLayout(vbox)
                
                def on_change_mode(idx):
       -            if idx < 2 and self.ws:
       -                self.ws.stop()
       -                self.ws = None
                    self.cfg['mode'] = 0 if self.devmode == 0x11 else idx if idx > 0 else 1
       -            if self.cfg['mode'] > 1 and self.cfg['pair'] and not self.ws:
       -                self.req_validation()
                    if self.cfg['mode'] > 0:
                        self.handler.win.wallet.get_keystore().cfg = self.cfg
                        self.handler.win.wallet.save_keystore()
                    self.update_dlg()
       -        def add_pairing():
       -            self.do_pairing()
                def return_pin():
                    self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text() 
                    if self.cfg['mode'] == 1:
       t@@ -86,17 +64,13 @@ class LedgerAuthDialog(QDialog):
                modelayout.addWidget(QLabel(_("Method:")))
                self.modes = QComboBox()
                modelayout.addWidget(self.modes, 2)
       -        self.addPair = QPushButton(_("Pair"))
       -        self.addPair.setMaximumWidth(60)
       -        modelayout.addWidget(self.addPair)
                modelayout.addStretch(1)
                self.modebox.setMaximumHeight(50)
                vbox.addWidget(self.modebox)
                
                self.populate_modes()
                self.modes.currentIndexChanged.connect(on_change_mode)
       -        self.addPair.clicked.connect(add_pairing)
       -        
       +
                self.helpmsg = QTextEdit()
                self.helpmsg.setStyleSheet("QTextEdit { background-color: lightgray; }")
                self.helpmsg.setReadOnly(True)
       t@@ -155,207 +129,32 @@ class LedgerAuthDialog(QDialog):
                self.cardbox.setVisible(self.cfg['mode'] == 1)
                vbox.addWidget(self.cardbox)
                
       -        self.pairbox = QWidget()
       -        pairlayout = QVBoxLayout()
       -        self.pairbox.setLayout(pairlayout)
       -        pairhelp = QTextEdit(helpTxt[5])
       -        pairhelp.setStyleSheet("QTextEdit { background-color: lightgray; }")
       -        pairhelp.setReadOnly(True)
       -        pairlayout.addWidget(pairhelp, 1)
       -        self.pairqr = QRCodeWidget()
       -        pairlayout.addWidget(self.pairqr, 4)
       -        self.pairbox.setVisible(False)
       -        vbox.addWidget(self.pairbox)
                self.update_dlg()
       -        
       -        if self.cfg['mode'] > 1 and not self.ws:
       -            self.req_validation()
       -        
       +
            def populate_modes(self):
                self.modes.blockSignals(True)
                self.modes.clear()
                self.modes.addItem(_("Summary Text PIN (requires dongle replugging)") if self.txdata['confirmationType'] == 1 else _("Summary Text PIN is Disabled"))
                if self.txdata['confirmationType'] > 1:
                    self.modes.addItem(_("Security Card Challenge"))
       -            if not self.cfg['pair']:
       -                self.modes.addItem(_("Mobile - Not paired")) 
       -            else:
       -                self.modes.addItem(_("Mobile - {}").format(self.cfg['pair'][1]))
                self.modes.blockSignals(False)
                
            def update_dlg(self):
                self.modes.setCurrentIndex(self.cfg['mode'])
                self.modebox.setVisible(True)
       -        self.addPair.setText(_("Pair") if not self.cfg['pair'] else _("Re-Pair"))
       -        self.addPair.setVisible(self.txdata['confirmationType'] > 2)
       -        self.helpmsg.setText(helpTxt[self.cfg['mode'] if self.cfg['mode'] < 2 else 2 if self.cfg['pair'] else 4])
       +        self.helpmsg.setText(helpTxt[self.cfg['mode']])
                self.helpmsg.setMinimumHeight(180 if self.txdata['confirmationType'] == 1 else 100)
       -        self.pairbox.setVisible(False)
                self.helpmsg.setVisible(True)
                self.pinbox.setVisible(self.cfg['mode'] == 0)
                self.cardbox.setVisible(self.cfg['mode'] == 1)
                self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)
                self.setMaximumHeight(400)
        
       -    def do_pairing(self):
       -        rng = os.urandom(16)
       -        pairID = (hexlify(rng) + hexlify(hashlib.sha256(rng).digest()[0:1])).decode('utf-8')
       -        self.pairqr.setData(pairID)
       -        self.modebox.setVisible(False)
       -        self.helpmsg.setVisible(False)
       -        self.pinbox.setVisible(False)
       -        self.cardbox.setVisible(False)
       -        self.pairbox.setVisible(True)
       -        self.pairqr.setMinimumSize(300,300)
       -        if self.ws:
       -            self.ws.stop()
       -        self.ws = LedgerWebSocket(self, pairID)
       -        self.ws.pairing_done.connect(self.pairing_done)
       -        self.ws.start() 
       -               
       -    def pairing_done(self, data):
       -        if data is not None:
       -            self.cfg['pair'] = [ data['pairid'], data['name'], data['platform'] ]
       -            self.cfg['mode'] = 2
       -            self.handler.win.wallet.get_keystore().cfg = self.cfg
       -            self.handler.win.wallet.save_keystore()
       -        self.pin = 'paired'
       -        self.accept()
       -    
       -    def req_validation(self):
       -        if self.cfg['pair'] and 'secureScreenData' in self.txdata:
       -            if self.ws:
       -                self.ws.stop()
       -            self.ws = LedgerWebSocket(self, self.cfg['pair'][0], self.txdata)
       -            self.ws.req_updated.connect(self.req_updated)
       -            self.ws.start()
       -              
       -    def req_updated(self, pin):
       -        if pin == 'accepted':
       -            self.helpmsg.setText(helpTxt[3])
       -        else:
       -            self.pin = str(pin)
       -            self.accept()
       -        
            def getDevice2FAMode(self):
                apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode
                try:
                    mode = self.dongle.exchange( bytearray(apdu) )
                    return mode
                except BTChipException as e:
       -            debug_msg('Device getMode Failed')
       +            _logger.debug('Device getMode Failed')
                return 0x11
       -    
       -    def closeEvent(self, evnt):
       -        debug_msg("CLOSE - Stop WS")
       -        if self.ws:
       -            self.ws.stop()
       -        if self.pairbox.isVisible():
       -            evnt.ignore()
       -            self.update_dlg()
       -
       -class LedgerWebSocket(QThread):
       -    pairing_done = pyqtSignal(object)
       -    req_updated = pyqtSignal(str)
       -    
       -    def __init__(self, dlg, pairID, txdata=None):
       -        QThread.__init__(self)
       -        self.stopping = False
       -        self.pairID = pairID
       -        self.txreq = '{"type":"request","second_factor_data":"' + hexlify(txdata['secureScreenData']).decode('utf-8') + '"}' if txdata else None
       -        self.dlg = dlg
       -        self.dongle = self.dlg.dongle
       -        self.data = None
       -             
       -        #websocket.enableTrace(True)
       -        logging.basicConfig(level=logging.INFO)
       -        self.ws = websocket.WebSocketApp('wss://ws.ledgerwallet.com/2fa/channels', 
       -                                on_message = self.on_message, on_error = self.on_error,
       -                                on_close = self.on_close, on_open = self.on_open)
       -                                
       -    def run(self):
       -        while not self.stopping:
       -            self.ws.run_forever()
       -    def stop(self):
       -        debug_msg("WS: Stopping")
       -        self.stopping = True
       -        self.ws.close()
       -
       -    def on_message(self, ws, msg):
       -        data = json.loads(msg)
       -        if data['type'] == 'identify':
       -            debug_msg('Identify')
       -            apdu = [0xe0, 0x12, 0x01, 0x00, 0x41] # init pairing
       -            apdu.extend(unhexlify(data['public_key']))
       -            try:
       -                challenge = self.dongle.exchange( bytearray(apdu) )
       -                ws.send( '{"type":"challenge","data":"%s" }' % hexlify(challenge).decode('utf-8') )
       -                self.data = data
       -            except BTChipException as e:
       -                debug_msg('Identify Failed')
       -                
       -        if data['type'] == 'challenge':
       -            debug_msg('Challenge')
       -            apdu = [0xe0, 0x12, 0x02, 0x00, 0x10] # confirm pairing
       -            apdu.extend(unhexlify(data['data']))
       -            try:
       -                self.dongle.exchange( bytearray(apdu) )
       -                debug_msg('Pairing Successful')
       -                ws.send( '{"type":"pairing","is_successful":"true"}' )
       -                self.data['pairid'] = self.pairID
       -                self.pairing_done.emit(self.data)
       -            except BTChipException as e:
       -                debug_msg('Pairing Failed')
       -                ws.send( '{"type":"pairing","is_successful":"false"}' ) 
       -                self.pairing_done.emit(None)
       -            ws.send( '{"type":"disconnect"}' )
       -            self.stopping = True
       -            ws.close()
       -        
       -        if data['type'] == 'accept':
       -            debug_msg('Accepted')
       -            self.req_updated.emit('accepted')
       -        if data['type'] == 'response':
       -            debug_msg('Responded', data)
       -            self.req_updated.emit(str(data['pin']) if data['is_accepted'] else '')
       -            self.txreq = None
       -            self.stopping = True
       -            ws.close()
       -            
       -        if data['type'] == 'repeat':
       -            debug_msg('Repeat')
       -            if self.txreq:
       -                ws.send( self.txreq )
       -                debug_msg("Req Sent", self.txreq)
       -        if data['type'] == 'connect':
       -            debug_msg('Connected')
       -            if self.txreq:
       -                ws.send( self.txreq )
       -                debug_msg("Req Sent", self.txreq)
       -        if data['type'] == 'disconnect':
       -            debug_msg('Disconnected')
       -            ws.close()
       -            
       -    def on_error(self, ws, error):
       -        message = getattr(error, 'strerror', '')
       -        if not message:
       -            message = getattr(error, 'message', '')
       -        debug_msg("WS: %s" % message)
       -    
       -    def on_close(self, ws):
       -        debug_msg("WS: ### socket closed ###")
       -    
       -    def on_open(self, ws):
       -        debug_msg("WS: ### socket open ###")
       -        debug_msg("Joining with pairing ID", self.pairID)
       -        ws.send( '{"type":"join","room":"%s"}' % self.pairID )
       -        ws.send( '{"type":"repeat"}' )
       -        if self.txreq:
       -            ws.send( self.txreq )
       -            debug_msg("Req Sent", self.txreq)
       -
       -
       -def debug_msg(*args):
       -    if DEBUG:
       -        str_ = " ".join([str(item) for item in args])
       -        _logger.debug(str_)
   DIR diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
       t@@ -224,7 +224,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
                # device reconnects
                self.force_watching_only = False
                self.signing = False
       -        self.cfg = d.get('cfg', {'mode':0,'pair':''})
       +        self.cfg = d.get('cfg', {'mode': 0})
        
            def dump(self):
                obj = Hardware_KeyStore.dump(self)
       t@@ -461,8 +461,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
                            pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
                            if not pin:
                                raise UserWarning()
       -                    if pin != 'paired':
       -                        self.handler.show_message(_("Confirmed. Signing Transaction..."))
       +                    self.handler.show_message(_("Confirmed. Signing Transaction..."))
                        while inputIndex < len(inputs):
                            singleInput = [ chipInputs[inputIndex] ]
                            self.get_client().startUntrustedTransaction(False, 0,
       t@@ -485,16 +484,14 @@ class Ledger_KeyStore(Hardware_KeyStore):
                                pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
                                if not pin:
                                    raise UserWarning()
       -                        if pin != 'paired':
       -                            self.handler.show_message(_("Confirmed. Signing Transaction..."))
       +                        self.handler.show_message(_("Confirmed. Signing Transaction..."))
                            else:
                                # Sign input with the provided PIN
                                inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
                                inputSignature[0] = 0x30 # force for 1.4.9+
                                signatures.append(inputSignature)
                                inputIndex = inputIndex + 1
       -                    if pin != 'paired':
       -                        firstTransaction = False
       +                    firstTransaction = False
                except UserWarning:
                    self.handler.show_error(_('Cancelled by user'))
                    return