Fix merge conflict

This commit is contained in:
Maran 2013-03-16 21:38:12 +01:00
commit dfd48319a3
28 changed files with 881 additions and 698 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ lib/icons_rc.py
build/ build/
dist/ dist/
*.egg/ *.egg/
/electrum.py /electrum.py
contrib/pyinstaller/

View File

@ -22,3 +22,5 @@ include scripts/servers
include scripts/validate_tx include scripts/validate_tx
include scripts/watch_address include scripts/watch_address
recursive-include data * recursive-include data *
recursive-include locale *.mo
recursive-include docs *

View File

@ -35,8 +35,7 @@ For an example, see Gavin's tutorial: https://gist.github.com/gavinandresen/3966
1. user creates an unsigned transaction using the online (watching-only) wallet. 1. user creates an unsigned transaction using the online (watching-only) wallet.
2. unsigned transaction is copied to the offline computer, and signed by the offline wallet. 2. unsigned transaction is copied to the offline computer, and signed by the offline wallet.
3. signed transaction is copied to the online computer, broadcasted by the online client. 3. signed transaction is copied to the online computer, broadcasted by the online client.
4. All these steps can be done via the command line interface or the classic GUI.
* Raw transactions can also be loaded/signed/broadcasted via the GUI.
* Many command line commands have been renamed in order to make the syntax consistent with bitcoind. * Many command line commands have been renamed in order to make the syntax consistent with bitcoind.

View File

@ -58,7 +58,7 @@ $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/ele
$PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec
# For building NSIS installer, run: # For building NSIS installer, run:
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsis wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi
#wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis #wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis
DATE=`date +"%Y%m%d"` DATE=`date +"%Y%m%d"`

View File

@ -40,7 +40,7 @@ $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/ele
$PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec
# For building NSIS installer, run: # For building NSIS installer, run:
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsis wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi
#wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis #wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis
cd dist cd dist

View File

@ -1,24 +1,64 @@
# -*- mode: python -*- # -*- mode: python -*-
a = Analysis(['C:/electrum/electrum'],
pathex=['Z:\\electrum-wine'], # We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
hiddenimports=[], a = Analysis(['electrum', 'gui/gui_classic.py', 'gui/gui_lite.py', 'gui/gui_text.py',
excludes=['Tkinter'], 'lib/util.py', 'lib/wallet.py', 'lib/simple_config.py',
'lib/bitcoin.py', 'lib/deserialize.py'
],
hiddenimports=["lib","gui"],
pathex=['lib:gui:plugins'],
hookspath=None) hookspath=None)
pyz = PYZ(a.pure, level=0)
##### include mydir in distribution #######
def extra_datas(mydir):
def rec_glob(p, files):
import os
import glob
for d in glob.glob(p):
if os.path.isfile(d):
files.append(d)
rec_glob("%s/*" % d, files)
files = []
rec_glob("%s/*" % mydir, files)
extra_datas = []
for f in files:
extra_datas.append((f, f, 'DATA'))
return extra_datas
###########################################
# append dirs
# Theme data
a.datas += extra_datas('data')
# Localization
a.datas += extra_datas('locale')
# Py folders that are needed because of the magic import finding
a.datas += extra_datas('gui')
a.datas += extra_datas('lib')
a.datas += extra_datas('plugins')
pyz = PYZ(a.pure)
exe = EXE(pyz, exe = EXE(pyz,
a.scripts, a.scripts,
exclude_binaries=1, exclude_binaries=1,
name=os.path.join('build\\pyi.win32\\electrum', 'electrum.exe'), name=os.path.join('build\\pyi.win32\\electrum', 'electrum.exe'),
debug=False, debug=True,
strip=None, strip=None,
upx=True, upx=False,
console=False ) icon='icons/electrum.ico',
console=True)
# The console True makes an annoying black box pop up, but it does make Electrum accept command line options.
coll = COLLECT(exe, coll = COLLECT(exe,
a.binaries, a.binaries,
a.zipfiles, a.zipfiles,
a.datas, a.datas,
strip=None, strip=None,
upx=True, upx=True,
debug=False,
icon='icons/electrum.ico',
console=True,
name=os.path.join('dist', 'electrum')) name=os.path.join('dist', 'electrum'))
app = BUNDLE(coll,
name=os.path.join('dist', 'electrum.app'))

View File

@ -30,7 +30,7 @@
;-------------------------------- ;--------------------------------
;Pages ;Pages
!insertmacro MUI_PAGE_LICENSE "tmp/LICENCE" ;!insertmacro MUI_PAGE_LICENSE "tmp/LICENCE"
;!insertmacro MUI_PAGE_COMPONENTS ;!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY

View File

@ -4,7 +4,7 @@
PYTHON_URL=http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi PYTHON_URL=http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi
PYQT4_URL=http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.9.5/PyQt-Py2.6-x86-gpl-4.9.5-1.exe PYQT4_URL=http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.9.5/PyQt-Py2.6-x86-gpl-4.9.5-1.exe
PYWIN32_URL=http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.6.exe/download PYWIN32_URL=http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.6.exe/download
PYINSTALLER_URL=https://github.com/downloads/pyinstaller/pyinstaller/pyinstaller-2.0.zip PYINSTALLER_URL=http://downloads.sourceforge.net/project/pyinstaller/2.0/pyinstaller-2.0.zip
NSIS_URL=http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download NSIS_URL=http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download
#ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download #ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download

View File

@ -34,20 +34,20 @@ except ImportError:
sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
is_android = 'ANDROID_DATA' in os.environ is_android = 'ANDROID_DATA' in os.environ
# load local module as electrum import __builtin__
if os.path.exists("lib") or is_android: __builtin__.use_local_modules = is_local or is_android
import imp
fp, pathname, description = imp.find_module('lib')
imp.load_module('electrum', fp, pathname, description)
fp, pathname, description = imp.find_module('gui')
imp.load_module('electrum_gui', fp, pathname, description)
# load local module as electrum
if __builtin__.use_local_modules:
import imp
imp.load_module('electrum', *imp.find_module('lib'))
imp.load_module('electrum_gui', *imp.find_module('gui'))
from electrum import * from electrum import *
# get password routine # get password routine
def prompt_password(prompt, confirm=True): def prompt_password(prompt, confirm=True):
import getpass import getpass
@ -162,6 +162,7 @@ if __name__ == '__main__':
gui.show_seed() gui.show_seed()
verifier = WalletVerifier(interface, config) verifier = WalletVerifier(interface, config)
verifier.start()
wallet.set_verifier(verifier) wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(wallet, config) synchronizer = WalletSynchronizer(wallet, config)
synchronizer.start() synchronizer.start()
@ -181,7 +182,6 @@ if __name__ == '__main__':
gui.password_dialog() gui.password_dialog()
wallet.save() wallet.save()
verifier.start()
gui.main(url) gui.main(url)
wallet.save() wallet.save()
@ -236,14 +236,16 @@ if __name__ == '__main__':
wallet.seed = None wallet.seed = None
wallet.init_sequence(str(seed)) wallet.init_sequence(str(seed))
else: else:
wallet.seed = str(seed) wallet.init_seed( str(seed) )
wallet.init_mpk( wallet.seed )
if not options.offline: if not options.offline:
interface = Interface(config) interface = Interface(config)
interface.start(wait=True) if not interface.start(wait=True):
print_msg("Not connected, aborting.")
sys.exit(1)
wallet.interface = interface wallet.interface = interface
verifier = WalletVerifier(interface, config) verifier = WalletVerifier(interface, config)
verifier.start()
wallet.set_verifier(verifier) wallet.set_verifier(verifier)
print_msg("Recovering wallet...") print_msg("Recovering wallet...")
@ -345,7 +347,7 @@ if __name__ == '__main__':
sys.exit(1) sys.exit(1)
if max_args < 0: if max_args < 0:
if len(args) > min_args: if len(args) > min_args + 1:
message = ' '.join(args[min_args:]) message = ' '.join(args[min_args:])
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message)) print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
args = args[0:min_args] + [ message ] args = args[0:min_args] + [ message ]
@ -358,9 +360,12 @@ if __name__ == '__main__':
if cmd not in offline_commands and not options.offline: if cmd not in offline_commands and not options.offline:
interface = Interface(config) interface = Interface(config)
interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n")) interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
interface.start() if not interface.start(wait=True):
print_msg("Not connected, aborting.")
sys.exit(1)
wallet.interface = interface wallet.interface = interface
verifier = WalletVerifier(interface, config) verifier = WalletVerifier(interface, config)
verifier.start()
wallet.set_verifier(verifier) wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(wallet, config) synchronizer = WalletSynchronizer(wallet, config)
synchronizer.start() synchronizer.start()

View File

@ -1 +1,2 @@
# do not remove this file # do not remove this file
from plugins import BasePlugin

View File

@ -36,7 +36,10 @@ class Exchanger(threading.Thread):
response = connection.getresponse() response = connection.getresponse()
if response.reason == httplib.responses[httplib.NOT_FOUND]: if response.reason == httplib.responses[httplib.NOT_FOUND]:
return return
response = json.loads(response.read()) try:
response = json.loads(response.read())
except:
return
quote_currencies = {} quote_currencies = {}
try: try:
for r in response: for r in response:

View File

