electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 7ddc29a912a2fdf6b2a307623a29c40823102c52
parent 50ba1faae4ca9570948aa1522e3221f6f6524aa6
Author: ThomasV <thomasv1@gmx.de>
Date:   Mon, 27 May 2013 02:25:59 -0700

Merge pull request #227 from tigereye/master

Sending/Receiving unsigned/signed transactions via QRCode
Diffstat:
Mgui/qrcodewidget.py | 10+++++++---
Mplugins/qrscanner.py | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 181 insertions(+), 6 deletions(-)

diff --git a/gui/qrcodewidget.py b/gui/qrcodewidget.py @@ -10,7 +10,6 @@ class QRCodeWidget(QWidget): def __init__(self, data = None): QWidget.__init__(self) - self.setMinimumSize(210, 210) self.addr = None self.qr = None if data: @@ -19,13 +18,18 @@ class QRCodeWidget(QWidget): def set_addr(self, addr): if self.addr != addr: - self.addr = addr + if len(addr) < 128: + MinSize = 210 + else: + MinSize = 500 + self.setMinimumSize(MinSize, MinSize) + self.addr = addr self.qr = None self.update() def update_qr(self): if self.addr and not self.qr: - for size in [4,5,6]: + for size in range(len(pyqrnative.QRUtil.PATTERN_POSITION_TABLE)): # [4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]: try: self.qr = pyqrnative.QRCode(size, pyqrnative.QRErrorCorrectLevel.L) self.qr.addData(self.addr) diff --git a/plugins/qrscanner.py b/plugins/qrscanner.py @@ -1,8 +1,15 @@ from electrum.util import print_error from urlparse import urlparse, parse_qs -from PyQt4.QtGui import QPushButton +from PyQt4.QtGui import QPushButton, QMessageBox, QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel +from PyQt4.QtCore import Qt from electrum_gui.i18n import _ +import re +from electrum.bitcoin import MIN_RELAY_TX_FEE, Transaction, is_valid +from electrum_gui.qrcodewidget import QRCodeWidget +import electrum_gui.bmp +import json + try: import zbar except ImportError: @@ -34,6 +41,16 @@ class Plugin(BasePlugin): b = QPushButton(_("Scan QR code")) b.clicked.connect(self.fill_from_qr) grid.addWidget(b, 1, 5) + b2 = QPushButton(_("Scan TxQR")) + b2.clicked.connect(self.read_raw_qr) + + if not self.gui.wallet.seed: + b3 = QPushButton(_("Show unsigned TxQR")) + b3.clicked.connect(self.show_raw_qr) + grid.addWidget(b3, 7, 1) + grid.addWidget(b2, 7, 2) + else: + grid.addWidget(b2, 7, 1) def scan_qr(self): @@ -51,11 +68,165 @@ class Plugin(BasePlugin): for r in proc.results: if str(r.type) != 'QRCODE': continue - return parse_uri(r.data) + return r.data + def show_raw_qr(self): + r = unicode( self.gui.payto_e.text() ) + r = r.strip() + + # label or alias, with address in brackets + m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) + to_address = m.group(2) if m else r + + if not is_valid(to_address): + QMessageBox.warning(self.gui, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK')) + return + + try: + amount = self.gui.read_amount(unicode( self.gui.amount_e.text())) + except: + QMessageBox.warning(self.gui, _('Error'), _('Invalid Amount'), _('OK')) + return + try: + fee = self.gui.read_amount(unicode( self.gui.fee_e.text())) + except: + QMessageBox.warning(self.gui, _('Error'), _('Invalid Fee'), _('OK')) + return + + try: + tx = self.gui.wallet.mktx( [(to_address, amount)], None, fee, account=self.gui.current_account) + except BaseException, e: + self.gui.show_message(str(e)) + return + + if tx.requires_fee(self.gui.wallet.verifier) and fee < MIN_RELAY_TX_FEE: + QMessageBox.warning(self.gui, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK')) + return + + try: + out = { + "hex" : tx.hash(), + "complete" : "false" + } + + input_info = [] + + except BaseException, e: + self.gui.show_message(str(e)) + + try: + json_text = json.dumps(tx.as_dict()).replace(' ', '') + self.show_tx_qrcode(json_text, 'Unsigned Transaction') + except BaseException, e: + self.gui.show_message(str(e)) + + def show_tx_qrcode(self, data, title): + if not data: return + d = QDialog(self.gui) + d.setModal(1) + d.setWindowTitle(title) + d.setMinimumSize(250, 525) + vbox = QVBoxLayout() + qrw = QRCodeWidget(data) + vbox.addWidget(qrw, 0) + hbox = QHBoxLayout() + hbox.addStretch(1) + + def print_qr(self): + filename = "qrcode.bmp" + electrum_gui.bmp.save_qrcode(qrw.qr, filename) + QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK')) + + b = QPushButton(_("Save")) + hbox.addWidget(b) + b.clicked.connect(print_qr) + + b = QPushButton(_("Close")) + hbox.addWidget(b) + b.clicked.connect(d.accept) + b.setDefault(True) + + vbox.addLayout(hbox, 1) + d.setLayout(vbox) + d.exec_() + + def read_raw_qr(self): + qrcode = self.scan_qr() + if qrcode: + tx_dict = self.gui.tx_dict_from_text(qrcode) + if tx_dict: + self.create_transaction_details_window(tx_dict) + + + def create_transaction_details_window(self, tx_dict): + tx = Transaction(tx_dict["hex"]) + + dialog = QDialog(self.gui) + dialog.setMinimumWidth(500) + dialog.setWindowTitle(_('Process Offline transaction')) + dialog.setModal(1) + + l = QGridLayout() + dialog.setLayout(l) + + l.addWidget(QLabel(_("Transaction status:")), 3,0) + l.addWidget(QLabel(_("Actions")), 4,0) + + if tx_dict["complete"] == False: + l.addWidget(QLabel(_("Unsigned")), 3,1) + if self.gui.wallet.seed : + b = QPushButton("Sign transaction") + input_info = json.loads(tx_dict["input_info"]) + b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog)) + l.addWidget(b, 4, 1) + else: + l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1) + else: + l.addWidget(QLabel(_("Signed")), 3,1) + b = QPushButton("Broadcast transaction") + b.clicked.connect(lambda: self.gui.send_raw_transaction(tx, dialog)) + l.addWidget(b,4,1) + + l.addWidget( self.gui.generate_transaction_information_widget(tx), 0,0,2,3) + closeButton = QPushButton(_("Close")) + closeButton.clicked.connect(lambda: dialog.done(0)) + l.addWidget(closeButton, 4,2) + + dialog.exec_() + + def do_protect(self, func, args): + if self.gui.wallet.use_encryption: + password = self.gui.password_dialog() + if not password: + return + else: + password = None + + if args != (False,): + args = (self,) + args + (password,) + else: + args = (self,password) + apply( func, args) + + def protected(func): + return lambda s, *args: s.do_protect(func, args) + + @protected + def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""): + try: + self.gui.wallet.signrawtransaction(tx, input_info, [], password) + txtext = json.dumps(tx.as_dict()).replace(' ', '') + self.show_tx_qrcode(txtext, 'Signed Transaction') + + except BaseException, e: + self.gui.show_message(str(e)) + def fill_from_qr(self): - qrcode = self.scan_qr() + qrcode = parse_uri(self.scan_qr()) + if not qrcode: + return + if 'address' in qrcode: self.gui.payto_e.setText(qrcode['address']) if 'amount' in qrcode: