Skip to content

Commit

Permalink
Electrum-ltc-satochip v4.0.9-0.11
Browse files Browse the repository at this point in the history
Electrum-ltc v4.0.9 with support for Satochip v0.11

Merge branch 'electrum-satochip-v4.0.2-0.11' into electrum-ltc-satochip-v4.0.9-0.11

# Conflicts:
#	contrib/build-wine/deterministic.spec
#	contrib/deterministic-build/requirements-hw.txt
#	contrib/requirements/requirements-hw.txt
#	electrum_ltc/plugins/hw_wallet/qt.py
  • Loading branch information
Toporin committed Jan 8, 2021
2 parents 41a6fe9 + ff0580c commit f137f8e
Show file tree
Hide file tree
Showing 9 changed files with 910 additions and 787 deletions.
2 changes: 2 additions & 0 deletions contrib/build-linux/appimage/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ RUN apt-get update -q && \
zlib1g-dev=1:1.2.8.dfsg-2ubuntu4.3 \
libffi-dev=3.2.1-4 \
libncurses5-dev=6.0+20160213-1ubuntu1 \
libpcsclite-dev=1.8.14-1ubuntu1.16.04.1 \
swig=3.0.8-0ubuntu3 \
libsqlite3-dev=3.11.0-1ubuntu1.5 \
libusb-1.0-0-dev=2:1.0.20-1 \
libudev-dev=229-4ubuntu21.29 \
Expand Down
2 changes: 1 addition & 1 deletion contrib/build-wine/deterministic.spec
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ binaries += [('C:/tmp/libusb-1.0.dll', '.')]
binaries += [('C:/tmp/libzbar-0.dll', '.')]

# pyscard binaries for Satochip
binaries += [('C:/python*/Lib/site-packages/smartcard/scard/_scard.cp36-win32.pyd', '.')] #satochip
binaries += [('C:/python*/Lib/site-packages/smartcard/scard/_scard.cp37-win32.pyd', '.')] #satochip