@ -61,8 +61,6 @@ elif platform.system() == 'Darwin':
else: else:
MONOSPACE_FONT = 'monospace' MONOSPACE_FONT = 'monospace'
ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
from electrum import ELECTRUM_VERSION from electrum import ELECTRUM_VERSION
import re import re
@ -226,7 +224,6 @@ class StatusBarButton(QPushButton):
def waiting_dialog(f): def waiting_dialog(f):
s = Timer() s = Timer()
@ -248,19 +245,35 @@ def waiting_dialog(f):
w.destroy() w.destroy()
def ok_cancel_buttons(dialog): def ok_cancel_buttons(dialog, ok_label=_("OK") ):
hbox = QHBoxLayout() hbox = QHBoxLayout()
hbox.addStretch(1) hbox.addStretch(1)
b = QPushButton("Cancel") b = QPushButton(_("Cancel"))
hbox.addWidget(b) hbox.addWidget(b)
b.clicked.connect(dialog.reject) b.clicked.connect(dialog.reject)
b = QPushButton("OK") b = QPushButton(ok_label)
hbox.addWidget(b) hbox.addWidget(b)
b.clicked.connect(dialog.accept) b.clicked.connect(dialog.accept)
b.setDefault(True) b.setDefault(True)
return hbox return hbox
def text_dialog(parent, title, label, ok_label):
dialog = QDialog(parent)
dialog.setMinimumWidth(500)
dialog.setWindowTitle(title)
dialog.setModal(1)
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(label))
txt = QTextEdit()
l.addWidget(txt)
l.addLayout(ok_cancel_buttons(dialog, ok_label))
if dialog.exec_():
return unicode(txt.toPlainText())
default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
"receive":[[370],[370,200,130]] } "receive":[[370],[370,200,130]] }
@ -327,46 +340,44 @@ class ElectrumWindow(QMainWindow):
self.console.showMessage(self.wallet.banner) self.console.showMessage(self.wallet.banner)
# plugins that need to change the GUI do it here # plugins that need to change the GUI do it here
self.run_hook('init') self.run_hook('init_gui')
# plugins # plugins
def init_plugins(self): def init_plugins(self):
import imp, pkgutil import imp, pkgutil, __builtin__
if os.path.exists("plugins"): if __builtin__.use_local_modules:
fp, pathname, description = imp.find_module('plugins') fp, pathname, description = imp.find_module('plugins')
plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
imp.load_module('electrum_plugins', fp, pathname, description) imp.load_module('electrum_plugins', fp, pathname, description)
plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])] plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
else: else:
import electrum_plugins import electrum_plugins
plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)] plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names] plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
self.plugin_hooks = {} self.plugins = []
for p in self.plugins: for p in plugins:
try: try:
p.init(self) self.plugins.append( p.Plugin(self) )
except: except:
print_msg("Error:cannot initialize plugin",p) print_msg("Error:cannot initialize plugin",p)
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
def set_hook(self, name, callback):
h = self.plugin_hooks.get(name, [])
h.append(callback)
self.plugin_hooks[name] = h
def unset_hook(self, name, callback):
h = self.plugin_hooks.get(name,[])
if callback in h: h.remove(callback)
self.plugin_hooks[name] = h
def run_hook(self, name, args = ()):
args = (self,) + args
for cb in self.plugin_hooks.get(name,[]):
apply(cb, args)
def run_hook(self, name, *args):
for p in self.plugins:
if not p.is_enabled():
continue
try:
f = eval('p.'+name)
except:
continue
apply(f, args)
return
def set_label(self, name, text = None): def set_label(self, name, text = None):
changed = False changed = False
old_text = self.wallet.labels.get(name) old_text = self.wallet.labels.get(name)
@ -378,8 +389,7 @@ class ElectrumWindow(QMainWindow):
if old_text: if old_text:
self.wallet.labels.pop(name) self.wallet.labels.pop(name)
changed = True changed = True
self.run_hook('set_label', (name, text, changed)) self.run_hook('set_label', name, text, changed)
return changed return changed
@ -412,23 +422,6 @@ class ElectrumWindow(QMainWindow):
def timer_actions(self): def timer_actions(self):
self.run_hook('timer_actions') self.run_hook('timer_actions')
if self.payto_e.hasFocus():
return
r = unicode( self.payto_e.text() )
if r != self.previous_payto_e:
self.previous_payto_e = r
r = r.strip()
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
try:
to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
except:
return
if to_address:
s = r + ' <' + to_address + '>'
self.payto_e.setText(s)
def update_status(self): def update_status(self):
if self.wallet.interface and self.wallet.interface.is_connected: if self.wallet.interface and self.wallet.interface.is_connected:
if not self.wallet.up_to_date: if not self.wallet.up_to_date:
@ -520,7 +513,7 @@ class ElectrumWindow(QMainWindow):
vbox.addWidget(QLabel("Date: %s"%time_str)) vbox.addWidget(QLabel("Date: %s"%time_str))
vbox.addWidget(QLabel("Status: %d confirmations"%conf)) vbox.addWidget(QLabel("Status: %d confirmations"%conf))
if is_mine: if is_mine:
if fee: if fee is not None:
vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False))) vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False))) vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
else: else:
@ -577,10 +570,11 @@ class ElectrumWindow(QMainWindow):
def address_label_clicked(self, item, column, l, column_addr, column_label): def address_label_clicked(self, item, column, l, column_addr, column_label):
if column == column_label and item.isSelected(): if column == column_label and item.isSelected():
is_editable = item.data(0, 32).toBool()
if not is_editable:
return
addr = unicode( item.text(column_addr) ) addr = unicode( item.text(column_addr) )
label = unicode( item.text(column_label) ) label = unicode( item.text(column_label) )
if label in self.wallet.aliases.keys():
return
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
l.editItem( item, column ) l.editItem( item, column )
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
@ -590,26 +584,22 @@ class ElectrumWindow(QMainWindow):
if column == column_label: if column == column_label:
addr = unicode( item.text(column_addr) ) addr = unicode( item.text(column_addr) )
text = unicode( item.text(column_label) ) text = unicode( item.text(column_label) )
changed = False is_editable = item.data(0, 32).toBool()
if not is_editable:
return
if text in self.wallet.aliases.keys(): changed = self.set_label(addr, text)
print_error("Error: This is one of your aliases") if changed:
label = self.wallet.labels.get(addr,'') self.update_history_tab()
item.setText(column_label, QString(label)) self.update_completions()
else:
changed = self.set_label(addr, text)
if changed:
self.update_history_tab()
self.update_completions()
self.current_item_changed(item) self.current_item_changed(item)
self.run_hook('item_changed', (item, column)) self.run_hook('item_changed', item, column)
def current_item_changed(self, a): def current_item_changed(self, a):
self.run_hook('current_item_changed', (a,)) self.run_hook('current_item_changed', a)
@ -755,7 +745,7 @@ class ElectrumWindow(QMainWindow):
self.amount_e.textChanged.connect(lambda: entry_changed(False) ) self.amount_e.textChanged.connect(lambda: entry_changed(False) )
self.fee_e.textChanged.connect(lambda: entry_changed(True) ) self.fee_e.textChanged.connect(lambda: entry_changed(True) )
self.run_hook('create_send_tab', (grid,)) self.run_hook('create_send_tab', grid)
return w2 return w2
@ -764,8 +754,8 @@ class ElectrumWindow(QMainWindow):
for addr,label in self.wallet.labels.items(): for addr,label in self.wallet.labels.items():
if addr in self.wallet.addressbook: if addr in self.wallet.addressbook:
l.append( label + ' <' + addr + '>') l.append( label + ' <' + addr + '>')
l = l + self.wallet.aliases.keys()
self.run_hook('update_completions', l)
self.completions.setStringList(l) self.completions.setStringList(l)
@ -780,19 +770,9 @@ class ElectrumWindow(QMainWindow):
r = unicode( self.payto_e.text() ) r = unicode( self.payto_e.text() )
r = r.strip() r = r.strip()
# alias
m1 = re.match(ALIAS_REGEXP, r)
# label or alias, with address in brackets # label or alias, with address in brackets
m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
to_address = m.group(2) if m else r
if m1:
to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
if not to_address:
return
elif m2:
to_address = m2.group(2)
else:
to_address = r
if not is_valid(to_address): if not is_valid(to_address):
QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK')) QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
@ -815,7 +795,7 @@ class ElectrumWindow(QMainWindow):
self.show_message(str(e)) self.show_message(str(e))
return return
self.run_hook('send_tx', (tx,)) self.run_hook('send_tx', tx)
if label: if label:
self.set_label(tx.hash(), label) self.set_label(tx.hash(), label)
@ -844,10 +824,19 @@ class ElectrumWindow(QMainWindow):
def set_url(self, url): def set_url(self, url):
payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question) address, amount, label, message, signature, identity, url = util.parse_url(url)
if label and self.wallet.labels.get(address) != label:
if self.question('Give label "%s" to address %s ?'%(label,address)):
if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
self.wallet.addressbook.append(address)
self.set_label(address, label)
self.run_hook('set_url', url, self.show_message, self.question)
self.tabs.setCurrentIndex(1) self.tabs.setCurrentIndex(1)
label = self.wallet.labels.get(payto) label = self.wallet.labels.get(address)
m_addr = label + ' <'+ payto+'>' if label else payto m_addr = label + ' <'+ address +'>' if label else address
self.payto_e.setText(m_addr) self.payto_e.setText(m_addr)
self.message_e.setText(message) self.message_e.setText(message)
@ -1011,54 +1000,45 @@ class ElectrumWindow(QMainWindow):
t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize") t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
menu.addAction(t, lambda: self.toggle_priority(addr)) menu.addAction(t, lambda: self.toggle_priority(addr))
self.run_hook('receive_menu', (menu,)) self.run_hook('receive_menu', menu)
menu.exec_(self.receive_list.viewport().mapToGlobal(position)) menu.exec_(self.receive_list.viewport().mapToGlobal(position))
def payto(self, x, is_alias): def payto(self, addr):
if not x: return if not addr: return
if is_alias: label = self.wallet.labels.get(addr)
label = x m_addr = label + ' <' + addr + '>' if label else addr
m_addr = label
else:
addr = x
label = self.wallet.labels.get(addr)
m_addr = label + ' <' + addr + '>' if label else addr
self.tabs.setCurrentIndex(1) self.tabs.setCurrentIndex(1)
self.payto_e.setText(m_addr) self.payto_e.setText(m_addr)
self.amount_e.setFocus() self.amount_e.setFocus()
def delete_contact(self, x, is_alias):
def delete_contact(self, x):
if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")): if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
if not is_alias and x in self.wallet.addressbook: if x in self.wallet.addressbook:
self.wallet.addressbook.remove(x) self.wallet.addressbook.remove(x)
self.set_label(x, None) self.set_label(x, None)
elif is_alias and x in self.wallet.aliases: self.update_history_tab()
self.wallet.aliases.pop(x) self.update_contacts_tab()
self.update_history_tab() self.update_completions()
self.update_contacts_tab()
self.update_completions()
def create_contact_menu(self, position): def create_contact_menu(self, position):
# fixme: this function apparently has a side effect.
# if it is not called the menu pops up several times
#self.contacts_list.selectedIndexes()
item = self.contacts_list.itemAt(position) item = self.contacts_list.itemAt(position)
if not item: return if not item: return
addr = unicode(item.text(0)) addr = unicode(item.text(0))
label = unicode(item.text(1)) label = unicode(item.text(1))
is_alias = label in self.wallet.aliases.keys() is_editable = item.data(0,32).toBool()
x = label if is_alias else addr payto_addr = item.data(0,33).toString()
menu = QMenu() menu = QMenu()
menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr)) menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias)) menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address"))) menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
if not is_alias: if is_editable:
menu.addAction(_("Edit label"), lambda: self.edit_label(False)) menu.addAction(_("Edit label"), lambda: self.edit_label(False))
else: menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias)) self.run_hook('create_contact_menu', menu, item)
menu.exec_(self.contacts_list.viewport().mapToGlobal(position)) menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
@ -1068,7 +1048,7 @@ class ElectrumWindow(QMainWindow):
label = self.wallet.labels.get(address,'') label = self.wallet.labels.get(address,'')
item.setData(1,0,label) item.setData(1,0,label)
self.run_hook('update_receive_item', (address, item)) self.run_hook('update_receive_item', address, item)
c, u = self.wallet.get_addr_balance(address) c, u = self.wallet.get_addr_balance(address)
balance = format_satoshis( c + u, False, self.wallet.num_zeros ) balance = format_satoshis( c + u, False, self.wallet.num_zeros )
@ -1114,13 +1094,12 @@ class ElectrumWindow(QMainWindow):
for address in account[is_change]: for address in account[is_change]:
h = self.wallet.history.get(address,[]) h = self.wallet.history.get(address,[])
if not is_change: if h == []:
if h == []: gap += 1
gap += 1 if gap > self.wallet.gap_limit:
if gap > self.wallet.gap_limit: is_red = True
is_red = True else:
else: gap = 0
gap = 0
num_tx = '*' if h == ['*'] else "%d"%len(h) num_tx = '*' if h == ['*'] else "%d"%len(h)
item = QTreeWidgetItem( [ address, '', '', num_tx] ) item = QTreeWidgetItem( [ address, '', '', num_tx] )
@ -1143,43 +1122,28 @@ class ElectrumWindow(QMainWindow):
# we use column 1 because column 0 may be hidden # we use column 1 because column 0 may be hidden
l.setCurrentItem(l.topLevelItem(0),1) l.setCurrentItem(l.topLevelItem(0),1)
def show_contact_details(self, m):
a = self.wallet.aliases.get(m)
if a:
if a[0] in self.wallet.authorities.keys():
s = self.wallet.authorities.get(a[0])
else:
s = "self-signed"
msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
QMessageBox.information(self, 'Alias', msg, 'OK')
def update_contacts_tab(self): def update_contacts_tab(self):
l = self.contacts_list l = self.contacts_list
l.clear() l.clear()
alias_targets = []
for alias, v in self.wallet.aliases.items():
s, target = v
alias_targets.append(target)
item = QTreeWidgetItem( [ target, alias, '-'] )
item.setBackgroundColor(0, QColor('lightgray'))
l.addTopLevelItem(item)
for address in self.wallet.addressbook: for address in self.wallet.addressbook:
if address in alias_targets: continue
label = self.wallet.labels.get(address,'') label = self.wallet.labels.get(address,'')
n = 0 n = self.wallet.get_num_tx(address)
for tx in self.wallet.transactions.values():
if address in map(lambda x: x[0], tx.outputs): n += 1
item = QTreeWidgetItem( [ address, label, "%d"%n] ) item = QTreeWidgetItem( [ address, label, "%d"%n] )
item.setFont(0, QFont(MONOSPACE_FONT)) item.setFont(0, QFont(MONOSPACE_FONT))
# 32 = label can be edited (bool)
item.setData(0,32, True)
# 33 = payto string
item.setData(0,33, address)
l.addTopLevelItem(item) l.addTopLevelItem(item)
self.run_hook('update_contacts_tab', l)
l.setCurrentItem(l.topLevelItem(0)) l.setCurrentItem(l.topLevelItem(0))
def create_console_tab(self): def create_console_tab(self):
from qt_console import Console from qt_console import Console
self.console = console = Console() self.console = console = Console()
@ -1288,7 +1252,7 @@ class ElectrumWindow(QMainWindow):
try: try:
seed = self.wallet.decode_seed(password) seed = self.wallet.decode_seed(password)
except: except:
QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK')) QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
return return
self.show_seed(seed, self) self.show_seed(seed, self)
@ -1518,8 +1482,9 @@ class ElectrumWindow(QMainWindow):
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addLayout(ok_cancel_buttons(d)) vbox.addLayout(ok_cancel_buttons(d))
d.setLayout(vbox) d.setLayout(vbox)
self.run_hook('password_dialog', pw, grid, 1)
if not d.exec_(): return if not d.exec_(): return
return unicode(pw.text()) return unicode(pw.text())
@ -1595,20 +1560,22 @@ class ElectrumWindow(QMainWindow):
d.setModal(1) d.setModal(1)
vbox = QVBoxLayout() vbox = QVBoxLayout()
msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.") msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
vbox.addWidget(QLabel(msg)) vbox.addWidget(QLabel(msg))
grid = QGridLayout() grid = QGridLayout()
grid.setSpacing(8) grid.setSpacing(8)
seed_e = QLineEdit() seed_e = QLineEdit()
grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0) grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
grid.addWidget(seed_e, 1, 1) grid.addWidget(seed_e, 1, 1)
grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
gap_e = QLineEdit() gap_e = QLineEdit()
gap_e.setText("5") gap_e.setText("5")
grid.addWidget(QLabel(_('Gap limit')), 2, 0) grid.addWidget(QLabel(_('Gap limit')), 2, 0)
grid.addWidget(gap_e, 2, 1) grid.addWidget(gap_e, 2, 1)
grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
gap_e.textChanged.connect(lambda: numbify(gap_e,True)) gap_e.textChanged.connect(lambda: numbify(gap_e,True))
vbox.addLayout(grid) vbox.addLayout(grid)
@ -1733,23 +1700,10 @@ class ElectrumWindow(QMainWindow):
self.show_message("There was a problem sending your transaction:\n %s" % (result_message)) self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
def do_process_from_text(self): def do_process_from_text(self):
dialog = QDialog(self) text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
dialog.setMinimumWidth(500) if not text:
dialog.setWindowTitle(_('Input raw transaction')) return
dialog.setModal(1) tx_dict = self.tx_dict_from_text(text)
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(_("Transaction:")))
txt = QTextEdit()
l.addWidget(txt)
ok_button = QPushButton(_("Load transaction"))
ok_button.setDefault(True)
ok_button.clicked.connect(dialog.accept)
l.addWidget(ok_button)
dialog.exec_()
tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
if tx_dict: if tx_dict:
self.create_process_transaction_window(tx_dict) self.create_process_transaction_window(tx_dict)
@ -1832,7 +1786,7 @@ class ElectrumWindow(QMainWindow):
for key, value in json.loads(data).items(): for key, value in json.loads(data).items():
self.wallet.labels[key] = value self.wallet.labels[key] = value
self.wallet.save() self.wallet.save()
QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile)) QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
except (IOError, os.error), reason: except (IOError, os.error), reason:
QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason)) QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
@ -1857,24 +1811,34 @@ class ElectrumWindow(QMainWindow):
@protected @protected
def do_import_privkey(self, password): def do_import_privkey(self, password):
if not self.wallet.imported_keys: if not self.wallet.imported_keys:
r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \ r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
+ _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \ + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
+ _('In addition, when you send bitcoins from one of your imported addresses, the "change" will be sent to an address derived from your seed, unless you disabled this option.') + '<p>' \
+ _('Are you sure you understand what you are doing?'), 3, 4) + _('Are you sure you understand what you are doing?'), 3, 4)
if r == 4: return if r == 4: return
text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':') text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
if not ok: return if not text: return
sec = str(text).strip()
try: text = str(text).split()
addr = self.wallet.import_key(sec, password) badkeys = []
if not addr: addrlist = []
QMessageBox.critical(None, _("Unable to import key"), "error") for key in text:
try:
addr = self.wallet.import_key(key, password)
except BaseException as e:
badkeys.append(key)
continue
if not addr:
badkeys.append(key)
else: else:
QMessageBox.information(None, _("Key imported"), addr) addrlist.append(addr)
self.update_receive_tab() if addrlist:
self.update_history_tab() QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
except BaseException as e: if badkeys:
QMessageBox.critical(None, _("Unable to import key"), str(e)) QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
self.update_receive_tab()
self.update_history_tab()
def settings_dialog(self): def settings_dialog(self):
@ -2036,7 +2000,7 @@ class ElectrumWindow(QMainWindow):
grid_plugins.setColumnStretch(0,1) grid_plugins.setColumnStretch(0,1)
tabs.addTab(tab5, _('Plugins') ) tabs.addTab(tab5, _('Plugins') )
def mk_toggle(cb, p): def mk_toggle(cb, p):
return lambda: cb.setChecked(p.toggle(self)) return lambda: cb.setChecked(p.toggle())
for i, p in enumerate(self.plugins): for i, p in enumerate(self.plugins):
try: try:
name, description = p.get_info() name, description = p.get_info()
@ -2222,7 +2186,7 @@ class ElectrumWindow(QMainWindow):
for p in protocol_letters: for p in protocol_letters:
i = protocol_letters.index(p) i = protocol_letters.index(p)
j = server_protocol.model().index(i,0) j = server_protocol.model().index(i,0)
if p not in pp.keys(): if p not in pp.keys() and interface.is_connected:
server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1) server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
else: else:
server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1) server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)

