URI: 
       tauth2fa.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tauth2fa.py (7138B)
       ---
            1 import copy
            2 from typing import TYPE_CHECKING
            3 
            4 from PyQt5.QtWidgets import (QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel,
            5                              QWidget, QHBoxLayout, QComboBox)
            6 
            7 from btchip.btchip import BTChipException
            8 
            9 from electrum.gui.qt.util import PasswordLineEdit
           10 
           11 from electrum.i18n import _
           12 from electrum import constants, bitcoin
           13 from electrum.logging import get_logger
           14 
           15 if TYPE_CHECKING:
           16     from .ledger import Ledger_Client
           17 
           18 
           19 _logger = get_logger(__name__)
           20 
           21 
           22 DEBUG = False
           23 
           24 helpTxt = [_("Your Ledger Wallet wants to tell you a one-time PIN code.<br><br>" \
           25             "For best security you should unplug your device, open a text editor on another computer, " \
           26             "put your cursor into it, and plug your device into that computer. " \
           27             "It will output a summary of the transaction being signed and a one-time PIN.<br><br>" \
           28             "Verify the transaction summary and type the PIN code here.<br><br>" \
           29             "Before pressing enter, plug the device back into this computer.<br>" ),
           30         _("Verify the address below.<br>Type the character from your security card corresponding to the <u><b>BOLD</b></u> character."),
           31         ]
           32 
           33 class LedgerAuthDialog(QDialog):
           34     def __init__(self, handler, data, *, client: 'Ledger_Client'):
           35         '''Ask user for 2nd factor authentication. Support text and security card methods.
           36         Use last method from settings, but support downgrade.
           37         '''
           38         QDialog.__init__(self, handler.top_level_window())
           39         self.handler = handler
           40         self.txdata = data
           41         self.idxs = self.txdata['keycardData'] if self.txdata['confirmationType'] > 1 else ''
           42         self.setMinimumWidth(650)
           43         self.setWindowTitle(_("Ledger Wallet Authentication"))
           44         self.cfg = copy.deepcopy(self.handler.win.wallet.get_keystore().cfg)
           45         self.dongle = client.dongleObject.dongle
           46         self.pin = ''
           47         
           48         self.devmode = self.getDevice2FAMode()
           49         if self.devmode == 0x11 or self.txdata['confirmationType'] == 1:
           50             self.cfg['mode'] = 0
           51         
           52         vbox = QVBoxLayout()
           53         self.setLayout(vbox)
           54         
           55         def on_change_mode(idx):
           56             self.cfg['mode'] = 0 if self.devmode == 0x11 else idx if idx > 0 else 1
           57             if self.cfg['mode'] > 0:
           58                 self.handler.win.wallet.get_keystore().cfg = self.cfg
           59                 self.handler.win.wallet.save_keystore()
           60             self.update_dlg()
           61         def return_pin():
           62             self.pin = self.pintxt.text() if self.txdata['confirmationType'] == 1 else self.cardtxt.text() 
           63             if self.cfg['mode'] == 1:
           64                 self.pin = ''.join(chr(int(str(i),16)) for i in self.pin)
           65             self.accept()
           66         
           67         self.modebox = QWidget()
           68         modelayout = QHBoxLayout()
           69         self.modebox.setLayout(modelayout)
           70         modelayout.addWidget(QLabel(_("Method:")))
           71         self.modes = QComboBox()
           72         modelayout.addWidget(self.modes, 2)
           73         modelayout.addStretch(1)
           74         self.modebox.setMaximumHeight(50)
           75         vbox.addWidget(self.modebox)
           76         
           77         self.populate_modes()
           78         self.modes.currentIndexChanged.connect(on_change_mode)
           79 
           80         self.helpmsg = QTextEdit()
           81         self.helpmsg.setStyleSheet("QTextEdit { color:black; background-color: lightgray; }")
           82         self.helpmsg.setReadOnly(True)
           83         vbox.addWidget(self.helpmsg)
           84         
           85         self.pinbox = QWidget()
           86         pinlayout = QHBoxLayout()
           87         self.pinbox.setLayout(pinlayout)
           88         self.pintxt = PasswordLineEdit()
           89         self.pintxt.setMaxLength(4)
           90         self.pintxt.returnPressed.connect(return_pin)
           91         pinlayout.addWidget(QLabel(_("Enter PIN:")))
           92         pinlayout.addWidget(self.pintxt)
           93         pinlayout.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
           94         pinlayout.addStretch(1)
           95         self.pinbox.setVisible(self.cfg['mode'] == 0)
           96         vbox.addWidget(self.pinbox)
           97                     
           98         self.cardbox = QWidget()
           99         card = QVBoxLayout()
          100         self.cardbox.setLayout(card)
          101         self.addrtext = QTextEdit()
          102         self.addrtext.setStyleSheet('''
          103             QTextEdit {
          104                 color:blue; background-color:lightgray; padding:15px 10px; border:none;
          105                 font-size:20pt; font-family: "Courier New", monospace; }
          106         ''')
          107         self.addrtext.setReadOnly(True)
          108         self.addrtext.setMaximumHeight(130)
          109         card.addWidget(self.addrtext)
          110         
          111         def pin_changed(s):
          112             if len(s) < len(self.idxs):
          113                 i = self.idxs[len(s)]
          114                 addr = self.txdata['address']
          115                 if not constants.net.TESTNET:
          116                     text = addr[:i] + '<u><b>' + addr[i:i+1] + '</u></b>' + addr[i+1:]
          117                 else:
          118                     # pin needs to be created from mainnet address
          119                     addr_mainnet = bitcoin.script_to_address(bitcoin.address_to_script(addr), net=constants.BitcoinMainnet)
          120                     addr_mainnet = addr_mainnet[:i] + '<u><b>' + addr_mainnet[i:i+1] + '</u></b>' + addr_mainnet[i+1:]
          121                     text = str(addr) + '\n' + str(addr_mainnet)
          122                 self.addrtext.setHtml(str(text))
          123             else:
          124                 self.addrtext.setHtml(_("Press Enter"))
          125                 
          126         pin_changed('')    
          127         cardpin = QHBoxLayout()
          128         cardpin.addWidget(QLabel(_("Enter PIN:")))
          129         self.cardtxt = PasswordLineEdit()
          130         self.cardtxt.setMaxLength(len(self.idxs))
          131         self.cardtxt.textChanged.connect(pin_changed)
          132         self.cardtxt.returnPressed.connect(return_pin)
          133         cardpin.addWidget(self.cardtxt)
          134         cardpin.addWidget(QLabel(_("NOT DEVICE PIN - see above")))
          135         cardpin.addStretch(1)
          136         card.addLayout(cardpin)
          137         self.cardbox.setVisible(self.cfg['mode'] == 1)
          138         vbox.addWidget(self.cardbox)
          139         
          140         self.update_dlg()
          141 
          142     def populate_modes(self):
          143         self.modes.blockSignals(True)
          144         self.modes.clear()
          145         self.modes.addItem(_("Summary Text PIN (requires dongle replugging)") if self.txdata['confirmationType'] == 1 else _("Summary Text PIN is Disabled"))
          146         if self.txdata['confirmationType'] > 1:
          147             self.modes.addItem(_("Security Card Challenge"))
          148         self.modes.blockSignals(False)
          149         
          150     def update_dlg(self):
          151         self.modes.setCurrentIndex(self.cfg['mode'])
          152         self.modebox.setVisible(True)
          153         self.helpmsg.setText(helpTxt[self.cfg['mode']])
          154         self.helpmsg.setMinimumHeight(180 if self.txdata['confirmationType'] == 1 else 100)
          155         self.helpmsg.setVisible(True)
          156         self.pinbox.setVisible(self.cfg['mode'] == 0)
          157         self.cardbox.setVisible(self.cfg['mode'] == 1)
          158         self.pintxt.setFocus(True) if self.cfg['mode'] == 0 else self.cardtxt.setFocus(True)
          159         self.setMaximumHeight(400)
          160 
          161     def getDevice2FAMode(self):
          162         apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode
          163         try:
          164             mode = self.dongle.exchange( bytearray(apdu) )
          165             return mode
          166         except BTChipException as e:
          167             _logger.debug('Device getMode Failed')
          168         return 0x11