datas = [
(home+'electrum_ltc/*.json', 'electrum_ltc'),
Expand Down
13 changes: 6 additions & 7 deletions contrib/build-wine/prepare-wine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ PYINSTALLER_REPO="https://github.com/SomberNight/pyinstaller.git"
PYINSTALLER_COMMIT="6e455b2c1208465742484436009bfb1e1baf2e01"
# ^ tag 4.0, plus a custom commit that fixes cross-compilation with MinGW

PYTHON_VERSION=3.7.9

#Satochip pyscard (from https://ci.appveyor.com/project/LudovicRousseau/pyscard)
# pyscard prebuilt binaries for Satochip
# PYSCARD_FILENAME=pyscard-1.9.9-cp36-cp36m-win32.whl # python 3.6, 32-bit
# PYSCARD_URL=https://ci.appveyor.com/api/buildjobs/3uiua5o4llvpegbp/artifacts/dist/pyscard-1.9.9-cp36-cp36m-win32.whl
# PYSCARD_URL=https://github.com/cculianu/Electron-Cash-Build-Tools/releases/download/v1.0/pyscard-1.9.9-cp36-cp36m-win32.whl
# PYSCARD_SHA256=99d2b450f322f9ed9682fd2a99d95ce781527e371006cded38327efca8158fe7
PYSCARD_FILENAME=pyscard-1.9.9-cp36-cp36m-win32.whl # python 3.6, 32-bit
PYSCARD_URL=https://github.com/cculianu/Electron-Cash-Build-Tools/releases/download/v1.0/pyscard-1.9.9-cp36-cp36m-win32.whl
PYSCARD_SHA256=99d2b450f322f9ed9682fd2a99d95ce781527e371006cded38327efca8158fe7
PYSCARD_FILENAME=pyscard-1.9.9-cp37-cp37m-win32.whl # python 3.7, 32-bit
PYSCARD_URL=https://ci.appveyor.com/api/buildjobs/f9cmce4j8hkau9n4/artifacts/dist/pyscard-1.9.9-cp37-cp37m-win32.whl
PYSCARD_SHA256=3f7d52dd6694dd369b02e797fe1a3e39b63cf1d1c4b5fc0e1341aafa24f87e7a

PYTHON_VERSION=3.7.9

## These settings probably don't need change
export WINEPREFIX=/opt/wine64
Expand Down
6 changes: 3 additions & 3 deletions contrib/deterministic-build/requirements-hw.txt
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ pyaes==1.6.1 \
pycparser==2.20 \
--hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \
--hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705
pysatochip==0.11.3 \
--hash=sha256:f115b659b80304ef0dec650d753c2d0ba6f0512a2ad6be9bdef291e0d6307ec4 \
--hash=sha256:dcf6ac88d15570941543ea46ba0360b75ec216b1253354b4ba863473e95850fc
pysatochip==0.11.4 \
--hash=sha256:89ac8b7936c51c96a3fe4a7b4c602a4a4b20ea36a935cc43c268ddcbbd90203e \
--hash=sha256:263ff36484bd697999db1762dbd12e9fb89122b224f68065aed37dfbaa2920ca
pyscard==1.9.9 \
--hash=sha256:6620a74f58d5fa9076544263bb4e42c946eb20f315c896d14a7e5743d5431469 \
--hash=sha256:a047738c58d05b4dab15aa9c99fbd92f8d0670900de89c68bec247a422f8d8c7 \
Expand Down
2 changes: 2 additions & 0 deletions contrib/requirements/requirements-hw.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ pysatochip >=0.11.3
keepkey>=6.3.1
btchip-python>=0.1.30
ckcc-protocol>=0.7.7
pyscard>=1.9.9
pysatochip==0.11.4
bitbox02>=5.0.0
5 changes: 3 additions & 2 deletions electrum_ltc/plugins/hw_wallet/qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,10 @@ def error_dialog(self, msg, blocking):

def clear_dialog(self):
if self.dialog:
try: self.dialog.accept()
try:
self.dialog.accept()
except RuntimeError:
pass #see https://github.com/Electron-Cash/Electron-Cash/issues/1437
pass #see https://github.com/Electron-Cash/Electron-Cash/issues/1437
self.dialog = None

def win_query_choice(self, msg, labels):
Expand Down
17 changes: 2 additions & 15 deletions electrum_ltc/plugins/satochip/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Introduction
This plugin allows to integrate the Satochip Hardware Wallet with Electrum. To use it, you need a device with the Satochip javacard applet installed (see https://github.com/Toporin/SatochipApplet).
If the wallet is not intialized yet, Electrum will perform the setup (you only need to do this once). During setup, a seed is created: this seed allows you to recover your wallet at anytime, so make sure to BACKUP THE SEED SECURELY! During setup, a PIN code is also created: this PIN allows to unlock th device to access your funds. If you try too many wrong PIN, your device will be locked indefinitely (it is 'bricked'). If you loose your PIN or brick your device, you can only recover your funds with the seed backup.

The Satochip wallet is currently in Beta, use with caution! In this phase, it is strongly recommended to use the software on the Bitcoin testnet first.
The Satochip wallet is currently in Beta, use with caution!You can use the software on the Bitcoin testnet using the --testnet option.
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Rem: Electrum uses Python 3.x. In case of error, check first that you are not trying to run Electrum with Python 2.x or with Python 2.x libraries.
Expand Down Expand Up @@ -81,17 +81,4 @@ Pyscard is required to connect to the smartcard::


To run Electrum use::
python3 electrum -v --testnet
Test suite
=============

To run the test suite, run::

python -m unittest electrum.plugins.satochip.test_CardConnector
The test suite uses the following default PIN code: "12345678".
If you run the test suite after (or before) electrum, you may block the card if the PIN used are not the same!
If the card is locked, you will have to reinstall the javacard applet on the card.

python3 electrum -v --testnet
159 changes: 119 additions & 40 deletions electrum_ltc/plugins/satochip/qt.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from electrum_ltc.i18n import _
from electrum_ltc.logging import get_logger
from electrum_ltc.simple_config import SimpleConfig
from electrum_ltc.gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton, CancelButton, WindowModalDialog, WWLabel)
from PyQt5.QtCore import Qt, pyqtSignal
from electrum_ltc.gui.qt.qrcodewidget import QRCodeWidget, QRDialog
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import (QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout, QLineEdit, QCheckBox)
from functools import partial
from os import urandom

#satochip
from .satochip import SatochipPlugin
Expand All @@ -17,6 +20,8 @@

_logger = get_logger(__name__)

MSG_USE_2FA= _("Do you want to use 2-Factor-Authentication (2FA)?\n\nWith 2FA, any transaction must be confirmed on a second device such as your smartphone. First you have to install the Satochip-2FA android app on google play. Then you have to pair your 2FA device with your Satochip by scanning the qr-code on the next screen. \n\nWARNING: be sure to backup a copy of the qr-code in a safe place, in case you have to reinstall the app!")

class Plugin(SatochipPlugin, QtPluginBase):
icon_unpaired = "satochip_unpaired.png"
icon_paired = "satochip.png"
Expand Down Expand Up @@ -49,10 +54,17 @@ def settings_dialog(self, window):

def show_settings_dialog(self, window, keystore):
# When they click on the icon for Satochip we come here.
device_id = self.choose_device(window, keystore)
if device_id:
SatochipSettingsDialog(window, self, keystore, device_id).exec_()

# device_id = self.choose_device(window, keystore)
# if device_id:
# SatochipSettingsDialog(window, self, keystore, device_id).exec_()
def connect():
device_id = self.choose_device(window, keystore)
return device_id
def show_dialog(device_id):
if device_id:
SatochipSettingsDialog(window, self, keystore, device_id).exec_()
keystore.thread.add(connect, on_success=show_dialog)

class Satochip_Handler(QtHandlerBase):

def __init__(self, win):
Expand Down Expand Up @@ -93,7 +105,7 @@ def connect_and_doit():
<br><a href="https://satochip.io">satochip.io</a>''')
title.setTextInteractionFlags(Qt.LinksAccessibleByMouse)

grid.addWidget(title , 0,0, 1,2, Qt.AlignHCenter)
grid.addWidget(title, 0, 0, 1, 2, Qt.AlignHCenter)
y = 3

rows = [
Expand All @@ -119,18 +131,35 @@ def _change_pin():
thread.add(connect_and_doit, on_success=self.change_pin)
pin_btn.clicked.connect(_change_pin)

seed_btn = QPushButton('reset seed')
seed_btn = QPushButton('Reset seed')
def _reset_seed():
thread.add(connect_and_doit, on_success=self.reset_seed)
thread.add(connect_and_doit, on_success=self.show_values)
seed_btn.clicked.connect(_reset_seed)


set_2FA_btn = QPushButton('Enable 2FA')
def _set_2FA():
thread.add(connect_and_doit, on_success=self.set_2FA)
thread.add(connect_and_doit, on_success=self.show_values)
set_2FA_btn.clicked.connect(_set_2FA)

reset_2FA_btn = QPushButton('Disable 2FA')
def _reset_2FA():
thread.add(connect_and_doit, on_success=self.reset_2FA)
thread.add(connect_and_doit, on_success=self.show_values)
reset_2FA_btn.clicked.connect(_reset_2FA)

y += 3
grid.addWidget(pin_btn, y, 0)
grid.addWidget(seed_btn, y, 1)
y += 5
grid.addWidget(CloseButton(self), y, 1)

grid.addWidget(pin_btn, y, 0, 1, 2, Qt.AlignHCenter)
y += 2
grid.addWidget(seed_btn, y, 0, 1, 2, Qt.AlignHCenter)
y += 2
grid.addWidget(set_2FA_btn, y, 0, 1, 2, Qt.AlignHCenter)
y += 2
grid.addWidget(reset_2FA_btn, y, 0, 1, 2, Qt.AlignHCenter)
y += 2
grid.addWidget(CloseButton(self), y, 0, 1, 2, Qt.AlignHCenter)

dialog_vbox = QVBoxLayout(self)
dialog_vbox.addWidget(body)

Expand Down Expand Up @@ -161,7 +190,7 @@ def show_values(self, client):
# needs2FA?
if d["needs2FA"]:
self.needs_2FA.setText('<tt>%s' % "yes")
elif len(response)>=9 and response[8]==0X00:
else:
self.needs_2FA.setText('<tt>%s' % "no")

# needs secure channel
Expand All @@ -176,7 +205,7 @@ def show_values(self, client):
self.needs_2FA.setText('<tt>%s' % "(unitialized)")
self.is_seeded.setText('<tt>%s' % "no")
self.needs_SC.setText('<tt>%s' % "(unknown)")


def change_pin(self, client):
_logger.info("In change_pin")
Expand Down Expand Up @@ -209,7 +238,7 @@ def reset_seed(self, client):
_("Please be sure that your wallet is empty and that you have a backup of the seed as a precaution.\n\n"),
_("To proceed, enter the PIN for your Satochip:")
])
(password, reset_2FA)= self.reset_seed_dialog(msg)
password = self.reset_seed_dialog(msg)
if (password is None):
return
pin = password.encode('utf8')
Expand Down Expand Up @@ -253,11 +282,58 @@ def reset_seed(self, client):
msg= _("Seed reset successfully!\nYou should close this wallet and launch the wizard to generate a new wallet.")
client.handler.show_message(msg)
#to do: close client?
elif (sw1==0x9c and sw2==0x0b):
msg= _(f"Failed to reset seed: request rejected by 2FA device (error code: {hex(256*sw1+sw2)})")
client.handler.show_message(msg)
#to do: close client?
else:
msg= _(f"Failed to reset seed with error code: {hex(sw1)}{hex(sw2)}")
client.handler.show_error(msg)

if reset_2FA and client.cc.needs_2FA:
msg= _(f"Failed to reset seed with error code: {hex(256*sw1+sw2)}")
client.handler.show_error(msg)

def reset_seed_dialog(self, msg):
_logger.info("In reset_seed_dialog")
parent = self.top_level_window()
d = WindowModalDialog(parent, _("Enter PIN"))
pw = QLineEdit()
pw.setEchoMode(2)
pw.setMinimumWidth(200)

vbox = QVBoxLayout()
vbox.addWidget(WWLabel(msg))
vbox.addWidget(pw)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox)

passphrase = pw.text() if d.exec_() else None
return passphrase

def set_2FA(self, client):
if not client.cc.needs_2FA:
use_2FA=client.handler.yes_no_question(MSG_USE_2FA)
if (use_2FA):
secret_2FA= urandom(20)
secret_2FA_hex=secret_2FA.hex()
# the secret must be shared with the second factor app (eg on a smartphone)
try:
config = SimpleConfig()
help_txt="Scan the QR-code with your Satochip-2FA app and make a backup of the following secret: "+ secret_2FA_hex
d = QRDialog(data=secret_2FA_hex, parent=None, title="Secret_2FA", show_text=False, help_text=help_txt, show_copy_text_btn=True, config=config)
d.exec_()
except Exception as e:
_logger.info("SatochipPlugin: setup 2FA error: "+str(e))
return
# further communications will require an id and an encryption key (for privacy).
# Both are derived from the secret_2FA using a one-way function inside the Satochip
amount_limit= 0 # i.e. always use
(response, sw1, sw2)=client.cc.card_set_2FA_key(secret_2FA, amount_limit)
if sw1!=0x90 or sw2!=0x00:
_logger.info(f"Unable to set 2FA with error code:= {hex(256*sw1+sw2)}")#debugSatochip
raise RuntimeError(f'Unable to setup 2FA with error code: {hex(256*sw1+sw2)}')
else:
client.handler.show_message("2FA enabled successfully!")

def reset_2FA(self, client):
if client.cc.needs_2FA:
# challenge based on ID_2FA
# format & encrypt msg
import json
Expand All @@ -276,7 +352,7 @@ def reset_seed(self, client):
try:
reply_encrypt= d['reply_encrypt']
except Exception as e:
self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True)
self.give_error("No response received from 2FA!", True)
reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False)
_logger.info("challenge:response= "+ reply_decrypt)
reply_decrypt= reply_decrypt.split(":")
Expand All @@ -289,28 +365,31 @@ def reset_seed(self, client):
msg= _("2FA reset successfully!")
client.cc.needs_2FA= False
client.handler.show_message(msg)
elif (sw1==0x9c and sw2==0x17):
msg= _(f"Failed to reset 2FA: \nyou must reset the seed first (error code {hex(256*sw1+sw2)})")
client.handler.show_error(msg)
else:
msg= _(f"Failed to reset 2FA with error code: {hex(sw1)}{hex(sw2)}")
msg= _(f"Failed to reset 2FA with error code: {hex(256*sw1+sw2)}")
client.handler.show_error(msg)
else:
msg= _(f"2FA is already disabled!")
client.handler.show_error(msg)






def reset_seed_dialog(self, msg):
_logger.info("In reset_seed_dialog")
parent = self.top_level_window()
d = WindowModalDialog(parent, _("Enter PIN"))
pw = QLineEdit()
pw.setEchoMode(2)
pw.setMinimumWidth(200)

cb_reset_2FA = QCheckBox(_('Also reset 2FA'))


vbox = QVBoxLayout()
vbox.addWidget(WWLabel(msg))
vbox.addWidget(pw)
vbox.addWidget(cb_reset_2FA)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox)

passphrase = pw.text() if d.exec_() else None
reset_2FA= cb_reset_2FA.isChecked()
return (passphrase, reset_2FA)

Loading

0 comments on commit f137f8e

Please sign in to comment.