View File

@ -868,14 +868,14 @@ class ElectrumWindow:
self.show_message(tx_details) self.show_message(tx_details)
elif treeview == self.contacts_treeview: elif treeview == self.contacts_treeview:
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
a = self.wallet.aliases.get(m) #a = self.wallet.aliases.get(m)
if a: #if a:
if a[0] in self.wallet.authorities.keys(): # if a[0] in self.wallet.authorities.keys():
s = self.wallet.authorities.get(a[0]) # s = self.wallet.authorities.get(a[0])
else: # else:
s = "self-signed" # s = "self-signed"
msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] # msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
self.show_message(msg) # self.show_message(msg)
def treeview_key_press(self, treeview, event): def treeview_key_press(self, treeview, event):
@ -890,14 +890,14 @@ class ElectrumWindow:
self.show_message(tx_details) self.show_message(tx_details)
elif treeview == self.contacts_treeview: elif treeview == self.contacts_treeview:
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
a = self.wallet.aliases.get(m) #a = self.wallet.aliases.get(m)
if a: #if a:
if a[0] in self.wallet.authorities.keys(): # if a[0] in self.wallet.authorities.keys():
s = self.wallet.authorities.get(a[0]) # s = self.wallet.authorities.get(a[0])
else: # else:
s = "self" # s = "self"
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0] # msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
self.show_message(msg) # self.show_message(msg)
return False return False
@ -1145,17 +1145,14 @@ class ElectrumWindow:
def update_sending_tab(self): def update_sending_tab(self):
# detect addresses that are not mine in history, add them here... # detect addresses that are not mine in history, add them here...
self.addressbook_list.clear() self.addressbook_list.clear()
for alias, v in self.wallet.aliases.items(): #for alias, v in self.wallet.aliases.items():
s, target = v # s, target = v
label = self.wallet.labels.get(alias) # label = self.wallet.labels.get(alias)
self.addressbook_list.append((alias, label, '-')) # self.addressbook_list.append((alias, label, '-'))
for address in self.wallet.addressbook: for address in self.wallet.addressbook:
label = self.wallet.labels.get(address) label = self.wallet.labels.get(address)
n = 0 n = self.wallet.get_num_tx(address)
for tx in self.wallet.transactions.values():
if address in map(lambda x:x[0], tx.outputs): n += 1
self.addressbook_list.append((address, label, "%d"%n)) self.addressbook_list.append((address, label, "%d"%n))
def update_history_tab(self): def update_history_tab(self):
@ -1176,7 +1173,7 @@ class ElectrumWindow:
label, is_default_label = self.wallet.get_label(tx_hash) label, is_default_label = self.wallet.get_label(tx_hash)
tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else '' tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
details = self.wallet.get_tx_details(tx_hash) details = self.get_tx_details(tx_hash)
self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label, self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
format_satoshis(value,True,self.wallet.num_zeros), format_satoshis(value,True,self.wallet.num_zeros),
@ -1184,6 +1181,40 @@ class ElectrumWindow:
if cursor: self.history_treeview.set_cursor( cursor ) if cursor: self.history_treeview.set_cursor( cursor )
def get_tx_details(self, tx_hash):
import datetime
if not tx_hash: return ''
tx = self.wallet.transactions.get(tx_hash)
is_mine, v, fee = self.wallet.get_tx_value(tx)
conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
if timestamp:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
else:
time_str = 'pending'
inputs = map(lambda x: x.get('address'), tx.inputs)
outputs = map(lambda x: x.get('address'), tx.d['outputs'])
tx_details = "Transaction Details" +"\n\n" \
+ "Transaction ID:\n" + tx_hash + "\n\n" \
+ "Status: %d confirmations\n"%conf
if is_mine:
if fee:
tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
+ "Transaction fee: %s\n"% format_satoshis(fee, False)
else:
tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
+ "Transaction fee: unknown\n"
else:
tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
tx_details += "Date: %s\n\n"%time_str \
+ "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
+ "Outputs:\n-"+ '\n-'.join(outputs)
return tx_details
def newaddress_dialog(self, w): def newaddress_dialog(self, w):

