Compare commits

...

40 Commits

Author SHA1 Message Date
ThomasV
82e88cb89d prepare release 3.0.6 2018-02-03 07:38:36 +01:00
SomberNight
d76f36df70 ledger: mention "bitcoin" app when update is needed 2018-02-02 02:41:41 +01:00
SomberNight
cf82b1d9d5 follow-up 70aa1f1db9 2018-01-31 07:05:29 +01:00
SomberNight
29206852fb follow-up prev commit. better handling of p2sh-segwit. added comment to describe the problem for native segwit. 2018-01-31 07:05:13 +01:00
Neil Booth
f0942a2535 Fix PNG file
Avoids libpng warning: iCCP: known incorrect sRGB profile
2018-01-31 07:04:57 +01:00
SomberNight
7b8b75c7d3 fix #3788
# Conflicts:
#	lib/transaction.py
2018-01-31 07:04:16 +01:00
SomberNight
b0966671d7 fix #3790 2018-01-31 07:02:46 +01:00
SomberNight
64fa7dd6c3 fix #3783 2018-01-31 07:02:31 +01:00
Johann Bauer
d0433c1539 Change number of zero when base unit changes 2018-01-29 20:57:23 +01:00
SomberNight
ff38e90405 revert adding handling of 'blockchain.address.subscribe' in network.py 2018-01-29 20:57:11 +01:00
SomberNight
c79662fbce websocket: migrate to scripthashes 2018-01-29 20:56:59 +01:00
racquemis
d5e20d607e Handle invalid PIN on exporting private key
Prevent Android App from crashing when a wrong PIN is entered.
2018-01-29 20:56:46 +01:00
SomberNight
53fc343b4a setconfig rpcpassword: don't try to evaluate or reencode 2018-01-29 20:56:30 +01:00
SomberNight
70d6f50d94 fix-up stdio gui 2018-01-29 20:56:16 +01:00
ThomasV
1c4773d41a fix #3619: set correct parent window 2018-01-29 20:56:03 +01:00
SomberNight
7cfa3c2d51 ledger: handle pin-locked state better 2018-01-29 20:55:49 +01:00
Marcel O'Neil
47f57af145 fix crash when exporting private keys 2018-01-29 20:55:35 +01:00
SomberNight
7279fc8902 qt privkey export: if multisig wallet, warn users re backup 2018-01-29 20:54:10 +01:00
SomberNight
87737dbe45 fix #3663: 'copying' QR code does not save to file 2018-01-29 20:53:47 +01:00
SomberNight
231f4931d6 avoid shorter seeds 'by luck' 2018-01-29 20:53:19 +01:00
ThomasV
a9973ce6ab version 3.0.5 2018-01-08 00:50:15 +01:00
SomberNight
1fc45132c0 Password-protect the JSON RPC interface 2018-01-08 00:29:57 +01:00
ThomasV
e65353c062 disable jsonrpc commands in the GUI 2018-01-07 23:53:25 +01:00
ThomasV
0045784a58 disable jsonrpc on android 2018-01-07 16:13:56 +01:00
ThomasV
79d402d3f9 update hw wallet versions 2018-01-07 15:13:59 +01:00
ThomasV
063ec0a758 release 3.0.4 2018-01-06 22:45:41 +01:00
Johann Bauer
d4f1445914 Bundle QR scanner with Android app 2018-01-06 22:44:37 +01:00
SomberNight
498a269c88 fix #3411 2018-01-06 22:44:12 +01:00
Andrew Chow
b7c20e71ac Avoid modifying self.transactions in prepare_for_verifier
In python3, the `.keys()` function returns an iterator, not a list,
so to get a list that can be iterated over, use `list()` instead to
avoid modification of a list while in use.
2018-01-06 22:44:04 +01:00
SomberNight
a6e59499db fix #3217: make sure qt quits 2018-01-06 22:43:56 +01:00
SomberNight
d3a963e673 fix kivy refresh bug with Addresses tab 2018-01-06 22:43:49 +01:00
SomberNight
e4308a360b fix #3601 2018-01-06 22:43:09 +01:00
SomberNight
e98406fc7c fix: kivy Addresses tab crash for Imported_Wallet change 2018-01-06 22:43:04 +01:00
SomberNight
dfaf4817c9 fix #3578 2018-01-06 22:42:55 +01:00
SomberNight
0f54051ecb fix: address filters for Imported_Wallet: wallet.is_used() 2018-01-06 22:42:46 +01:00
SomberNight
02fda5a85b fix: crash when closing qrscanner window (see #3546) 2018-01-06 22:42:37 +01:00
Charles Bell
d9925967b7 Fix segfault when using a specific camera device 2018-01-06 22:42:29 +01:00
SomberNight
006aece3a3 fix #3526 2018-01-06 22:42:22 +01:00
ThomasV
c7a47a06b5 format a few strings with str.format(). fix #3405 2018-01-06 22:42:15 +01:00
Tristan Seligmann
fdd10bfb60 Stop allowing CORS for the JSON-RPC server
As far as I can tell, there is no need to allow this, and doing so poses severe security risks (see #3374).
2018-01-06 22:41:58 +01:00
33 changed files with 462 additions and 154 deletions

View File

@ -1,3 +1,28 @@
# Release 3.0.6 :
* Fix transaction parsing bug #3788
# Release 3.0.5 : (Security update)
This is a follow-up to the 3.0.4 release, which did not completely fix
issue #3374. Users should upgrade to 3.0.5.
* The JSONRPC interface is password protected
* JSONRPC commands are disabled if the GUI is running, except 'ping',
which is used to determine if a GUI is already running
# Release 3.0.4 : (Security update)
* Fix a vulnerability caused by Cross-Origin Resource Sharing (CORS)
in the JSONRPC interface. Previous versions of Electrum are
vulnerable to port scanning and deanonimization attacks from
malicious websites. Wallets that are not password-protected are
vulnerable to theft.
* Bundle QR scanner with Android app
* Minor bug fixes
# Release 3.0.3
* Qt GUI: sweeping now uses the Send tab, allowing fees to be set
* Windows: if using the installer binary, there is now a separate shortcut

View File

@ -43,6 +43,7 @@ done
popd
pushd electrum
git checkout $BRANCH
VERSION=`git describe --tags`
echo "Last commit: $VERSION"
find -exec touch -d '2000-11-11T11:11:11+00:00' {} +

View File

@ -23,6 +23,6 @@ cd tmp
$PYTHON -m pip install setuptools --upgrade
$PYTHON -m pip install cython --upgrade
$PYTHON -m pip install trezor==0.7.16 --upgrade
$PYTHON -m pip install keepkey==4.0.0 --upgrade
$PYTHON -m pip install btchip-python==0.1.23 --upgrade
$PYTHON -m pip install keepkey==4.0.2 --upgrade
$PYTHON -m pip install btchip-python==0.1.24 --upgrade

View File

@ -278,7 +278,8 @@ def run_offline_command(config, config_options):
# arguments passed to function
args = [config.get(x) for x in cmd.params]
# decode json arguments
args = list(map(json_decode, args))
if cmdname not in ('setconfig',):
args = list(map(json_decode, args))
# options
kwargs = {}
for x in cmd.options:
@ -372,7 +373,7 @@ if __name__ == '__main__':
fd, server = daemon.get_fd_or_server(config)
if fd is not None:
plugins = init_plugins(config, config.get('gui', 'qt'))
d = daemon.Daemon(config, fd)
d = daemon.Daemon(config, fd, True)
d.start()
d.init_gui(config, plugins)
sys.exit(0)
@ -393,7 +394,7 @@ if __name__ == '__main__':
print_stderr("starting daemon (PID %d)" % pid)
sys.exit(0)
init_plugins(config, 'cmdline')
d = daemon.Daemon(config, fd)
d = daemon.Daemon(config, fd, False)
d.start()
if config.get('websocket_server'):
from electrum import websockets
@ -425,7 +426,6 @@ if __name__ == '__main__':
else:
init_plugins(config, 'cmdline')
result = run_offline_command(config, config_options)
# print result
if isinstance(result, str):
print_msg(result)

View File

@ -0,0 +1,48 @@
package org.electrum.qr;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.content.Intent;
import java.util.Arrays;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
import com.google.zxing.Result;
import com.google.zxing.BarcodeFormat;
public class SimpleScannerActivity extends Activity implements ZXingScannerView.ResultHandler {
private ZXingScannerView mScannerView;
final String TAG = "org.electrum.SimpleScannerActivity";
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
mScannerView = new ZXingScannerView(this); // Programmatically initialize the scanner view
mScannerView.setFormats(Arrays.asList(BarcodeFormat.QR_CODE));
setContentView(mScannerView); // Set the scanner view as the content view
}
@Override
public void onResume() {
super.onResume();
mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
mScannerView.startCamera(); // Start camera on resume
}
@Override
public void onPause() {
super.onPause();
mScannerView.stopCamera(); // Stop camera on pause
}
@Override
public void handleResult(Result rawResult) {
Intent resultIntent = new Intent();
resultIntent.putExtra("text", rawResult.getText());
resultIntent.putExtra("format", rawResult.getBarcodeFormat().toString());
setResult(Activity.RESULT_OK, resultIntent);
this.finish();
}
}

View File

@ -325,7 +325,7 @@ class ElectrumWindow(App):
@profiler
def update_tabs(self):
for tab in ['invoices', 'send', 'history', 'receive', 'requests']:
for tab in ['invoices', 'send', 'history', 'receive', 'address']:
self.update_tab(tab)
def switch_to(self, name):
@ -384,45 +384,22 @@ class ElectrumWindow(App):
def scan_qr(self, on_complete):
if platform != 'android':
return
from jnius import autoclass
from jnius import autoclass, cast
from android import activity
PythonActivity = autoclass('org.kivy.android.PythonActivity')
SimpleScannerActivity = autoclass("org.electrum.qr.SimpleScannerActivity")
Intent = autoclass('android.content.Intent')
intent = Intent("com.google.zxing.client.android.SCAN")
intent.putExtra("SCAN_MODE", "QR_CODE_MODE")
def on_qr_result(requestCode, resultCode, intent):
if requestCode == 0:
if resultCode == -1: # RESULT_OK:
contents = intent.getStringExtra("SCAN_RESULT")
if intent.getStringExtra("SCAN_RESULT_FORMAT") == 'QR_CODE':
on_complete(contents)
else:
self.show_error("wrong format " + intent.getStringExtra("SCAN_RESULT_FORMAT"))
activity.bind(on_activity_result=on_qr_result)
try:
PythonActivity.mActivity.startActivityForResult(intent, 0)
except:
self.show_error(_('Could not start Barcode Scanner.') + ' ' + _('Please install the Barcode Scanner app from ZXing'))
intent = Intent(PythonActivity.mActivity, SimpleScannerActivity)
def scan_qr_zxing(self, on_complete):
# uses zxing embedded lib
if platform != 'android':
return
from jnius import autoclass
from android import activity
PythonActivity = autoclass('org.kivy.android.PythonActivity')
IntentIntegrator = autoclass('com.google.zxing.integration.android.IntentIntegrator')
integrator = IntentIntegrator(PythonActivity.mActivity)
def on_qr_result(requestCode, resultCode, intent):
if requestCode == 0:
if resultCode == -1: # RESULT_OK:
contents = intent.getStringExtra("SCAN_RESULT")
if intent.getStringExtra("SCAN_RESULT_FORMAT") == 'QR_CODE':
on_complete(contents)
else:
self.show_error("wrong format " + intent.getStringExtra("SCAN_RESULT_FORMAT"))
if resultCode == -1: # RESULT_OK:
# this doesn't work due to some bug in jnius:
# contents = intent.getStringExtra("text")
String = autoclass("java.lang.String")
contents = intent.getStringExtra(String("text"))
on_complete(contents)
activity.bind(on_activity_result=on_qr_result)
integrator.initiateScan()
PythonActivity.mActivity.startActivityForResult(intent, 0)
def do_share(self, data, title):
if platform != 'android':
@ -943,9 +920,18 @@ class ElectrumWindow(App):
self._password_dialog.open()
def export_private_keys(self, pk_label, addr):
if self.wallet.is_watching_only():
self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))
return
def show_private_key(addr, pk_label, password):
if self.wallet.has_password() and password is None:
return
key = str(self.wallet.export_private_key(addr, password)[0])
pk_label.data = key
if not self.wallet.can_export():
return
try:
key = str(self.wallet.export_private_key(addr, password)[0])
pk_label.data = key
except InvalidPassword:
self.show_error("Invalid PIN")
return
self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label))

View File

@ -52,7 +52,8 @@ fullscreen = False
#
# (list) Permissions
android.permissions = INTERNET, WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE
android.permissions = INTERNET, WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE, CAMERA
# (int) Android API to use
#android.api = 14
@ -86,7 +87,11 @@ android.ndk_path = /opt/crystax-ndk-10.3.2
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
android.add_src = gui/kivy/data/java-classes/
android.gradle_dependencies = me.dm7.barcodescanner:zxing:1.9.8
android.add_activities = org.electrum.qr.SimpleScannerActivity
# (str) python-for-android branch to use, if not master, useful to try
# not yet merged features.

View File

@ -521,7 +521,7 @@ class AddressScreen(CScreen):
def update(self):
self.menu_actions = [('Receive', self.do_show), ('Details', self.do_view)]
wallet = self.app.wallet
_list = wallet.change_addresses if self.screen.show_change else wallet.receiving_addresses
_list = wallet.get_change_addresses() if self.screen.show_change else wallet.get_receiving_addresses()
search = self.screen.message
container = self.screen.ids.search_container
container.clear_widgets()

View File

@ -240,6 +240,7 @@ class ElectrumGui:
except GoBack:
return
except:
import traceback
traceback.print_exc(file=sys.stdout)
return
self.timer.start()
@ -248,11 +249,23 @@ class ElectrumGui:
if not self.start_new_window(path, self.config.get('url')):
return
signal.signal(signal.SIGINT, lambda *args: self.app.quit())
def quit_after_last_window():
# on some platforms, not only does exec_ not return but not even
# aboutToQuit is emitted (but following this, it should be emitted)
if self.app.quitOnLastWindowClosed():
self.app.quit()
self.app.lastWindowClosed.connect(quit_after_last_window)
def clean_up():
# Shut down the timer cleanly
self.timer.stop()
# clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
event = QtCore.QEvent(QtCore.QEvent.Clipboard)
self.app.sendEvent(self.app.clipboard(), event)
self.tray.hide()
self.app.aboutToQuit.connect(clean_up)
# main loop
self.app.exec_()
# Shut down the timer cleanly
self.timer.stop()
# clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
event = QtCore.QEvent(QtCore.QEvent.Clipboard)
self.app.sendEvent(self.app.clipboard(), event)
self.tray.hide()
# on some platforms the exec_ call may not return, so use clean_up()

View File

@ -93,6 +93,6 @@ class AddressDialog(WindowModalDialog):
def show_qr(self):
text = self.address
try:
self.parent.show_qrcode(text, 'Address')
self.parent.show_qrcode(text, 'Address', parent=self)
except Exception as e:
self.show_message(str(e))

View File

@ -527,10 +527,10 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
grid.addWidget(m_label, 1, 0)
grid.addWidget(m_edit, 1, 1)
def on_m(m):
m_label.setText(_('Require %d signatures')%m)
m_label.setText(_('Require {0} signatures').format(m))
cw.set_m(m)
def on_n(n):
n_label.setText(_('From %d cosigners')%n)
n_label.setText(_('From {0} cosigners').format(n))
cw.set_n(n)
m_edit.setMaximum(n)
n_edit.valueChanged.connect(on_n)

View File

@ -2164,6 +2164,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_message(_("This is a watching-only wallet"))
return
if isinstance(self.wallet, Multisig_Wallet):
self.show_message(_('WARNING: This is a multi-signature wallet.') + '\n' +
_('It can not be "backed up" by simply exporting these private keys.'))
d = WindowModalDialog(self, _('Private keys'))
d.setMinimumSize(850, 300)
vbox = QVBoxLayout(d)
@ -2189,25 +2193,38 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
private_keys = {}
addresses = self.wallet.get_addresses()
done = False
cancelled = False
def privkeys_thread():
for addr in addresses:
time.sleep(0.1)
if done:
if done or cancelled:
break
privkey = self.wallet.export_private_key(addr, password)[0]
private_keys[addr] = privkey
self.computing_privkeys_signal.emit()
self.computing_privkeys_signal.disconnect()
self.show_privkeys_signal.emit()
if not cancelled:
self.computing_privkeys_signal.disconnect()
self.show_privkeys_signal.emit()
def show_privkeys():
s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
e.setText(s)
b.setEnabled(True)
self.show_privkeys_signal.disconnect()
nonlocal done
done = True
def on_dialog_closed(*args):
nonlocal done
nonlocal cancelled
if not done:
cancelled = True
self.computing_privkeys_signal.disconnect()
self.show_privkeys_signal.disconnect()
self.computing_privkeys_signal.connect(lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
self.show_privkeys_signal.connect(show_privkeys)
d.finished.connect(on_dialog_closed)
threading.Thread(target=privkeys_thread).start()
if not d.exec_():
@ -2437,6 +2454,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.fiat_receive_e.setVisible(b)
self.history_list.refresh_headers()
self.history_list.update()
self.address_list.refresh_headers()
self.address_list.update()
self.update_status()
@ -2587,7 +2605,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
unit_combo = QComboBox()
unit_combo.addItems(units)
unit_combo.setCurrentIndex(units.index(self.base_unit()))
def on_unit(x):
def on_unit(x, nz):
unit_result = units[unit_combo.currentIndex()]
if self.base_unit() == unit_result:
return
@ -2602,13 +2620,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
else:
raise Exception('Unknown base unit')
self.config.set_key('decimal_point', self.decimal_point, True)
nz.setMaximum(self.decimal_point)
self.history_list.update()
self.request_list.update()
self.address_list.update()
for edit, amount in zip(edits, amounts):
edit.setAmount(amount)
self.update_status()
unit_combo.currentIndexChanged.connect(on_unit)
unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
gui_widgets.append((unit_label, unit_combo))
block_explorers = sorted(util.block_explorer_info().keys())

View File

@ -355,14 +355,14 @@ class NetworkChoiceLayout(object):
height_str = "%d "%(self.network.get_local_height()) + _('blocks')
self.height_label.setText(height_str)
n = len(self.network.get_interfaces())
status = _("Connected to %d nodes.")%n if n else _("Not connected")
status = _("Connected to {0} nodes.").format(n) if n else _("Not connected")
self.status_label.setText(status)
chains = self.network.get_blockchains()
if len(chains)>1:
chain = self.network.blockchain()
checkpoint = chain.get_checkpoint()
name = chain.get_name()
msg = _('Chain split detected at block %d')%checkpoint + '\n'
msg = _('Chain split detected at block {0}').format(checkpoint) + '\n'
msg += (_('You are following branch') if auto_connect else _('Your server is on branch'))+ ' ' + name
msg += ' (%d %s)' % (chain.get_branch_size(), _('blocks'))
else:

View File

@ -113,8 +113,7 @@ class QRDialog(WindowModalDialog):
def copy_to_clipboard():
p = qscreen.grabWindow(qrw.winId())
p.save(filename, 'png')
QApplication.clipboard().setImage(QImage(filename))
QApplication.clipboard().setPixmap(p)
self.show_message(_("QR code copied to clipboard"))
b = QPushButton(_("Copy"))

View File

@ -56,6 +56,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
except BaseException as e:
self.show_error(str(e))
data = ''
if not data:
data = ''
self.setText(data)
return data

View File

@ -35,7 +35,7 @@ from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
def seed_warning_msg(seed):
return ''.join([
"<p>",
_("Please save these %d words on paper (order is important). "),
_("Please save these {0} words on paper (order is important). "),
_("This seed will allow you to recover your wallet in case "
"of computer failure."),
"</p>",
@ -45,7 +45,7 @@ def seed_warning_msg(seed):
"<li>" + _("Never type it on a website.") + "</li>",
"<li>" + _("Do not store it electronically.") + "</li>",
"</ul>"
]) % len(seed.split())
]).format(len(seed.split()))
class SeedLayout(QVBoxLayout):

View File

@ -85,12 +85,11 @@ class ElectrumGui:
delta = (80 - sum(width) - 4)/3
format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%" \
+ "%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
b = 0
messages = []
for item in self.wallet.get_history():
tx_hash, confirmations, value, timestamp, balance = item
if confirmations:
tx_hash, height, conf, timestamp, delta, balance = item
if conf:
try:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
except Exception:
@ -99,7 +98,7 @@ class ElectrumGui:
time_str = 'unconfirmed'
label = self.wallet.get_label(tx_hash)
messages.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
messages.append( format_str%( time_str, label, format_satoshis(delta, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
@ -149,12 +148,13 @@ class ElectrumGui:
for i, x in enumerate( self.wallet.network.banner.split('\n') ):
print( x )
def print_list(self, list, firstline):
self.maxpos = len(list)
def print_list(self, lst, firstline):
lst = list(lst)
self.maxpos = len(lst)
if not self.maxpos: return
print(firstline)
for i in range(self.maxpos):
msg = list[i] if i < len(list) else ""
msg = lst[i] if i < len(lst) else ""
print(msg)
@ -176,7 +176,7 @@ class ElectrumGui:
print(_('Invalid Fee'))
return
if self.wallet.use_encryption:
if self.wallet.has_password():
password = self.password_dialog()
if not password:
return

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -34,7 +34,7 @@ from functools import wraps
from decimal import Decimal
from .import util
from .util import bfh, bh2u, format_satoshis
from .util import bfh, bh2u, format_satoshis, json_decode
from .import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from .i18n import _
@ -151,10 +151,8 @@ class Commands:
@command('')
def setconfig(self, key, value):
"""Set a configuration variable. 'value' may be a string or a Python expression."""
try:
value = ast.literal_eval(value)
except:
pass
if key not in ('rpcuser', 'rpcpassword'):
value = json_decode(value)
self.config.set_key(key, value)
return True

View File

@ -28,12 +28,12 @@ import time
# from jsonrpc import JSONRPCResponseManager
import jsonrpclib
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler
from .jsonrpc import VerifyingJSONRPCServer
from .version import ELECTRUM_VERSION
from .network import Network
from .util import json_decode, DaemonThread
from .util import print_error
from .util import print_error, to_string
from .wallet import Wallet
from .storage import WalletStorage
from .commands import known_commands, Commands
@ -75,7 +75,14 @@ def get_server(config):
try:
with open(lockfile) as f:
(host, port), create_time = ast.literal_eval(f.read())
server = jsonrpclib.Server('http://%s:%d' % (host, port))
rpc_user, rpc_password = get_rpc_credentials(config)
if rpc_password == '':
# authentication disabled
server_url = 'http://%s:%d' % (host, port)
else:
server_url = 'http://%s:%s@%s:%d' % (
rpc_user, rpc_password, host, port)
server = jsonrpclib.Server(server_url)
# Test daemon is running
server.ping()
return server
@ -87,22 +94,29 @@ def get_server(config):
time.sleep(1.0)
class RequestHandler(SimpleJSONRPCRequestHandler):
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
def end_headers(self):
self.send_header("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept")
self.send_header("Access-Control-Allow-Origin", "*")
SimpleJSONRPCRequestHandler.end_headers(self)
def get_rpc_credentials(config):
rpc_user = config.get('rpcuser', None)
rpc_password = config.get('rpcpassword', None)
if rpc_user is None or rpc_password is None:
rpc_user = 'user'
import ecdsa, base64
bits = 128
nbytes = bits // 8 + (bits % 8 > 0)
pw_int = ecdsa.util.randrange(pow(2, bits))
pw_b64 = base64.b64encode(
pw_int.to_bytes(nbytes, 'big'), b'-_')
rpc_password = to_string(pw_b64, 'ascii')
config.set_key('rpcuser', rpc_user)
config.set_key('rpcpassword', rpc_password, save=True)
elif rpc_password == '':
from .util import print_stderr
print_stderr('WARNING: RPC authentication is disabled.')
return rpc_user, rpc_password
class Daemon(DaemonThread):
def __init__(self, config, fd):
def __init__(self, config, fd, is_gui):
DaemonThread.__init__(self)
self.config = config
if config.get('offline'):
@ -117,14 +131,16 @@ class Daemon(DaemonThread):
self.gui = None
self.wallets = {}
# Setup JSONRPC server
self.cmd_runner = Commands(self.config, None, self.network)
self.init_server(config, fd)
self.init_server(config, fd, is_gui)
def init_server(self, config, fd):
def init_server(self, config, fd, is_gui):
host = config.get('rpchost', '127.0.0.1')
port = config.get('rpcport', 0)
rpc_user, rpc_password = get_rpc_credentials(config)
try:
server = SimpleJSONRPCServer((host, port), logRequests=False, requestHandler=RequestHandler)
server = VerifyingJSONRPCServer((host, port), logRequests=False,
rpc_user=rpc_user, rpc_password=rpc_password)
except Exception as e:
self.print_error('Warning: cannot initialize RPC server on host', host, e)
self.server = None
@ -132,14 +148,17 @@ class Daemon(DaemonThread):
return
os.write(fd, bytes(repr((server.socket.getsockname(), time.time())), 'utf8'))
os.close(fd)
server.timeout = 0.1
for cmdname in known_commands:
server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
server.register_function(self.run_cmdline, 'run_cmdline')
server.register_function(self.ping, 'ping')
server.register_function(self.run_daemon, 'daemon')
server.register_function(self.run_gui, 'gui')
self.server = server
server.timeout = 0.1
server.register_function(self.ping, 'ping')
if is_gui:
server.register_function(self.run_gui, 'gui')
else:
server.register_function(self.run_daemon, 'daemon')
self.cmd_runner = Commands(self.config, None, self.network)
for cmdname in known_commands:
server.register_function(getattr(self.cmd_runner, cmdname), cmdname)
server.register_function(self.run_cmdline, 'run_cmdline')
def ping(self):
return True
@ -188,12 +207,13 @@ class Daemon(DaemonThread):
def run_gui(self, config_options):
config = SimpleConfig(config_options)
if self.gui:
if hasattr(self.gui, 'new_window'):
path = config.get_wallet_path()
self.gui.new_window(path, config.get('url'))
response = "ok"
else:
response = "error: current GUI does not support multiple windows"
#if hasattr(self.gui, 'new_window'):
# path = config.get_wallet_path()
# self.gui.new_window(path, config.get('url'))
# response = "ok"
#else:
# response = "error: current GUI does not support multiple windows"
response = "error: Electrum GUI already running"
else:
response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
return response

View File

@ -5,6 +5,7 @@ import sys
from threading import Thread
import time
import csv
import decimal
from decimal import Decimal
from .bitcoin import COIN
@ -389,7 +390,11 @@ class FxThread(ThreadJob):
def ccy_amount_str(self, amount, commas):
prec = CCY_PRECISIONS.get(self.ccy, 2)
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
return fmt_str.format(round(amount, prec))
try:
rounded_amount = round(amount, prec)
except decimal.InvalidOperation:
rounded_amount = amount
return fmt_str.format(rounded_amount)
def run(self):
# This runs from the plugins thread which catches exceptions

95
lib/jsonrpc.py Normal file
View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2018 Thomas Voegtlin
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler
from base64 import b64decode
import time
from . import util
class RPCAuthCredentialsInvalid(Exception):
def __str__(self):
return 'Authentication failed (bad credentials)'
class RPCAuthCredentialsMissing(Exception):
def __str__(self):
return 'Authentication failed (missing credentials)'
class RPCAuthUnsupportedType(Exception):
def __str__(self):
return 'Authentication failed (only basic auth is supported)'
# based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke
class VerifyingJSONRPCServer(SimpleJSONRPCServer):
def __init__(self, *args, rpc_user, rpc_password, **kargs):
self.rpc_user = rpc_user
self.rpc_password = rpc_password
class VerifyingRequestHandler(SimpleJSONRPCRequestHandler):
def parse_request(myself):
# first, call the original implementation which returns
# True if all OK so far
if SimpleJSONRPCRequestHandler.parse_request(myself):
try:
self.authenticate(myself.headers)
return True
except (RPCAuthCredentialsInvalid, RPCAuthCredentialsMissing,
RPCAuthUnsupportedType) as e:
myself.send_error(401, str(e))
except BaseException as e:
import traceback, sys
traceback.print_exc(file=sys.stderr)
myself.send_error(500, str(e))
return False
SimpleJSONRPCServer.__init__(
self, requestHandler=VerifyingRequestHandler, *args, **kargs)
def authenticate(self, headers):
if self.rpc_password == '':
# RPC authentication is disabled
return
auth_string = headers.get('Authorization', None)
if auth_string is None:
raise RPCAuthCredentialsMissing()
(basic, _, encoded) = auth_string.partition(' ')
if basic != 'Basic':
raise RPCAuthUnsupportedType()
encoded = util.to_bytes(encoded, 'utf8')
credentials = util.to_string(b64decode(encoded), 'utf8')
(username, _, password) = credentials.partition(':')
if not (util.constant_time_compare(username, self.rpc_user)
and util.constant_time_compare(password, self.rpc_password)):
time.sleep(0.050)
raise RPCAuthCredentialsInvalid()

View File

@ -171,7 +171,10 @@ class Mnemonic(object):
n_custom = int(math.ceil(math.log(custom_entropy, 2)))
n = max(16, num_bits - n_custom)
print_error("make_seed", prefix, "adding %d bits"%n)
my_entropy = ecdsa.util.randrange(pow(2, n))
my_entropy = 1
while my_entropy < pow(2, n - bpw):
# try again if seed would not contain enough words
my_entropy = ecdsa.util.randrange(pow(2, n))
nonce = 0
while True:
nonce += 1

View File

@ -106,7 +106,8 @@ proxy_modes = ['socks4', 'socks5', 'http']
def serialize_proxy(p):
if not isinstance(p, dict):
return None
return ':'.join([p.get('mode'),p.get('host'), p.get('port'), p.get('user'), p.get('password')])
return ':'.join([p.get('mode'), p.get('host'), p.get('port'),
p.get('user', ''), p.get('password', '')])
def deserialize_proxy(s):

View File

@ -165,7 +165,7 @@ def _parsePKCS8(_bytes):
def _parseSSLeay(bytes):
return _parseASN1PrivateKey(ASN1_Node(str(bytes)))
return _parseASN1PrivateKey(ASN1_Node(bytes))
def bytesToNumber(s):

View File

@ -49,7 +49,8 @@ def scan_barcode(device='', timeout=-1, display=True, threaded=False):
libzbar.zbar_symbol_set_first_symbol.restype = ctypes.POINTER(ctypes.c_int)
proc = libzbar.zbar_processor_create(threaded)
libzbar.zbar_processor_request_size(proc, 640, 480)
libzbar.zbar_processor_init(proc, device, display)
if libzbar.zbar_processor_init(proc, device.encode('utf-8'), display) != 0:
raise RuntimeError("Can not start QR scanner; initialization failed.")
libzbar.zbar_processor_set_visible(proc)
if libzbar.zbar_process_one(proc, timeout):
symbols = libzbar.zbar_processor_get_results(proc)

View File

@ -224,6 +224,10 @@ class TestTransaction(unittest.TestCase):
tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000')
self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid())
def test_txid_input_p2wsh_p2sh_not_multisig(self):
tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000')
self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid())
class NetworkMock(object):

View File

@ -45,6 +45,14 @@ class SerializationError(Exception):
""" Thrown when there's a problem deserializing or serializing """
class UnknownTxinType(Exception):
pass
class NotRecognizedRedeemScript(Exception):
pass
class BCDataStream(object):
def __init__(self):
self.input = None
@ -302,10 +310,23 @@ def parse_scriptSig(d, _bytes):
if match_decoded(decoded, match):
item = decoded[0][1]
if item[0] == 0:
# segwit embedded into p2sh
# witness version 0
# segwit embedded into p2sh
d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))
d['type'] = 'p2wpkh-p2sh' if len(item) == 22 else 'p2wsh-p2sh'
if len(item) == 22:
d['type'] = 'p2wpkh-p2sh'
elif len(item) == 34:
d['type'] = 'p2wsh-p2sh'
else:
print_error("unrecognized txin type", bh2u(item))
elif opcodes.OP_1 <= item[0] <= opcodes.OP_16:
# segwit embedded into p2sh
# witness version 1-16
pass
else:
# payto_pubkey
# assert item[0] == 0x30
# pay-to-pubkey
d['type'] = 'p2pk'
d['address'] = "(pubkey)"
d['signatures'] = [bh2u(item)]
@ -361,7 +382,7 @@ def parse_redeemScript(s):
match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]
if not match_decoded(dec2, match_multisig):
print_error("cannot find address in input script", bh2u(s))
return
raise NotRecognizedRedeemScript()
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
redeemScript = multisig_script(pubkeys, m)
@ -430,21 +451,40 @@ def parse_witness(vds, txin):
if n == 0xffffffff:
txin['value'] = vds.read_uint64()
n = vds.read_compact_size()
# now 'n' is the number of items in the witness
w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n))
add_w = lambda x: var_int(len(x) // 2) + x
txin['witness'] = var_int(n) + ''.join(add_w(i) for i in w)
# FIXME: witness version > 0 will probably fail here.
# For native segwit, we would need the scriptPubKey of the parent txn
# to determine witness program version, and properly parse the witness.
# In case of p2sh-segwit, we can tell based on the scriptSig in this txn.
# The code below assumes witness version 0.
# p2sh-segwit should work in that case; for native segwit we need to tell
# between p2wpkh and p2wsh; we do this based on number of witness items,
# hence (FIXME) p2wsh with n==2 (maybe n==1 ?) will probably fail.
# If v==0 and n==2, we need parent scriptPubKey to distinguish between p2wpkh and p2wsh.
if txin['type'] == 'coinbase':
pass
elif n > 2:
elif txin['type'] == 'p2wsh-p2sh' or n > 2:
try:
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
except NotRecognizedRedeemScript:
raise UnknownTxinType()
txin['signatures'] = parse_sig(w[1:-1])
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
txin['num_sig'] = m
txin['x_pubkeys'] = x_pubkeys
txin['pubkeys'] = pubkeys
txin['witnessScript'] = witnessScript
else:
elif txin['type'] == 'p2wpkh-p2sh' or n == 2:
txin['num_sig'] = 1
txin['x_pubkeys'] = [w[1]]
txin['pubkeys'] = [safe_parse_pubkey(w[1])]
txin['signatures'] = parse_sig([w[0]])
else:
raise UnknownTxinType()
def parse_output(vds, i):
d = {}
@ -474,7 +514,12 @@ def deserialize(raw):
if is_segwit:
for i in range(n_vin):
txin = d['inputs'][i]
parse_witness(vds, txin)
try:
parse_witness(vds, txin)
except UnknownTxinType:
txin['type'] = 'unknown'
# FIXME: GUI might show 'unknown' address (e.g. for a non-multisig p2wsh)
continue
# segwit-native script
if not txin.get('scriptSig'):
if txin['num_sig'] == 1:
@ -674,7 +719,9 @@ class Transaction:
witness_script = multisig_script(pubkeys, txin['num_sig'])
witness = var_int(n) + '00' + ''.join(add_w(x) for x in sig_list) + add_w(witness_script)
else:
raise BaseException('wrong txin type')
witness = txin.get('witness', None)
if not witness:
raise BaseException('wrong txin type:', txin['type'])
if self.is_txin_complete(txin) or estimate_size:
value_field = ''
else:
@ -682,8 +729,13 @@ class Transaction:
return value_field + witness
@classmethod
def is_segwit_input(self, txin):
return txin['type'] in ['p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh']
def is_segwit_input(cls, txin):
has_nonzero_witness = txin.get('witness', '00') != '00'
return cls.is_segwit_inputtype(txin['type']) or has_nonzero_witness
@classmethod
def is_segwit_inputtype(cls, txin_type):
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
@classmethod
def input_script(self, txin, estimate_size=False):

View File

@ -28,6 +28,7 @@ from decimal import Decimal
import traceback
import urllib
import threading
import hmac
from .i18n import _
@ -196,6 +197,13 @@ def json_decode(x):
except:
return x
# taken from Django Source Code
def constant_time_compare(val1, val2):
"""Return True if the two strings are equal, False otherwise."""
return hmac.compare_digest(to_bytes(val1, 'utf8'), to_bytes(val2, 'utf8'))
# decorator that prints execution time
def profiler(func):
def do_profile(func, args, kw_args):

View File

@ -1,4 +1,4 @@
ELECTRUM_VERSION = '3.0.3' # version of the client package
ELECTRUM_VERSION = '3.0.6' # version of the client package
PROTOCOL_VERSION = '1.1' # protocol version requested
# The hash of the mnemonic seed must begin with this

View File

@ -361,7 +361,8 @@ class Abstract_Wallet(PrintError):
def add_unverified_tx(self, tx_hash, tx_height):
if tx_height == 0 and tx_hash in self.verified_tx:
self.verified_tx.pop(tx_hash)
self.verifier.merkle_roots.pop(tx_hash, None)
if self.verifier:
self.verifier.merkle_roots.pop(tx_hash, None)
# tx will be verified only if height > 0
if tx_hash not in self.verified_tx:
@ -959,7 +960,7 @@ class Abstract_Wallet(PrintError):
# if we are on a pruning server, remove unverified transactions
with self.lock:
vr = list(self.verified_tx.keys()) + list(self.unverified_tx.keys())
for tx_hash in self.transactions.keys():
for tx_hash in list(self.transactions):
if tx_hash not in vr:
self.print_error("removing transaction", tx_hash)
self.transactions.pop(tx_hash)
@ -1435,9 +1436,6 @@ class Imported_Wallet(Simple_Wallet):
def is_deterministic(self):
return False
def is_used(self, address):
return False
def is_change(self, address):
return False

View File

@ -84,7 +84,8 @@ class WsClientThread(util.DaemonThread):
l = self.subscriptions.get(addr, [])
l.append((ws, amount))
self.subscriptions[addr] = l
self.network.send([('blockchain.address.subscribe', [addr])], self.response_queue.put)
h = self.network.addr_to_scripthash(addr)
self.network.send([('blockchain.scripthash.subscribe', [h])], self.response_queue.put)
def run(self):
@ -100,10 +101,13 @@ class WsClientThread(util.DaemonThread):
result = r.get('result')
if result is None:
continue
if method == 'blockchain.address.subscribe':
self.network.send([('blockchain.address.get_balance', params)], self.response_queue.put)
elif method == 'blockchain.address.get_balance':
addr = params[0]
if method == 'blockchain.scripthash.subscribe':
self.network.send([('blockchain.scripthash.get_balance', params)], self.response_queue.put)
elif method == 'blockchain.scripthash.get_balance':
h = params[0]
addr = self.network.h2addr.get(h, None)
if addr is None:
util.print_error("can't find address for scripthash: %s" % h)
l = self.subscriptions.get(addr, [])
for ws, amount in l:
if not ws.closed:

View File

@ -25,6 +25,12 @@ try:
except ImportError:
BTCHIP = False
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
' https://www.ledgerwallet.com'
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcoin" app) too old for Segwit support. Please update at') + \
' https://www.ledgerwallet.com'
class Ledger_Client():
def __init__(self, hidDevice):
self.dongleObject = btchip(hidDevice)
@ -46,8 +52,23 @@ class Ledger_Client():
return ""
def i4b(self, x):
return pack('>I', x)
return pack('>I', x)
def test_pin_unlocked(func):
"""Function decorator to test the Ledger for being unlocked, and if not,
raise a human-readable exception.
"""
def catch_exception(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except BTChipException as e:
if e.sw == 0x6982:
raise Exception(_('Your Ledger is locked. Please unlock it.'))
else:
raise
return catch_exception
@test_pin_unlocked
def get_xpub(self, bip32_path, xtype):
self.checkDevice()
# bip32_path is of the form 44'/0'/1'
@ -57,9 +78,9 @@ class Ledger_Client():
#self.get_client() # prompt for the PIN before displaying the dialog if necessary
#self.handler.show_message("Computing master public key")
if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit():
raise Exception("Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com")
raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit():
raise Exception("Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com")
raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
splitPath = bip32_path.split('/')
if splitPath[0] == 'm':
splitPath = splitPath[1:]
@ -68,7 +89,7 @@ class Ledger_Client():
if len(splitPath) > 1:
prevPath = "/".join(splitPath[0:len(splitPath) - 1])
nodeData = self.dongleObject.getWalletPublicKey(prevPath)
publicKey = compress_public_key(nodeData['publicKey'])#
publicKey = compress_public_key(nodeData['publicKey'])
h = hashlib.new('ripemd160')
h.update(hashlib.sha256(publicKey).digest())
fingerprint = unpack(">I", h.digest()[0:4])[0]
@ -119,7 +140,7 @@ class Ledger_Client():
if not checkFirmware(firmware):
self.dongleObject.dongle.close()
raise Exception("HW1 firmware version too old. Please update at https://www.ledgerwallet.com")
raise Exception(MSG_NEEDS_FW_UPDATE_GENERIC)
try:
self.dongleObject.getOperationMode()
except BTChipException as e:
@ -183,14 +204,14 @@ class Ledger_KeyStore(Hardware_KeyStore):
return obj
def get_derivation(self):
return self.derivation
return self.derivation
def get_client(self):
return self.plugin.get_client(self).dongleObject
def get_client_electrum(self):
return self.plugin.get_client(self)
def give_error(self, message, clear_client = False):
print_error(message)
if not self.signing:
@ -285,12 +306,12 @@ class Ledger_KeyStore(Hardware_KeyStore):
if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
if not self.get_client_electrum().supports_segwit():
self.give_error("Firmware version too old to support segwit. Please update at https://www.ledgerwallet.com")
self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
segwitTransaction = True
if txin['type'] in ['p2wpkh', 'p2wsh']:
if not self.get_client_electrum().supports_native_segwit():
self.give_error("Firmware version too old to support native segwit. Please update at https://www.ledgerwallet.com")
self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
segwitTransaction = True
pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
@ -342,10 +363,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
try:
# Get trusted inputs from the original transactions
for utxo in inputs:
for utxo in inputs:
sequence = int_to_hex(utxo[5], 4)
if segwitTransaction:
txtmp = bitcoinTransaction(bfh(utxo[0]))
txtmp = bitcoinTransaction(bfh(utxo[0]))
tmp = bfh(utxo[3])[::-1]
tmp += bfh(int_to_hex(utxo[1], 4))
tmp += txtmp.outputs[utxo[1]].amount
@ -434,7 +455,7 @@ class LedgerPlugin(HW_PluginBase):
libraries_available = BTCHIP
keystore_class = Ledger_KeyStore
client = None
DEVICE_IDS = [
DEVICE_IDS = [
(0x2581, 0x1807), # HW.1 legacy btchip
(0x2581, 0x2b7c), # HW.1 transitional production
(0x2581, 0x3b7c), # HW.1 ledger production
@ -459,12 +480,12 @@ class LedgerPlugin(HW_PluginBase):
def get_btchip_device(self, device):
ledger = False
if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97):
ledger = True
ledger = True
dev = hid.device()
dev.open_path(device.path)
dev.set_nonblocking(True)
return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
def create_client(self, device, handler):
self.handler = handler
@ -473,7 +494,7 @@ class LedgerPlugin(HW_PluginBase):
client = Ledger_Client(client)
return client
def setup_device(self, device_info, wizard):
def setup_device(self, device_info, wizard):
devmgr = self.device_manager()
device_id = device_info.device.id_
client = devmgr.client_by_id(device_id)
@ -494,10 +515,10 @@ class LedgerPlugin(HW_PluginBase):
devmgr = self.device_manager()
handler = keystore.handler
with devmgr.hid_lock:
client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
# returns the client for a given keystore. can use xpub
#if client:
# client.used()
if client is not None:
client.checkDevice()
client.checkDevice()
return client