extend bitcoin URIs with signed payment requests passed directly
This commit is contained in:
parent
e2185da094
commit
9c30ad3dd5
|
@ -475,7 +475,8 @@ def make_new_contact():
|
||||||
data = str(r['extras']['SCAN_RESULT']).strip()
|
data = str(r['extras']['SCAN_RESULT']).strip()
|
||||||
if data:
|
if data:
|
||||||
if re.match('^bitcoin:', data):
|
if re.match('^bitcoin:', data):
|
||||||
address, _, _, _, _ = util.parse_URI(data)
|
out = util.parse_URI(data)
|
||||||
|
address = out.get('address')
|
||||||
elif is_address(data):
|
elif is_address(data):
|
||||||
address = data
|
address = data
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -730,9 +730,12 @@ class ElectrumWindow:
|
||||||
entry.modify_base(Gtk.StateType.NORMAL, Gdk.color_parse("#ffffff"))
|
entry.modify_base(Gtk.StateType.NORMAL, Gdk.color_parse("#ffffff"))
|
||||||
|
|
||||||
def set_url(self, url):
|
def set_url(self, url):
|
||||||
payto, amount, label, message, payment_request = parse_URI(url)
|
out = parse_URI(url)
|
||||||
|
address = out.get('address')
|
||||||
|
message = out.get('message')
|
||||||
|
amount = out.get('amount')
|
||||||
self.notebook.set_current_page(1)
|
self.notebook.set_current_page(1)
|
||||||
self.payto_entry.set_text(payto)
|
self.payto_entry.set_text(address)
|
||||||
self.message_entry.set_text(message)
|
self.message_entry.set_text(message)
|
||||||
self.amount_entry.set_text(amount)
|
self.amount_entry.set_text(amount)
|
||||||
self.payto_sig.set_visible(False)
|
self.payto_sig.set_visible(False)
|
||||||
|
|
|
@ -308,12 +308,14 @@ class MiniWindow(QDialog):
|
||||||
|
|
||||||
def pay_from_URI(self, URI):
|
def pay_from_URI(self, URI):
|
||||||
try:
|
try:
|
||||||
dest_address, amount, label, message, request_url = util.parse_URI(URI)
|
out = util.parse_URI(URI)
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
address = out.get('address')
|
||||||
|
amount = out.get('amount')
|
||||||
amount_text = str(D(amount) / (10**self.actuator.g.decimal_point))
|
amount_text = str(D(amount) / (10**self.actuator.g.decimal_point))
|
||||||
self.address_input.setText(dest_address)
|
self.address_input.setText(address)
|
||||||
self.address_field_changed(dest_address)
|
self.address_field_changed(address)
|
||||||
self.amount_input.setText(amount_text)
|
self.amount_input.setText(amount_text)
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
|
|
|
@ -715,21 +715,15 @@ class ElectrumWindow(QMainWindow):
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
menu.addAction(_("Copy Address"), lambda: self.app.clipboard().setText(addr))
|
menu.addAction(_("Copy Address"), lambda: self.app.clipboard().setText(addr))
|
||||||
menu.addAction(_("Copy URI"), lambda: self.app.clipboard().setText(str(URI)))
|
menu.addAction(_("Copy URI"), lambda: self.app.clipboard().setText(str(URI)))
|
||||||
menu.addAction(_("Export as BIP70 file"), lambda: self.export_payment_request(addr)).setEnabled(amount is not None)
|
if req.get('signature'):
|
||||||
|
menu.addAction(_("View signed URI"), lambda: self.view_signed_request(addr))
|
||||||
|
menu.addAction(_("Export as BIP70 file"), lambda: self.export_payment_request(addr)) #.setEnabled(amount is not None)
|
||||||
menu.addAction(_("Delete"), lambda: self.delete_payment_request(item))
|
menu.addAction(_("Delete"), lambda: self.delete_payment_request(item))
|
||||||
run_hook('receive_list_menu', menu, addr)
|
run_hook('receive_list_menu', menu, addr)
|
||||||
menu.exec_(self.receive_list.viewport().mapToGlobal(position))
|
menu.exec_(self.receive_list.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
def save_payment_request(self):
|
def sign_payment_request(self, addr):
|
||||||
addr = str(self.receive_address_e.text())
|
req = self.wallet.receive_requests.get(addr)
|
||||||
amount = self.receive_amount_e.get_amount()
|
|
||||||
message = unicode(self.receive_message_e.text())
|
|
||||||
if not message and not amount:
|
|
||||||
QMessageBox.warning(self, _('Error'), _('No message or amount'), _('OK'))
|
|
||||||
return False
|
|
||||||
i = self.expires_combo.currentIndex()
|
|
||||||
expiration = map(lambda x: x[1], expiration_values)[i]
|
|
||||||
req = self.wallet.make_payment_request(addr, amount, message, expiration)
|
|
||||||
alias = self.config.get('alias')
|
alias = self.config.get('alias')
|
||||||
alias_privkey = None
|
alias_privkey = None
|
||||||
if alias and self.alias_info:
|
if alias and self.alias_info:
|
||||||
|
@ -754,16 +748,45 @@ class ElectrumWindow(QMainWindow):
|
||||||
req['requestor'] = requestor
|
req['requestor'] = requestor
|
||||||
req['signature'] = pr.signature.encode('hex')
|
req['signature'] = pr.signature.encode('hex')
|
||||||
self.wallet.add_payment_request(req, self.config)
|
self.wallet.add_payment_request(req, self.config)
|
||||||
|
|
||||||
|
def save_payment_request(self):
|
||||||
|
addr = str(self.receive_address_e.text())
|
||||||
|
amount = self.receive_amount_e.get_amount()
|
||||||
|
message = unicode(self.receive_message_e.text())
|
||||||
|
if not message and not amount:
|
||||||
|
QMessageBox.warning(self, _('Error'), _('No message or amount'), _('OK'))
|
||||||
|
return False
|
||||||
|
i = self.expires_combo.currentIndex()
|
||||||
|
expiration = map(lambda x: x[1], expiration_values)[i]
|
||||||
|
req = self.wallet.make_payment_request(addr, amount, message, expiration)
|
||||||
|
self.wallet.add_payment_request(req, self.config)
|
||||||
|
self.sign_payment_request(addr)
|
||||||
self.update_receive_tab()
|
self.update_receive_tab()
|
||||||
self.update_address_tab()
|
self.update_address_tab()
|
||||||
self.save_request_button.setEnabled(False)
|
self.save_request_button.setEnabled(False)
|
||||||
return pr
|
|
||||||
|
def view_signed_request(self, addr):
|
||||||
|
import urllib
|
||||||
|
r = self.wallet.receive_requests.get(addr)
|
||||||
|
pr = paymentrequest.serialize_request(r).SerializeToString()
|
||||||
|
pr_text = 'bitcoin:?s=' + bitcoin.base_encode(pr, base=58)
|
||||||
|
dialog = QDialog(self)
|
||||||
|
dialog.setWindowTitle(_("Signed Request"))
|
||||||
|
vbox = QVBoxLayout()
|
||||||
|
pr_e = ShowQRTextEdit(text=pr_text)
|
||||||
|
pr_e.setMaximumHeight(170)
|
||||||
|
msg = _('This URI contains the payment request signed with your alias.') + '\n' + _('Note: This feature is experimental')
|
||||||
|
vbox.addWidget(QLabel(msg))
|
||||||
|
vbox.addWidget(pr_e)
|
||||||
|
pr_e.addCopyButton(self.app)
|
||||||
|
vbox.addLayout(Buttons(CloseButton(dialog)))
|
||||||
|
dialog.setLayout(vbox)
|
||||||
|
dialog.exec_()
|
||||||
|
|
||||||
|
|
||||||
def export_payment_request(self, addr):
|
def export_payment_request(self, addr):
|
||||||
r = self.wallet.receive_requests.get(addr)
|
r = self.wallet.receive_requests.get(addr)
|
||||||
pr = paymentrequest.serialize_request(r)
|
pr = paymentrequest.serialize_request(r).SerializeToString()
|
||||||
pr = pr.SerializeToString()
|
|
||||||
name = r['id'] + '.bip70'
|
name = r['id'] + '.bip70'
|
||||||
fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
|
fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
|
||||||
if fileName:
|
if fileName:
|
||||||
|
@ -1320,42 +1343,51 @@ class ElectrumWindow(QMainWindow):
|
||||||
if not URI:
|
if not URI:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
address, amount, label, message, request_url = util.parse_URI(unicode(URI))
|
out = util.parse_URI(unicode(URI))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
|
QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.tabs.setCurrentIndex(1)
|
self.tabs.setCurrentIndex(1)
|
||||||
|
|
||||||
if not request_url:
|
r = out.get('r')
|
||||||
if label:
|
s = out.get('s')
|
||||||
if self.wallet.labels.get(address) != label:
|
if r or s:
|
||||||
if self.question(_('Save label "%(label)s" for address %(address)s ?'%{'label':label,'address':address})):
|
def get_payment_request_thread():
|
||||||
if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
|
if s:
|
||||||
self.wallet.addressbook.append(address)
|
from electrum import paymentrequest
|
||||||
self.wallet.set_label(address, label)
|
data = bitcoin.base_decode(s, None, base=58)
|
||||||
else:
|
self.payment_request = paymentrequest.PaymentRequest(data)
|
||||||
label = self.wallet.labels.get(address)
|
else:
|
||||||
if address:
|
self.payment_request = get_payment_request(r)
|
||||||
self.payto_e.setText(label + ' <'+ address +'>' if label else address)
|
if self.payment_request.verify(self.contacts):
|
||||||
if message:
|
self.emit(SIGNAL('payment_request_ok'))
|
||||||
self.message_e.setText(message)
|
else:
|
||||||
if amount:
|
self.emit(SIGNAL('payment_request_error'))
|
||||||
self.amount_e.setAmount(amount)
|
t = threading.Thread(target=get_payment_request_thread)
|
||||||
self.amount_e.textEdited.emit("")
|
t.setDaemon(True)
|
||||||
|
t.start()
|
||||||
|
self.prepare_for_payment_request()
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_payment_request_thread():
|
address = out.get('address')
|
||||||
self.payment_request = get_payment_request(request_url)
|
amount = out.get('amount')
|
||||||
if self.payment_request.verify(self.contacts):
|
label = out.get('label')
|
||||||
self.emit(SIGNAL('payment_request_ok'))
|
message = out.get('message')
|
||||||
else:
|
if label:
|
||||||
self.emit(SIGNAL('payment_request_error'))
|
if self.wallet.labels.get(address) != label:
|
||||||
|
if self.question(_('Save label "%(label)s" for address %(address)s ?'%{'label':label,'address':address})):
|
||||||
t = threading.Thread(target=get_payment_request_thread)
|
if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
|
||||||
t.setDaemon(True)
|
self.wallet.addressbook.append(address)
|
||||||
t.start()
|
self.wallet.set_label(address, label)
|
||||||
self.prepare_for_payment_request()
|
else:
|
||||||
|
label = self.wallet.labels.get(address)
|
||||||
|
if address:
|
||||||
|
self.payto_e.setText(label + ' <'+ address +'>' if label else address)
|
||||||
|
if message:
|
||||||
|
self.message_e.setText(message)
|
||||||
|
if amount:
|
||||||
|
self.amount_e.setAmount(amount)
|
||||||
|
self.amount_e.textEdited.emit("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
28
lib/util.py
28
lib/util.py
|
@ -261,29 +261,25 @@ def parse_URI(uri):
|
||||||
for k, v in pq.items():
|
for k, v in pq.items():
|
||||||
if len(v)!=1:
|
if len(v)!=1:
|
||||||
raise Exception('Duplicate Key', k)
|
raise Exception('Duplicate Key', k)
|
||||||
|
if k not in ['amount', 'label', 'message', 'r', 's']:
|
||||||
|
raise BaseException('Unknown key', k)
|
||||||
|
|
||||||
amount = label = message = request_url = ''
|
out = {k: v[0] for k, v in pq.items()}
|
||||||
if 'amount' in pq:
|
if address:
|
||||||
am = pq['amount'][0]
|
assert bitcoin.is_address(address)
|
||||||
|
out['address'] = address
|
||||||
|
if 'amount' in out:
|
||||||
|
am = out['amount']
|
||||||
m = re.match('([0-9\.]+)X([0-9])', am)
|
m = re.match('([0-9\.]+)X([0-9])', am)
|
||||||
if m:
|
if m:
|
||||||
k = int(m.group(2)) - 8
|
k = int(m.group(2)) - 8
|
||||||
amount = Decimal(m.group(1)) * pow( Decimal(10) , k)
|
amount = Decimal(m.group(1)) * pow( Decimal(10) , k)
|
||||||
else:
|
else:
|
||||||
amount = Decimal(am) * COIN
|
amount = Decimal(am) * COIN
|
||||||
if 'message' in pq:
|
out['amount'] = amount
|
||||||
message = pq['message'][0].decode('utf8')
|
if 'message' in out:
|
||||||
if 'label' in pq:
|
out['message'] = out['message'].decode('utf8')
|
||||||
label = pq['label'][0]
|
return out
|
||||||
if 'r' in pq:
|
|
||||||
request_url = pq['r'][0]
|
|
||||||
|
|
||||||
if request_url != '':
|
|
||||||
return address, amount, label, message, request_url
|
|
||||||
|
|
||||||
assert bitcoin.is_address(address)
|
|
||||||
|
|
||||||
return address, amount, label, message, request_url
|
|
||||||
|
|
||||||
|
|
||||||
def create_URI(addr, amount, message):
|
def create_URI(addr, amount, message):
|
||||||
|
|
Loading…
Reference in New Issue