32
gui/plugins.py Normal file
View File

@ -0,0 +1,32 @@
class BasePlugin:
def get_info(self):
return self.fullname, self.description
def __init__(self, gui, name, fullname, description):
self.name = name
self.fullname = fullname
self.description = description
self.gui = gui
self.config = gui.config
def toggle(self):
enabled = not self.is_enabled()
self.set_enabled(enabled)
self.init_gui()
return enabled
def init_gui(self):
pass
def is_enabled(self):
return self.is_available() and self.config.get('use_'+self.name) is True
def is_available(self):
return True
def set_enabled(self, enabled):
self.config.set_key('use_'+self.name, enabled, True)

BIN
icons/electrum.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -364,10 +364,9 @@ random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
def bip32_init(seed): def bip32_init(seed):
import hmac import hmac
seed = seed.decode('hex')
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest() I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
print "seed", seed.encode('hex')
master_secret = I[0:32] master_secret = I[0:32]
master_chain = I[32:] master_chain = I[32:]
@ -415,8 +414,8 @@ def CKD_prime(K, c, n):
class ElectrumSequence: class ElectrumSequence:
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """ """ Privatekey(type,n) = Master_private_key + H(n|S|type) """
def __init__(self, master_public_key, mpk2 = None, mpk3 = None): def __init__(self, mpk, mpk2 = None, mpk3 = None):
self.master_public_key = master_public_key self.mpk = mpk
self.mpk2 = mpk2 self.mpk2 = mpk2
self.mpk3 = mpk3 self.mpk3 = mpk3
@ -445,7 +444,7 @@ class ElectrumSequence:
address = public_key_to_bc_address( pubkey.decode('hex') ) address = public_key_to_bc_address( pubkey.decode('hex') )
elif not self.mpk3: elif not self.mpk3:
pubkey1 = self.get_pubkey(sequence) pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, use_mpk2=True) pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"] address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
else: else:
pubkey1 = self.get_pubkey(sequence) pubkey1 = self.get_pubkey(sequence)
@ -456,7 +455,7 @@ class ElectrumSequence:
def get_pubkey(self, sequence, mpk=None): def get_pubkey(self, sequence, mpk=None):
curve = SECP256k1 curve = SECP256k1
if mpk is None: mpk = self.master_public_key if mpk is None: mpk = self.mpk
z = self.get_sequence(sequence, mpk) z = self.get_sequence(sequence, mpk)
master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 ) master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 )
pubkey_point = master_public_key.pubkey.point + z*curve.generator pubkey_point = master_public_key.pubkey.point + z*curve.generator
@ -465,7 +464,7 @@ class ElectrumSequence:
def get_private_key_from_stretched_exponent(self, sequence, secexp): def get_private_key_from_stretched_exponent(self, sequence, secexp):
order = generator_secp256k1.order() order = generator_secp256k1.order()
secexp = ( secexp + self.get_sequence(sequence, self.master_public_key) ) % order secexp = ( secexp + self.get_sequence(sequence, self.mpk) ) % order
pk = number_to_string( secexp, generator_secp256k1.order() ) pk = number_to_string( secexp, generator_secp256k1.order() )
compressed = False compressed = False
return SecretToASecret( pk, compressed ) return SecretToASecret( pk, compressed )
@ -483,7 +482,7 @@ class ElectrumSequence:
secexp = self.stretch_key(seed) secexp = self.stretch_key(seed)
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex') master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
if master_public_key != self.master_public_key: if master_public_key != self.mpk:
print_error('invalid password (mpk)') print_error('invalid password (mpk)')
raise BaseException('Invalid password') raise BaseException('Invalid password')
return True return True
@ -499,8 +498,8 @@ class ElectrumSequence:
redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript'] redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
else: else:
pubkey1 = self.get_pubkey(sequence) pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2) pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
pubkey3 = self.get_pubkey(sequence,mpk=self.mpk3) pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript'] redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
return pk_addr, redeemScript return pk_addr, redeemScript
@ -510,43 +509,71 @@ class ElectrumSequence:
class BIP32Sequence: class BIP32Sequence:
def __init__(self, mpkc, mpkc2 = None): def __init__(self, mpk, mpk2 = None, mpk3 = None):
self.master_public_key, self.master_chain = mpkc self.mpk = mpk
if mpkc2: self.mpk2 = mpk2
self.master_public_key2, self.master_chain2 = mpkc2 self.mpk3 = mpk3
self.is_p2sh = True
else:
self.is_p2sh = False
@classmethod @classmethod
def mpk_from_seed(klass, seed): def mpk_from_seed(klass, seed):
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed) master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
return master_public_key.encode('hex'), master_chain.encode('hex') return master_public_key.encode('hex'), master_chain.encode('hex')
def get_pubkey(self, sequence, use_mpk2=False): def get_pubkey(self, sequence, mpk = None):
if not use_mpl2: if not mpk: mpk = self.mpk
K = self.master_public_key.decode('hex') master_public_key, master_chain = self.mpk
chain = self.master_chain.decode('hex') K = master_public_key.decode('hex')
else: chain = master_chain.decode('hex')
K = self.master_public_key_2.decode('hex')
chain = self.master_chain_2.decode('hex')
for i in sequence: for i in sequence:
K, K_compressed, chain = CKD_prime(K, chain, i) K, K_compressed, chain = CKD_prime(K, chain, i)
return K_compressed return K_compressed.encode('hex')
def get_address(self, sequence): def get_address(self, sequence):
return hash_160_to_bc_address(hash_160(self.get_pubkey(sequence))) if not self.mpk2:
pubkey = self.get_pubkey(sequence)
address = public_key_to_bc_address( pubkey.decode('hex') )
elif not self.mpk3:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
else:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3)
address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"]
return address
def get_private_key(self, seed, sequence): def get_private_key(self, sequence, seed):
k = self.master_secret master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
chain = self.master_chain chain = master_chain
k = master_secret
for i in sequence: for i in sequence:
k, k_compressed, chain = CKD(k, chain, i) k, chain = CKD(k, chain, i)
return SecretToASecret(k0, True) return SecretToASecret(k, True)
def get_private_keys(self, sequence_list, seed):
return [ self.get_private_key( sequence, seed) for sequence in sequence_list]
def check_seed(self, seed): def check_seed(self, seed):
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed) master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
assert self.master_public_key == master_public_key assert self.mpk == (master_public_key.encode('hex'), master_chain.encode('hex'))
def get_input_info(self, sequence):
if not self.mpk2:
pk_addr = self.get_address(sequence)
redeemScript = None
elif not self.mpk3:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
else:
pubkey1 = self.get_pubkey(sequence)
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
return pk_addr, redeemScript
################################## transactions ################################## transactions
@ -842,7 +869,7 @@ class Transaction:
def test_bip32(): def test_bip32():
seed = "ff000000000000000000000000000000".decode('hex') seed = "ff000000000000000000000000000000"
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed) master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
print "secret key", master_secret.encode('hex') print "secret key", master_secret.encode('hex')

View File

@ -82,9 +82,10 @@ class Commands:
self.wallet = wallet self.wallet = wallet
self.interface = interface self.interface = interface
self._callback = callback self._callback = callback
self.password = None
def _run(self, method, args, password_getter): def _run(self, method, args, password_getter):
if method in protected_commands: if method in protected_commands and self.wallet.use_encryption:
self.password = apply(password_getter,()) self.password = apply(password_getter,())
f = eval('self.'+method) f = eval('self.'+method)
result = apply(f,args) result = apply(f,args)

View File

@ -3,6 +3,7 @@
# #
from bitcoin import public_key_to_bc_address, hash_160_to_bc_address, hash_encode, hash_160 from bitcoin import public_key_to_bc_address, hash_160_to_bc_address, hash_encode, hash_160
from util import print_error
#import socket #import socket
import time import time
import struct import struct
@ -356,7 +357,8 @@ def get_address_from_input_script(bytes):
pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ] pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ]
return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5) return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5)
raise BaseException("no match for scriptsig") print_error("cannot find address in input script", bytes.encode('hex'))
return [], [], "(None)"

View File

@ -595,8 +595,8 @@ class Interface(threading.Thread):
# wait until connection is established # wait until connection is established
self.connect_event.wait() self.connect_event.wait()
if not self.is_connected: if not self.is_connected:
print_msg("Not connected, aborting.") return False
sys.exit(1) return True
def run(self): def run(self):
while True: while True:

View File

@ -1,4 +1,4 @@
import os, sys import os, sys, re
import platform import platform
import shutil import shutil
from datetime import datetime from datetime import datetime
@ -147,3 +147,36 @@ def age(from_date, since_date = None, target_tz=None, include_seconds=False):
return "about 1 year ago" return "about 1 year ago"
else: else:
return "over %d years ago" % (round(distance_in_minutes / 525600)) return "over %d years ago" % (round(distance_in_minutes / 525600))
# URL decode
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
def parse_url(url):
o = url[8:].split('?')
address = o[0]
if len(o)>1:
params = o[1].split('&')
else:
params = []
amount = label = message = signature = identity = ''
for p in params:
k,v = p.split('=')
uv = urldecode(v)
if k == 'amount': amount = uv
elif k == 'message': message = uv
elif k == 'label': label = uv
elif k == 'signature':
identity, signature = uv.split(':')
url = url.replace('&%s=%s'%(k,v),'')
else:
print k,v
return address, amount, label, message, signature, identity, url

View File

@ -49,14 +49,10 @@ class WalletVerifier(threading.Thread):
def get_confirmations(self, tx): def get_confirmations(self, tx):
""" return the number of confirmations of a monitored transaction. """ """ return the number of confirmations of a monitored transaction. """
with self.lock: with self.lock:
if tx in self.transactions.keys(): if tx in self.verified_tx:
if tx in self.verified_tx: height, timestamp = self.verified_tx[tx]
height, timestamp = self.verified_tx[tx] conf = (self.local_height - height + 1)
conf = (self.local_height - height + 1)
else:
conf = -1
else: else:
#print "verifier: tx not in list", tx
conf = 0 conf = 0
if conf <= 0: if conf <= 0:
@ -65,6 +61,13 @@ class WalletVerifier(threading.Thread):
return conf, timestamp return conf, timestamp
def get_height(self, tx_hash):
with self.lock:
v = self.verified_tx.get(tx_hash)
height = v[0] if v else None
return height
def add(self, tx_hash, tx_height): def add(self, tx_hash, tx_height):
""" add a transaction to the list of monitored transactions. """ """ add a transaction to the list of monitored transactions. """
assert tx_height > 0 assert tx_height > 0
@ -145,6 +148,10 @@ class WalletVerifier(threading.Thread):
continue continue
if not r: continue if not r: continue
if r.get('error'):
print_error('Verifier received an error:', r)
continue
# 3. handle response # 3. handle response
method = r['method'] method = r['method']
params = r['params'] params = r['params']
@ -183,7 +190,8 @@ class WalletVerifier(threading.Thread):
# we passed all the tests # we passed all the tests
header = self.read_header(tx_height) header = self.read_header(tx_height)
timestamp = header.get('timestamp') timestamp = header.get('timestamp')
self.verified_tx[tx_hash] = (tx_height, timestamp) with self.lock:
self.verified_tx[tx_hash] = (tx_height, timestamp)
print_error("verified %s"%tx_hash) print_error("verified %s"%tx_hash)
self.config.set_key('verified_tx2', self.verified_tx, True) self.config.set_key('verified_tx2', self.verified_tx, True)
self.interface.trigger_callback('updated') self.interface.trigger_callback('updated')
@ -241,12 +249,16 @@ class WalletVerifier(threading.Thread):
# this can be caused by a reorg. # this can be caused by a reorg.
print_error("verify header failed"+ repr(header)) print_error("verify header failed"+ repr(header))
# undo verifications # undo verifications
for tx_hash, item in self.verified_tx.items(): with self.lock:
items = self.verified_tx.items()[:]
for tx_hash, item in items:
tx_height, timestamp = item tx_height, timestamp = item
if tx_height >= height: if tx_height >= height:
print_error("redoing", tx_hash) print_error("redoing", tx_hash)
self.verified_tx.pop(tx_hash) with self.lock:
if tx_hash in self.merkle_roots: self.merkle_roots.pop(tx_hash) self.verified_tx.pop(tx_hash)
if tx_hash in self.merkle_roots:
self.merkle_roots.pop(tx_hash)
# return False to request previous header. # return False to request previous header.
return False return False

View File

@ -33,9 +33,6 @@ import time
from util import print_msg, print_error, user_dir, format_satoshis from util import print_msg, print_error, user_dir, format_satoshis
from bitcoin import * from bitcoin import *
# URL decode
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
# AES encryption # AES encryption
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
@ -82,19 +79,16 @@ class Wallet:
self.use_encryption = config.get('use_encryption', False) self.use_encryption = config.get('use_encryption', False)
self.seed = config.get('seed', '') # encrypted self.seed = config.get('seed', '') # encrypted
self.labels = config.get('labels', {}) self.labels = config.get('labels', {})
self.aliases = config.get('aliases', {}) # aliases for addresses
self.authorities = config.get('authorities', {}) # trusted addresses
self.frozen_addresses = config.get('frozen_addresses',[]) self.frozen_addresses = config.get('frozen_addresses',[])
self.prioritized_addresses = config.get('prioritized_addresses',[]) self.prioritized_addresses = config.get('prioritized_addresses',[])
self.receipts = config.get('receipts',{}) # signed URIs
self.addressbook = config.get('contacts', []) self.addressbook = config.get('contacts', [])
self.imported_keys = config.get('imported_keys',{}) self.imported_keys = config.get('imported_keys',{})
self.history = config.get('addr_history',{}) # address -> list(txid, height) self.history = config.get('addr_history',{}) # address -> list(txid, height)
self.tx_height = config.get('tx_height',{})
self.accounts = config.get('accounts', {}) # this should not include public keys self.accounts = config.get('accounts', {}) # this should not include public keys
self.SequenceClass = ElectrumSequence
self.sequences = {} self.sequences = {}
self.sequences[0] = ElectrumSequence(self.config.get('master_public_key')) self.sequences[0] = self.SequenceClass(self.config.get('master_public_key'))
if self.accounts.get(0) is None: if self.accounts.get(0) is None:
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' } self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
@ -161,13 +155,13 @@ class Wallet:
self.seed = seed self.seed = seed
self.config.set_key('seed', self.seed, True) self.config.set_key('seed', self.seed, True)
self.config.set_key('seed_version', self.seed_version, True) self.config.set_key('seed_version', self.seed_version, True)
mpk = ElectrumSequence.mpk_from_seed(self.seed) mpk = self.SequenceClass.mpk_from_seed(self.seed)
self.init_sequence(mpk) self.init_sequence(mpk)
def init_sequence(self, mpk): def init_sequence(self, mpk):
self.config.set_key('master_public_key', mpk, True) self.config.set_key('master_public_key', mpk, True)
self.sequences[0] = ElectrumSequence(mpk) self.sequences[0] = self.SequenceClass(mpk)
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' } self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
self.config.set_key('accounts', self.accounts, True) self.config.set_key('accounts', self.accounts, True)
@ -184,8 +178,8 @@ class Wallet:
return address in self.addresses(True) return address in self.addresses(True)
def is_change(self, address): def is_change(self, address):
#return address in self.change_addresses acct, s = self.get_address_index(address)
return False return s[0] == 1
def get_master_public_key(self): def get_master_public_key(self):
return self.sequences[0].master_public_key return self.sequences[0].master_public_key
@ -302,6 +296,7 @@ class Wallet:
address = self.get_new_address( account, for_change, n) address = self.get_new_address( account, for_change, n)
self.accounts[account][for_change].append(address) self.accounts[account][for_change].append(address)
self.history[address] = [] self.history[address] = []
print_msg(address)
return address return address
@ -412,6 +407,12 @@ class Wallet:
# redo labels # redo labels
# self.update_tx_labels() # self.update_tx_labels()
def get_num_tx(self, address):
n = 0
for tx in self.transactions.values():
if address in map(lambda x:x[0], tx.outputs): n += 1
return n
def get_address_flags(self, addr): def get_address_flags(self, addr):
flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-"
@ -424,46 +425,6 @@ class Wallet:
return tx.get_value(addresses, self.prevout_values) return tx.get_value(addresses, self.prevout_values)
def get_tx_details(self, tx_hash):
import datetime
if not tx_hash: return ''
tx = self.transactions.get(tx_hash)
is_mine, v, fee = self.get_tx_value(tx)
conf, timestamp = self.verifier.get_confirmations(tx_hash)
if timestamp:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
else:
time_str = 'pending'
inputs = map(lambda x: x.get('address'), tx.inputs)
outputs = map(lambda x: x.get('address'), tx.d['outputs'])
tx_details = "Transaction Details" +"\n\n" \
+ "Transaction ID:\n" + tx_hash + "\n\n" \
+ "Status: %d confirmations\n"%conf
if is_mine:
if fee:
tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
+ "Transaction fee: %s\n"% format_satoshis(fee, False)
else:
tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
+ "Transaction fee: unknown\n"
else:
tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
tx_details += "Date: %s\n\n"%time_str \
+ "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
+ "Outputs:\n-"+ '\n-'.join(outputs)
r = self.receipts.get(tx_hash)
if r:
tx_details += "\n_______________________________________" \
+ '\n\nSigned URI: ' + r[2] \
+ "\n\nSigned by: " + r[0] \
+ '\n\nSignature: ' + r[1]
return tx_details
def update_tx_outputs(self, tx_hash): def update_tx_outputs(self, tx_hash):
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
@ -566,6 +527,7 @@ class Wallet:
if h == ['*']: continue if h == ['*']: continue
for tx_hash, tx_height in h: for tx_hash, tx_height in h:
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
if tx is None: raise BaseException("Wallet not synchronized")
for output in tx.d.get('outputs'): for output in tx.d.get('outputs'):
if output.get('address') != addr: continue if output.get('address') != addr: continue
key = tx_hash + ":%d" % output.get('index') key = tx_hash + ":%d" % output.get('index')
@ -640,11 +602,12 @@ class Wallet:
def receive_tx_callback(self, tx_hash, tx, tx_height): def receive_tx_callback(self, tx_hash, tx, tx_height):
if not self.check_new_tx(tx_hash, tx): if not self.check_new_tx(tx_hash, tx):
raise BaseException("error: received transaction is not consistent with history", tx_hash) # may happen due to pruning
print_error("received transaction that is no longer referenced in history", tx_hash)
return
with self.lock: with self.lock:
self.transactions[tx_hash] = tx self.transactions[tx_hash] = tx
self.tx_height[tx_hash] = tx_height
#tx_height = tx.get('height') #tx_height = tx.get('height')
if self.verifier and tx_height>0: if self.verifier and tx_height>0:
@ -669,17 +632,12 @@ class Wallet:
if tx_height>0: if tx_height>0:
# add it in case it was previously unconfirmed # add it in case it was previously unconfirmed
if self.verifier: self.verifier.add(tx_hash, tx_height) if self.verifier: self.verifier.add(tx_hash, tx_height)
# set the height in case it changed
txh = self.tx_height.get(tx_hash)
if txh is not None and txh != tx_height:
print_error( "changing height for tx", tx_hash )
self.tx_height[tx_hash] = tx_height
def get_tx_history(self): def get_tx_history(self):
with self.lock: with self.lock:
history = self.transactions.items() history = self.transactions.items()
history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12) history.sort(key = lambda x: self.verifier.get_height(x[0]) if self.verifier.get_height(x[0]) else 1e12)
result = [] result = []
balance = 0 balance = 0
@ -724,6 +682,9 @@ class Wallet:
default_label = self.labels[o_addr] default_label = self.labels[o_addr]
except KeyError: except KeyError:
default_label = o_addr default_label = o_addr
break
else:
default_label = '(internal)'
else: else:
for o in tx.outputs: for o in tx.outputs:
o_addr, _ = o o_addr, _ = o
@ -812,48 +773,6 @@ class Wallet:
return True, out return True, out
def read_alias(self, alias):
# this might not be the right place for this function.
import urllib
m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
if m1:
url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1)
elif m2:
url = 'https://' + alias + '/bitcoin.id'
else:
return ''
try:
lines = urllib.urlopen(url).readlines()
except:
return ''
# line 0
line = lines[0].strip().split(':')
if len(line) == 1:
auth_name = None
target = signing_addr = line[0]
else:
target, auth_name, signing_addr, signature = line
msg = "alias:%s:%s:%s"%(alias,target,auth_name)
print msg, signature
EC_KEY.verify_message(signing_addr, signature, msg)
# other lines are signed updates
for line in lines[1:]:
line = line.strip()
if not line: continue
line = line.split(':')
previous = target
print repr(line)
target, signature = line
EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
if not is_valid(target):
raise ValueError("Invalid bitcoin address")
return target, signing_addr, auth_name
def update_password(self, seed, old_password, new_password): def update_password(self, seed, old_password, new_password):
if new_password == '': new_password = None if new_password == '': new_password = None
@ -867,96 +786,6 @@ class Wallet:
self.imported_keys[k] = c self.imported_keys[k] = c
self.save() self.save()
def get_alias(self, alias, interactive = False, show_message=None, question = None):
try:
target, signing_address, auth_name = self.read_alias(alias)
except BaseException, e:
# raise exception if verify fails (verify the chain)
if interactive:
show_message("Alias error: " + str(e))
return
print target, signing_address, auth_name
if auth_name is None:
a = self.aliases.get(alias)
if not a:
msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
if interactive and question( msg ):
self.aliases[alias] = (signing_address, target)
else:
target = None
else:
if signing_address != a[0]:
msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
if interactive and question( msg ):
self.aliases[alias] = (signing_address, target)
else:
target = None
else:
if signing_address not in self.authorities.keys():
msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
if interactive and question( msg ):
self.authorities[signing_address] = auth_name
else:
target = None
if target:
self.aliases[alias] = (signing_address, target)
return target
def parse_url(self, url, show_message, question):
o = url[8:].split('?')
address = o[0]
if len(o)>1:
params = o[1].split('&')
else:
params = []
amount = label = message = signature = identity = ''
for p in params:
k,v = p.split('=')
uv = urldecode(v)
if k == 'amount': amount = uv
elif k == 'message': message = uv
elif k == 'label': label = uv
elif k == 'signature':
identity, signature = uv.split(':')
url = url.replace('&%s=%s'%(k,v),'')
else:
print k,v
if label and self.labels.get(address) != label:
if question('Give label "%s" to address %s ?'%(label,address)):
if address not in self.addressbook and not self.is_mine(address):
self.addressbook.append(address)
self.labels[address] = label
if signature:
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
signing_address = self.get_alias(identity, True, show_message, question)
elif is_valid(identity):
signing_address = identity
else:
signing_address = None
if not signing_address:
return
try:
EC_KEY.verify_message(signing_address, signature, url )
self.receipt = (signing_address, signature, url)
except:
show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
address = amount = label = identity = message = ''
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
payto_address = self.get_alias(address, True, show_message, question)
if payto_address:
address = address + ' <' + payto_address + '>'
return address, amount, label, message, signature, identity, url
def freeze(self,addr): def freeze(self,addr):
@ -1007,15 +836,11 @@ class Wallet:
'labels': self.labels, 'labels': self.labels,
'contacts': self.addressbook, 'contacts': self.addressbook,
'imported_keys': self.imported_keys, 'imported_keys': self.imported_keys,
'aliases': self.aliases,
'authorities': self.authorities,
'receipts': self.receipts,
'num_zeros': self.num_zeros, 'num_zeros': self.num_zeros,
'frozen_addresses': self.frozen_addresses, 'frozen_addresses': self.frozen_addresses,
'prioritized_addresses': self.prioritized_addresses, 'prioritized_addresses': self.prioritized_addresses,
'gap_limit': self.gap_limit, 'gap_limit': self.gap_limit,
'transactions': tx, 'transactions': tx,
'tx_height': self.tx_height,
} }
for k, v in s.items(): for k, v in s.items():
self.config.set_key(k,v) self.config.set_key(k,v)
@ -1024,17 +849,6 @@ class Wallet:
def set_verifier(self, verifier): def set_verifier(self, verifier):
self.verifier = verifier self.verifier = verifier
# review stored transactions and send them to the verifier
# (they are not necessarily in the history, because history items might have have been pruned)
for tx_hash, tx in self.transactions.items():
tx_height = self.tx_height[tx_hash]
if tx_height <1:
print_error( "skipping", tx_hash, tx_height )
continue
if tx_height>0:
self.verifier.add(tx_hash, tx_height)
# review transactions that are in the history # review transactions that are in the history
for addr, hist in self.history.items(): for addr, hist in self.history.items():
if hist == ['*']: continue if hist == ['*']: continue
@ -1042,11 +856,6 @@ class Wallet:
if tx_height>0: if tx_height>0:
# add it in case it was previously unconfirmed # add it in case it was previously unconfirmed
self.verifier.add(tx_hash, tx_height) self.verifier.add(tx_hash, tx_height)
# set the height in case it changed
txh = self.tx_height.get(tx_hash)
if txh is not None and txh != tx_height:
print_error( "changing height for tx", tx_hash )
self.tx_height[tx_hash] = tx_height
@ -1082,7 +891,7 @@ class Wallet:
if not tx: continue if not tx: continue
# already verified? # already verified?
if self.tx_height.get(tx_hash): if self.verifier.get_height(tx_hash):
continue continue
# unconfirmed tx # unconfirmed tx
print_error("new history is orphaning transaction:", tx_hash) print_error("new history is orphaning transaction:", tx_hash)
@ -1099,7 +908,6 @@ class Wallet:
for item in h: for item in h:
if item.get('tx_hash') == tx_hash: if item.get('tx_hash') == tx_hash:
height = item.get('height') height = item.get('height')
self.tx_height[tx_hash] = height
if height: if height:
print_error("found height for", tx_hash, height) print_error("found height for", tx_hash, height)
self.verifier.add(tx_hash, height) self.verifier.add(tx_hash, height)
@ -1155,22 +963,6 @@ class WalletSynchronizer(threading.Thread):
def is_running(self): def is_running(self):
with self.lock: return self.running with self.lock: return self.running
def synchronize_wallet(self):
new_addresses = self.wallet.synchronize()
if new_addresses:
self.subscribe_to_addresses(new_addresses)
self.wallet.up_to_date = False
return
if not self.interface.is_up_to_date('synchronizer'):
if self.wallet.is_up_to_date():
self.wallet.set_up_to_date(False)
self.was_updated = True
return
self.wallet.set_up_to_date(True)
self.was_updated = True
def subscribe_to_addresses(self, addresses): def subscribe_to_addresses(self, addresses):
messages = [] messages = []
@ -1202,15 +994,30 @@ class WalletSynchronizer(threading.Thread):
self.subscribe_to_addresses(self.wallet.addresses(True)) self.subscribe_to_addresses(self.wallet.addresses(True))
while self.is_running(): while self.is_running():
# 1. send new requests # 1. create new addresses
self.synchronize_wallet() new_addresses = self.wallet.synchronize()
# request missing addresses
if new_addresses:
self.subscribe_to_addresses(new_addresses)
# request missing transactions
for tx_hash, tx_height in missing_tx: for tx_hash, tx_height in missing_tx:
if (tx_hash, tx_height) not in requested_tx: if (tx_hash, tx_height) not in requested_tx:
self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer') self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
requested_tx.append( (tx_hash, tx_height) ) requested_tx.append( (tx_hash, tx_height) )
missing_tx = [] missing_tx = []
# detect if situation has changed
if not self.interface.is_up_to_date('synchronizer'):
if self.wallet.is_up_to_date():
self.wallet.set_up_to_date(False)
self.was_updated = True
else:
if not self.wallet.is_up_to_date():
self.wallet.set_up_to_date(True)
self.was_updated = True
if self.was_updated: if self.was_updated:
self.interface.trigger_callback('updated') self.interface.trigger_callback('updated')
self.was_updated = False self.was_updated = False

View File

@ -10,7 +10,7 @@ if __name__ == '__main__':
sys.exit() sys.exit()
os.system("python mki18n.py") os.system("python mki18n.py")
os.system("pyrcc4 icons.qrc -o lib/icons_rc.py") os.system("pyrcc4 icons.qrc -o gui/icons_rc.py")
os.system("python setup.py sdist --format=zip,gztar") os.system("python setup.py sdist --format=zip,gztar")
_tgz="Electrum-%s.tar.gz"%version _tgz="Electrum-%s.tar.gz"%version

199
plugins/aliases.py Normal file
View File

@ -0,0 +1,199 @@
import re
import platform
from decimal import Decimal
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
from electrum_gui.qrcodewidget import QRCodeWidget
from electrum_gui import bmp, pyqrnative
from electrum_gui.i18n import _
ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
from electrum_gui import BasePlugin
class Plugin(BasePlugin):
def __init__(self, gui):
BasePlugin.__init__(self, gui, 'aliases', 'Aliases', _('Retrieve aliases using http.'))
self.aliases = self.config.get('aliases', {}) # aliases for addresses
self.authorities = self.config.get('authorities', {}) # trusted addresses
self.receipts = self.config.get('receipts',{}) # signed URIs
def timer_actions(self):
if self.gui.payto_e.hasFocus():
return
r = unicode( self.gui.payto_e.text() )
if r != self.gui.previous_payto_e:
self.gui.previous_payto_e = r
r = r.strip()
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
try:
to_address = self.get_alias(r, True, self.gui.show_message, self.gui.question)
except:
return
if to_address:
s = r + ' <' + to_address + '>'
self.gui.payto_e.setText(s)
def get_alias(self, alias, interactive = False, show_message=None, question = None):
try:
target, signing_address, auth_name = read_alias(self, alias)
except BaseException, e:
# raise exception if verify fails (verify the chain)
if interactive:
show_message("Alias error: " + str(e))
return
print target, signing_address, auth_name
if auth_name is None:
a = self.aliases.get(alias)
if not a:
msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
if interactive and question( msg ):
self.aliases[alias] = (signing_address, target)
else:
target = None
else:
if signing_address != a[0]:
msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
if interactive and question( msg ):
self.aliases[alias] = (signing_address, target)
else:
target = None
else:
if signing_address not in self.authorities.keys():
msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
if interactive and question( msg ):
self.authorities[signing_address] = auth_name
else:
target = None
if target:
self.aliases[alias] = (signing_address, target)
return target
def read_alias(self, alias):
import urllib
m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
if m1:
url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1)
elif m2:
url = 'https://' + alias + '/bitcoin.id'
else:
return ''
try:
lines = urllib.urlopen(url).readlines()
except:
return ''
# line 0
line = lines[0].strip().split(':')
if len(line) == 1:
auth_name = None
target = signing_addr = line[0]
else:
target, auth_name, signing_addr, signature = line
msg = "alias:%s:%s:%s"%(alias,target,auth_name)
print msg, signature
EC_KEY.verify_message(signing_addr, signature, msg)
# other lines are signed updates
for line in lines[1:]:
line = line.strip()
if not line: continue
line = line.split(':')
previous = target
print repr(line)
target, signature = line
EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
if not is_valid(target):
raise ValueError("Invalid bitcoin address")
return target, signing_addr, auth_name
def set_url(self, url, show_message, question):
payto, amount, label, message, signature, identity, url = util.parse_url(url)
if signature:
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
signing_address = get_alias(identity, True, show_message, question)
elif is_valid(identity):
signing_address = identity
else:
signing_address = None
if not signing_address:
return
try:
EC_KEY.verify_message(signing_address, signature, url )
self.receipt = (signing_address, signature, url)
except:
show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
address = amount = label = identity = message = ''
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
payto_address = get_alias(address, True, show_message, question)
if payto_address:
address = address + ' <' + payto_address + '>'
return address, amount, label, message, signature, identity, url
def update_contacts_tab(self, l):
alias_targets = []
for alias, v in self.aliases.items():
s, target = v
alias_targets.append(target)
item = QTreeWidgetItem( [ target, alias, '-'] )
item.setBackgroundColor(0, QColor('lightgray'))
item.setData(0,32,False)
item.setData(0,33,alias + ' <' + target + '>')
l.insertTopLevelItem(0,item)
def update_completions(self, l):
l[:] = l + self.aliases.keys()
def create_contact_menu(self, menu, item):
label = unicode(item.text(1))
if label in self.aliases.keys():
addr = unicode(item.text(0))
label = unicode(item.text(1))
menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
def show_contact_details(self, m):
a = self.aliases.get(m)
if a:
if a[0] in self.authorities.keys():
s = self.authorities.get(a[0])
else:
s = "self-signed"
msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
QMessageBox.information(self.gui, 'Alias', msg, 'OK')
def delete_alias(self, x):
if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
if x in self.aliases:
self.aliases.pop(x)
self.update_history_tab()
self.update_contacts_tab()
self.update_completions()

View File

@ -8,8 +8,7 @@ import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui import PyQt4.QtGui as QtGui
from electrum_gui.qrcodewidget import QRCodeWidget from electrum_gui.qrcodewidget import QRCodeWidget
from electrum_gui import bmp, pyqrnative from electrum_gui import bmp, pyqrnative, BasePlugin
from electrum_gui.i18n import _ from electrum_gui.i18n import _
@ -89,98 +88,95 @@ class QR_Window(QWidget):
self.qrw.set_addr( msg ) self.qrw.set_addr( msg )
config = {}
def get_info():
return 'Point of Sale', _('Show QR code window and amounts requested for each address. Add menu item to request amount.')
def init(gui):
global config
config = gui.config
gui.requested_amounts = config.get('requested_amounts',{})
gui.merchant_name = config.get('merchant_name', 'Invoice')
gui.qr_window = None
do_enable(gui, is_enabled())
def is_enabled():
return config.get('pointofsale') is True
def is_available():
return True
def toggle(gui):
enabled = not is_enabled()
config.set_key('pointofsale', enabled, True)
do_enable(gui, enabled)
update_gui(gui)
return enabled
def do_enable(gui, enabled):
if enabled:
gui.expert_mode = True
gui.set_hook('item_changed', item_changed)
gui.set_hook('current_item_changed', recv_changed)
gui.set_hook('receive_menu', receive_menu)
gui.set_hook('update_receive_item', update_receive_item)
gui.set_hook('timer_actions', timer_actions)
gui.set_hook('close_main_window', close_main_window)
gui.set_hook('init', update_gui)
else:
gui.unset_hook('item_changed', item_changed)
gui.unset_hook('current_item_changed', recv_changed)
gui.unset_hook('receive_menu', receive_menu)
gui.unset_hook('update_receive_item', update_receive_item)
gui.unset_hook('timer_actions', timer_actions)
gui.unset_hook('close_main_window', close_main_window)
gui.unset_hook('init', update_gui)
def update_gui(gui): class Plugin(BasePlugin):
enabled = is_enabled()
if enabled:
gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')])
else:
gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')])
toggle_QR_window(gui, enabled) def __init__(self, gui):
BasePlugin.__init__(self, gui, 'pointofsale', 'Point of Sale',
_('Show QR code window and amounts requested for each address. Add menu item to request amount.') )
self.qr_window = None
self.requested_amounts = self.config.get('requested_amounts',{})
self.merchant_name = self.config.get('merchant_name', 'Invoice')
def init_gui(self):
enabled = self.is_enabled()
if enabled:
self.gui.expert_mode = True
self.gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')])
else:
self.gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')])
self.toggle_QR_window(enabled)
def close_main_window(self):
if self.qr_window:
self.qr_window.close()
self.qr_window = None
def toggle_QR_window(self, show):
if show and not self.qr_window: def timer_actions(self):
self.qr_window = QR_Window(self.exchanger) if self.qr_window:
self.qr_window.setVisible(True) self.qr_window.qrw.update_qr()
self.qr_window_geometry = self.qr_window.geometry()
item = self.receive_list.currentItem()
if item: def toggle_QR_window(self, show):
address = str(item.text(1)) if show and not self.qr_window:
label = self.wallet.labels.get(address) self.qr_window = QR_Window(self.gui.exchanger)
self.qr_window.setVisible(True)
self.qr_window_geometry = self.qr_window.geometry()
item = self.gui.receive_list.currentItem()
if item:
address = str(item.text(1))
label = self.gui.wallet.labels.get(address)
amount, currency = self.requested_amounts.get(address, (None, None))
self.qr_window.set_content( address, label, amount, currency )
elif show and self.qr_window and not self.qr_window.isVisible():
self.qr_window.setVisible(True)
self.qr_window.setGeometry(self.qr_window_geometry)
elif not show and self.qr_window and self.qr_window.isVisible():
self.qr_window_geometry = self.qr_window.geometry()
self.qr_window.setVisible(False)
def update_receive_item(self, address, item):
try:
amount, currency = self.requested_amounts.get(address, (None, None)) amount, currency = self.requested_amounts.get(address, (None, None))
except:
print "cannot get requested amount", address, self.requested_amounts.get(address)
amount, currency = None, None
self.requested_amounts.pop(address)
amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
item.setData(column_index,0,amount_str)
def current_item_changed(self, a):
if a is not None and self.qr_window and self.qr_window.isVisible():
address = str(a.text(0))
label = self.gui.wallet.labels.get(address)
try:
amount, currency = self.requested_amounts.get(address, (None, None))
except:
amount, currency = None, None
self.qr_window.set_content( address, label, amount, currency ) self.qr_window.set_content( address, label, amount, currency )
elif show and self.qr_window and not self.qr_window.isVisible():
self.qr_window.setVisible(True)
self.qr_window.setGeometry(self.qr_window_geometry)
elif not show and self.qr_window and self.qr_window.isVisible():
self.qr_window_geometry = self.qr_window.geometry() def item_changed(self, item, column):
self.qr_window.setVisible(False) if column != column_index:
return
def item_changed(self, item, column):
if column == column_index:
address = str( item.text(0) ) address = str( item.text(0) )
text = str( item.text(column) ) text = str( item.text(column) )
try: try:
seq = self.wallet.get_address_index(address) seq = self.gui.wallet.get_address_index(address)
index = seq[-1] index = seq[-1]
except: except:
print "cannot get index" print "cannot get index"
@ -198,9 +194,9 @@ def item_changed(self, item, column):
currency = currency.upper() currency = currency.upper()
self.requested_amounts[address] = (amount, currency) self.requested_amounts[address] = (amount, currency)
self.wallet.config.set_key('requested_amounts', self.requested_amounts, True) self.gui.wallet.config.set_key('requested_amounts', self.requested_amounts, True)
label = self.wallet.labels.get(address) label = self.gui.wallet.labels.get(address)
if label is None: if label is None:
label = self.merchant_name + ' - %04d'%(index+1) label = self.merchant_name + ' - %04d'%(index+1)
self.wallet.labels[address] = label self.wallet.labels[address] = label
@ -213,50 +209,20 @@ def item_changed(self, item, column):
if address in self.requested_amounts: if address in self.requested_amounts:
self.requested_amounts.pop(address) self.requested_amounts.pop(address)
self.update_receive_item(self.receive_list.currentItem()) self.gui.update_receive_item(self.gui.receive_list.currentItem())
def recv_changed(self, a):
if a is not None and self.qr_window and self.qr_window.isVisible():
address = str(a.text(0))
label = self.wallet.labels.get(address)
try:
amount, currency = self.requested_amounts.get(address, (None, None))
except:
amount, currency = None, None
self.qr_window.set_content( address, label, amount, currency )
def edit_amount(self):
l = self.receive_list
item = l.currentItem()
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
l.editItem( item, column_index )
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
def receive_menu(self, menu): def edit_amount(self):
menu.addAction(_("Request amount"), lambda: edit_amount(self)) l = self.gui.receive_list
item = l.currentItem()
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
l.editItem( item, column_index )
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
def receive_menu(self, menu):
menu.addAction(_("Request amount"), self.edit_amount)
def update_receive_item(self, address, item):
try:
amount, currency = self.requested_amounts.get(address, (None, None))
except:
print "cannot get requested amount", address, self.requested_amounts.get(address)
amount, currency = None, None
self.requested_amounts.pop(address)
amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
item.setData(column_index,0,amount_str)
def close_main_window(self):
if self.qr_window:
self.qr_window.close()
self.qr_window = None
def timer_actions(self):
if self.qr_window:
self.qr_window.qrw.update_qr()

View File

@ -1,60 +1,69 @@
from electrum.util import print_error from electrum.util import print_error
from urlparse import urlparse, parse_qs from urlparse import urlparse, parse_qs
from PyQt4.QtGui import QPushButton
from electrum_gui.i18n import _
try: try:
import zbar import zbar
except ImportError: except ImportError:
zbar = None zbar = None
from electrum_gui import BasePlugin
class Plugin(BasePlugin):
def __init__(self, gui):
BasePlugin.__init__(self, gui, 'qrscans', 'QR scans', "QR Scans.\nInstall the zbar package to enable this plugin")
def is_available(self):
if not zbar:
return False
try:
proc = zbar.Processor()
proc.init()
except zbar.SystemError:
# Cannot open video device
return False
return True
def init(gui): def create_send_tab(self, grid):
if is_enabled(): b = QPushButton(_("Scan QR code"))
gui.set_hook('create_send_tab', create_send_tab) b.clicked.connect(self.fill_from_qr)
else: grid.addWidget(b, 1, 5)
gui.unset_hook('create_send_tab', create_send_tab)
def get_info():
return 'QR scans', "QR Scans.\nInstall the zbar package to enable this plugin"
def is_enabled():
return is_available()
def toggle(gui):
return is_enabled()
def is_available(): def scan_qr(self):
if not zbar:
return False
try:
proc = zbar.Processor() proc = zbar.Processor()
proc.init() proc.init()
except zbar.SystemError: proc.visible = True
# Cannot open video device
return False
return True while True:
try:
proc.process_one()
except:
# User closed the preview window
return {}
def scan_qr(): for r in proc.results:
proc = zbar.Processor() if str(r.type) != 'QRCODE':
proc.init() continue
proc.visible = True return parse_uri(r.data)
while True:
try:
proc.process_one()
except:
# User closed the preview window
return {}
for r in proc.results:
if str(r.type) != 'QRCODE':
continue
return parse_uri(r.data)
def fill_from_qr(self):
qrcode = self.scan_qr()
if 'address' in qrcode:
self.gui.payto_e.setText(qrcode['address'])
if 'amount' in qrcode:
self.gui.amount_e.setText(str(qrcode['amount']))
if 'label' in qrcode:
self.gui.message_e.setText(qrcode['label'])
if 'message' in qrcode:
self.gui.message_e.setText("%s (%s)" % (self.gui.message_e.text(), qrcode['message']))
def parse_uri(uri): def parse_uri(uri):
if ':' not in uri: if ':' not in uri:
# It's just an address (not BIP21) # It's just an address (not BIP21)
@ -80,24 +89,6 @@ def parse_uri(uri):
def fill_from_qr(self):
qrcode = qrscanner.scan_qr()
if 'address' in qrcode:
self.payto_e.setText(qrcode['address'])
if 'amount' in qrcode:
self.amount_e.setText(str(qrcode['amount']))
if 'label' in qrcode:
self.message_e.setText(qrcode['label'])
if 'message' in qrcode:
self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
def create_send_tab(gui, grid):
if qrscanner.is_available():
b = QPushButton(_("Scan QR code"))
b.clicked.connect(lambda: fill_from_qr(gui))
grid.addWidget(b, 1, 5)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1,65 @@
from PyQt4.QtGui import *
from electrum_gui import BasePlugin
from electrum_gui.i18n import _
class Plugin(BasePlugin):
def __init__(self, gui):
BasePlugin.__init__(self, gui, 'virtualkeyboard', 'Virtual Keyboard',
_("Add an optional, mouse keyboard to the password dialog.\nWarning: do not use this if it makes you pick a weaker password."))
self.vkb = None
self.vkb_index = 0
def password_dialog(self, pw, grid, pos):
vkb_button = QPushButton(_("+"))
vkb_button.setFixedWidth(20)
vkb_button.clicked.connect(lambda: self.toggle_vkb(grid, pw))
grid.addWidget(vkb_button, pos, 2)
self.kb_pos = 2
def toggle_vkb(self, grid, pw):
if self.vkb: grid.removeItem(self.vkb)
self.vkb = self.virtual_keyboard(self.vkb_index, pw)
grid.addLayout(self.vkb, self.kb_pos, 0, 1, 3)
self.vkb_index += 1
def virtual_keyboard(self, i, pw):
import random
i = i%3
if i == 0:
chars = 'abcdefghijklmnopqrstuvwxyz '
elif i == 1:
chars = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ '
elif i == 2:
chars = '1234567890!?.,;:/%&()[]{}+-'
n = len(chars)
s = []
for i in xrange(n):
while True:
k = random.randint(0,n-1)
if k not in s:
s.append(k)
break
def add_target(t):
return lambda: pw.setText(str( pw.text() ) + t)
vbox = QVBoxLayout()
grid = QGridLayout()
grid.setSpacing(2)
for i in range(n):
l_button = QPushButton(chars[s[i]])
l_button.setFixedWidth(25)
l_button.setFixedHeight(25)
l_button.clicked.connect(add_target(chars[s[i]]) )
grid.addWidget(l_button, i/6, i%6)
vbox.addLayout(grid)
return vbox

View File

@ -28,6 +28,8 @@ if sys.platform == 'darwin':
setup_requires=['py2app'], setup_requires=['py2app'],
app=[mainscript], app=[mainscript],
options=dict(py2app=dict(argv_emulation=True, options=dict(py2app=dict(argv_emulation=True,
includes = ['PyQt4.QtCore','PyQt4.QtGui', 'sip'],
packages = ['lib', 'gui', 'plugins'],
iconfile='electrum.icns', iconfile='electrum.icns',
resources=["data", "icons"])), resources=["data", "icons"])),
) )