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