Browse Source

qt send tab: handle invalid ln invoice; and ln invoice with ln disabled

fixes #5639
fixes #5662
dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
SomberNight 5 years ago
parent
commit
8dabdf8bfb
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 65
      electrum/gui/qt/main_window.py
  2. 26
      electrum/gui/qt/paytoedit.py
  3. 8
      electrum/lnaddr.py

65
electrum/gui/qt/main_window.py

@ -1629,10 +1629,31 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
outputs = self.payto_e.get_outputs(self.max_button.isChecked()) outputs = self.payto_e.get_outputs(self.max_button.isChecked())
return outputs return outputs
def check_send_tab_outputs_and_show_errors(self, outputs) -> bool: def check_send_tab_onchain_outputs_and_show_errors(self, outputs) -> bool:
"""Returns whether there are errors with outputs. """Returns whether there are errors with outputs.
Also shows error dialog to user if so. Also shows error dialog to user if so.
""" """
if not outputs:
self.show_error(_('No outputs'))
return True
for o in outputs:
if o.address is None:
self.show_error(_('Bitcoin Address is None'))
return True
if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
self.show_error(_('Invalid Bitcoin Address'))
return True
if o.value is None:
self.show_error(_('Invalid Amount'))
return True
return False # no errors
def check_send_tab_payto_line_and_show_errors(self) -> bool:
"""Returns whether there are errors.
Also shows error dialog to user if so.
"""
pr = self.payment_request pr = self.payment_request
if pr: if pr:
if pr.has_expired(): if pr.has_expired():
@ -1642,7 +1663,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if not pr: if not pr:
errors = self.payto_e.get_errors() errors = self.payto_e.get_errors()
if errors: if errors:
self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors])) self.show_warning(_("Invalid Lines found:") + "\n\n" +
'\n'.join([_("Line #") + f"{err.idx+1}: {err.line_content[:40]}... ({repr(err.exc)})"
for err in errors]))
return True return True
if self.payto_e.is_alias and self.payto_e.validated is False: if self.payto_e.is_alias and self.payto_e.validated is False:
@ -1653,21 +1676,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if not self.question(msg): if not self.question(msg):
return True return True
if not outputs:
self.show_error(_('No outputs'))
return True
for o in outputs:
if o.address is None:
self.show_error(_('Bitcoin Address is None'))
return True
if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
self.show_error(_('Invalid Bitcoin Address'))
return True
if o.value is None:
self.show_error(_('Invalid Amount'))
return True
return False # no errors return False # no errors
def pay_lightning_invoice(self, invoice): def pay_lightning_invoice(self, invoice):
@ -1694,14 +1702,21 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.show_error(_('Error') + '\n' + str(e)) self.show_error(_('Error') + '\n' + str(e))
def read_invoice(self): def read_invoice(self):
message = self.message_e.text() if self.check_send_tab_payto_line_and_show_errors():
amount = self.amount_e.get_amount() return
if not self.is_onchain: if not self.is_onchain:
return self.wallet.lnworker.parse_bech32_invoice(self.payto_e.lightning_invoice) invoice = self.payto_e.lightning_invoice
if not invoice:
return
if not self.wallet.lnworker:
self.show_error(_('Lightning is disabled'))
return
return self.wallet.lnworker.parse_bech32_invoice(invoice)
else: else:
outputs = self.read_outputs() outputs = self.read_outputs()
if self.check_send_tab_outputs_and_show_errors(outputs): if self.check_send_tab_onchain_outputs_and_show_errors(outputs):
return return
message = self.message_e.text()
return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI) return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI)
def do_save_invoice(self): def do_save_invoice(self):
@ -1968,8 +1983,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.payment_request_error_signal.emit() self.payment_request_error_signal.emit()
def parse_lightning_invoice(self, invoice): def parse_lightning_invoice(self, invoice):
from electrum.lnaddr import lndecode """Parse ln invoice, and prepare the send tab for it."""
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) from electrum.lnaddr import lndecode, LnDecodeException
try:
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
except Exception as e:
raise LnDecodeException(e) from e
pubkey = bh2u(lnaddr.pubkey.serialize()) pubkey = bh2u(lnaddr.pubkey.serialize())
for k,v in lnaddr.tags: for k,v in lnaddr.tags:
if k == 'd': if k == 'd':

26
electrum/gui/qt/paytoedit.py

@ -25,6 +25,7 @@
import re import re
from decimal import Decimal from decimal import Decimal
from typing import NamedTuple, Sequence
from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QFontMetrics
@ -33,6 +34,7 @@ from electrum.util import bfh
from electrum.transaction import TxOutput, push_script from electrum.transaction import TxOutput, push_script
from electrum.bitcoin import opcodes from electrum.bitcoin import opcodes
from electrum.logging import Logger from electrum.logging import Logger
from electrum.lnaddr import LnDecodeException
from .qrtextedit import ScanQRTextEdit from .qrtextedit import ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
@ -44,6 +46,12 @@ frozen_style = "QWidget {border:none;}"
normal_style = "QPlainTextEdit { }" normal_style = "QPlainTextEdit { }"
class PayToLineError(NamedTuple):
idx: int # index of line
line_content: str
exc: Exception
class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger): class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def __init__(self, win): def __init__(self, win):
@ -58,11 +66,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.c = None self.c = None
self.textChanged.connect(self.check_text) self.textChanged.connect(self.check_text)
self.outputs = [] self.outputs = []
self.errors = [] self.errors = [] # type: Sequence[PayToLineError]
self.is_pr = False self.is_pr = False
self.is_alias = False self.is_alias = False
self.update_size() self.update_size()
self.payto_address = None self.payto_address = None
self.lightning_invoice = None
self.previous_payto = '' self.previous_payto = ''
def setFrozen(self, b): def setFrozen(self, b):
@ -125,6 +134,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
outputs = [] outputs = []
total = 0 total = 0
self.payto_address = None self.payto_address = None
self.lightning_invoice = None
if len(lines) == 1: if len(lines) == 1:
data = lines[0] data = lines[0]
if data.startswith("bitcoin:"): if data.startswith("bitcoin:"):
@ -134,8 +144,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
if lower.startswith("lightning:ln"): if lower.startswith("lightning:ln"):
lower = lower[10:] lower = lower[10:]
if lower.startswith("ln"): if lower.startswith("ln"):
self.win.parse_lightning_invoice(lower) try:
self.lightning_invoice = lower self.win.parse_lightning_invoice(lower)
except LnDecodeException as e:
self.errors.append(PayToLineError(idx=0, line_content=data, exc=e))
else:
self.lightning_invoice = lower
return return
try: try:
self.payto_address = self.parse_output(data) self.payto_address = self.parse_output(data)
@ -150,8 +164,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
for i, line in enumerate(lines): for i, line in enumerate(lines):
try: try:
output = self.parse_address_and_amount(line) output = self.parse_address_and_amount(line)
except: except Exception as e:
self.errors.append((i, line.strip())) self.errors.append(PayToLineError(idx=i, line_content=line.strip(), exc=e))
continue continue
outputs.append(output) outputs.append(output)
if output.value == '!': if output.value == '!':
@ -171,7 +185,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.amount_edit.setAmount(total if outputs else None) self.amount_edit.setAmount(total if outputs else None)
self.win.lock_amount(total or len(lines)>1) self.win.lock_amount(total or len(lines)>1)
def get_errors(self): def get_errors(self) -> Sequence[PayToLineError]:
return self.errors return self.errors
def get_recipient(self): def get_recipient(self):

8
electrum/lnaddr.py

@ -276,10 +276,14 @@ class LnAddr(object):
now = time.time() now = time.time()
return now > self.get_expiry() + self.date return now > self.get_expiry() + self.date
def lndecode(a, verbose=False, expected_hrp=None):
class LnDecodeException(Exception): pass
def lndecode(invoice: str, *, verbose=False, expected_hrp=None) -> LnAddr:
if expected_hrp is None: if expected_hrp is None:
expected_hrp = constants.net.SEGWIT_HRP expected_hrp = constants.net.SEGWIT_HRP
hrp, data = bech32_decode(a, ignore_long_length=True) hrp, data = bech32_decode(invoice, ignore_long_length=True)
if not hrp: if not hrp:
raise ValueError("Bad bech32 checksum") raise ValueError("Bad bech32 checksum")

Loading…
Cancel
Save