@ -1,4 +1,4 @@
from PyQt4 . Qt import QApplication , QMessageBox , QDialog , QVBoxLayout , QLabel , QThread , SIGNAL
from PyQt4 . Qt import QApplication , QMessageBox , QDialog , QInputDialog , QLineEdit , Q VBoxLayout , QLabel , QThread , SIGNAL
import PyQt4 . QtCore as QtCore
import PyQt4 . QtCore as QtCore
from binascii import unhexlify
from binascii import unhexlify
from binascii import hexlify
from binascii import hexlify
@ -16,8 +16,9 @@ from electrum.plugins import BasePlugin, hook
from electrum . transaction import deserialize
from electrum . transaction import deserialize
from electrum . wallet import BIP32_HD_Wallet , BIP32_Wallet
from electrum . wallet import BIP32_HD_Wallet , BIP32_Wallet
from electrum . util import format_satoshis_plain
from electrum . util import format_satoshis_plain , print_error , print_msg
import hashlib
import hashlib
import threading
try :
try :
from btchip . btchipComm import getDongle , DongleWait
from btchip . btchipComm import getDongle , DongleWait
@ -38,6 +39,7 @@ class Plugin(BasePlugin):
BasePlugin . __init__ ( self , gui , name )
BasePlugin . __init__ ( self , gui , name )
self . _is_available = self . _init ( )
self . _is_available = self . _init ( )
self . wallet = None
self . wallet = None
self . handler = None
def constructor ( self , s ) :
def constructor ( self , s ) :
return BTChipWallet ( s )
return BTChipWallet ( s )
@ -71,10 +73,20 @@ class Plugin(BasePlugin):
return False
return False
return True
return True
@hook
def cmdline_load_wallet ( self , wallet ) :
self . wallet = wallet
self . wallet . plugin = self
if self . handler is None :
self . handler = BTChipCmdLineHandler ( )
@hook
@hook
def load_wallet ( self , wallet , window ) :
def load_wallet ( self , wallet , window ) :
self . wallet = wallet
self . wallet = wallet
self . wallet . plugin = self
self . window = window
self . window = window
if self . handler is None :
self . handler = BTChipQTHandler ( self . window . app )
if self . btchip_is_connected ( ) :
if self . btchip_is_connected ( ) :
if not self . wallet . check_proper_device ( ) :
if not self . wallet . check_proper_device ( ) :
QMessageBox . information ( self . window , _ ( ' Error ' ) , _ ( " This wallet does not match your BTChip device " ) , _ ( ' OK ' ) )
QMessageBox . information ( self . window , _ ( ' Error ' ) , _ ( " This wallet does not match your BTChip device " ) , _ ( ' OK ' ) )
@ -117,6 +129,7 @@ class BTChipWallet(BIP32_HD_Wallet):
self . force_watching_only = False
self . force_watching_only = False
def give_error ( self , message , clear_client = False ) :
def give_error ( self , message , clear_client = False ) :
print_error ( message )
if not self . signing :
if not self . signing :
QMessageBox . warning ( QDialog ( ) , _ ( ' Warning ' ) , _ ( message ) , _ ( ' OK ' ) )
QMessageBox . warning ( QDialog ( ) , _ ( ' Warning ' ) , _ ( message ) , _ ( ' OK ' ) )
else :
else :
@ -154,10 +167,10 @@ class BTChipWallet(BIP32_HD_Wallet):
aborted = False
aborted = False
if not self . client or self . client . bad :
if not self . client or self . client . bad :
try :
try :
d = getDongle ( BTCHIP_DEBUG )
d = getDongle ( BTCHIP_DEBUG )
d . setWaitImpl ( DongleWaitQT ( d ) )
self . client = btchip ( d )
self . client = btchip ( d )
self . client . handler = self . plugin . handler
firmware = self . client . getFirmwareVersion ( ) [ ' version ' ] . split ( " . " )
firmware = self . client . getFirmwareVersion ( ) [ ' version ' ] . split ( " . " )
if not checkFirmware ( firmware ) :
if not checkFirmware ( firmware ) :
d . close ( )
d . close ( )
@ -167,7 +180,6 @@ class BTChipWallet(BIP32_HD_Wallet):
aborted = True
aborted = True
raise e
raise e
d = getDongle ( BTCHIP_DEBUG )
d = getDongle ( BTCHIP_DEBUG )
d . setWaitImpl ( DongleWaitQT ( d ) )
self . client = btchip ( d )
self . client = btchip ( d )
try :
try :
self . client . getOperationMode ( )
self . client . getOperationMode ( )
@ -178,7 +190,6 @@ class BTChipWallet(BIP32_HD_Wallet):
dialog . exec_ ( )
dialog . exec_ ( )
# Then fetch the reference again as it was invalidated
# Then fetch the reference again as it was invalidated
d = getDongle ( BTCHIP_DEBUG )
d = getDongle ( BTCHIP_DEBUG )
d . setWaitImpl ( DongleWaitQT ( d ) )
self . client = btchip ( d )
self . client = btchip ( d )
else :
else :
raise e
raise e
@ -241,7 +252,7 @@ class BTChipWallet(BIP32_HD_Wallet):
# S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node
# S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node
# This only happens once so it's bearable
# This only happens once so it's bearable
self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
waitDialog . start ( " Computing master public key " )
self . plugin . handler . show_message ( " Computing master public key " )
try :
try :
splitPath = bip32_path . split ( ' / ' )
splitPath = bip32_path . split ( ' / ' )
fingerprint = 0
fingerprint = 0
@ -264,7 +275,7 @@ class BTChipWallet(BIP32_HD_Wallet):
except Exception , e :
except Exception , e :
self . give_error ( e , True )
self . give_error ( e , True )
finally :
finally :
waitDialog . emit ( SIGNAL ( ' dongle_done ' ) )
self . plugin . handler . stop ( )
return EncodeBase58Check ( xpub )
return EncodeBase58Check ( xpub )
@ -293,7 +304,7 @@ class BTChipWallet(BIP32_HD_Wallet):
if not self . check_proper_device ( ) :
if not self . check_proper_device ( ) :
self . give_error ( ' Wrong device or password ' )
self . give_error ( ' Wrong device or password ' )
address_path = self . address_id ( address )
address_path = self . address_id ( address )
waitDialog . start ( " Signing M essage ... " )
self . plugin . handler . show_message ( " Signing m essage ... " )
try :
try :
info = self . get_client ( ) . signMessagePrepare ( address_path , message )
info = self . get_client ( ) . signMessagePrepare ( address_path , message )
pin = " "
pin = " "
@ -316,8 +327,7 @@ class BTChipWallet(BIP32_HD_Wallet):
except Exception , e :
except Exception , e :
self . give_error ( e , True )
self . give_error ( e , True )
finally :
finally :
if waitDialog . waiting :
self . plugin . handler . stop ( )
waitDialog . emit ( SIGNAL ( ' dongle_done ' ) )
self . client . bad = use2FA
self . client . bad = use2FA
self . signing = False
self . signing = False
@ -341,8 +351,8 @@ class BTChipWallet(BIP32_HD_Wallet):
def sign_transaction ( self , tx , password ) :
def sign_transaction ( self , tx , password ) :
if tx . is_complete ( ) :
if tx . is_complete ( ) :
return
return
if tx . error :
#if tx.error :
raise BaseException ( tx . error )
# raise BaseException(tx.error )
self . signing = True
self . signing = True
inputs = [ ]
inputs = [ ]
inputsPaths = [ ]
inputsPaths = [ ]
@ -386,7 +396,7 @@ class BTChipWallet(BIP32_HD_Wallet):
if not self . check_proper_device ( ) :
if not self . check_proper_device ( ) :
self . give_error ( ' Wrong device or password ' )
self . give_error ( ' Wrong device or password ' )
waitDialog . start ( " Signing Transaction ... " )
self . plugin . handler . show_message ( " Signing Transaction ... " )
try :
try :
# Get trusted inputs from the original transactions
# Get trusted inputs from the original transactions
for utxo in inputs :
for utxo in inputs :
@ -404,10 +414,9 @@ class BTChipWallet(BIP32_HD_Wallet):
format_satoshis_plain ( self . get_tx_fee ( tx ) ) , changePath , bytearray ( rawTx . decode ( ' hex ' ) ) )
format_satoshis_plain ( self . get_tx_fee ( tx ) ) , changePath , bytearray ( rawTx . decode ( ' hex ' ) ) )
if firstTransaction :
if firstTransaction :
transactionOutput = outputData [ ' outputData ' ]
transactionOutput = outputData [ ' outputData ' ]
if outputData [ ' confirmationNeeded ' ] :
if outputData [ ' confirmationNeeded ' ] :
use2FA = True
# TODO : handle different confirmation types. For the time being only supports keyboard 2FA
# TODO : handle different confirmation types. For the time being only supports keyboard 2FA
waitDialog . emit ( SIGNAL ( ' dongle_done ' ) )
self . plugin . handler . stop ( )
if ' keycardData ' in outputData :
if ' keycardData ' in outputData :
pin2 = " "
pin2 = " "
for keycardIndex in range ( len ( outputData [ ' keycardData ' ] ) ) :
for keycardIndex in range ( len ( outputData [ ' keycardData ' ] ) ) :
@ -430,6 +439,7 @@ class BTChipWallet(BIP32_HD_Wallet):
raise Exception ( ' Invalid PIN character ' )
raise Exception ( ' Invalid PIN character ' )
pin = pin2
pin = pin2
else :
else :
use2FA = True
confirmed , p , pin = self . password_dialog ( )
confirmed , p , pin = self . password_dialog ( )
if not confirmed :
if not confirmed :
raise Exception ( ' Aborted by user ' )
raise Exception ( ' Aborted by user ' )
@ -437,7 +447,7 @@ class BTChipWallet(BIP32_HD_Wallet):
self . client . bad = True
self . client . bad = True
self . device_checked = False
self . device_checked = False
self . get_client ( True )
self . get_client ( True )
waitDialog . start ( " Signing ... " )
self . plugin . handler . show_message ( " Signing ... " )
else :
else :
# Sign input with the provided PIN
# Sign input with the provided PIN
inputSignature = self . get_client ( ) . untrustedHashSign ( inputsPaths [ inputIndex ] ,
inputSignature = self . get_client ( ) . untrustedHashSign ( inputsPaths [ inputIndex ] ,
@ -449,8 +459,7 @@ class BTChipWallet(BIP32_HD_Wallet):
except Exception , e :
except Exception , e :
self . give_error ( e , True )
self . give_error ( e , True )
finally :
finally :
if waitDialog . waiting :
self . plugin . handler . stop ( )
waitDialog . emit ( SIGNAL ( ' dongle_done ' ) )
# Reformat transaction
# Reformat transaction
inputIndex = 0
inputIndex = 0
@ -468,13 +477,13 @@ class BTChipWallet(BIP32_HD_Wallet):
def check_proper_device ( self ) :
def check_proper_device ( self ) :
pubKey = DecodeBase58Check ( self . master_public_keys [ " x/0 ' " ] ) [ 45 : ]
pubKey = DecodeBase58Check ( self . master_public_keys [ " x/0 ' " ] ) [ 45 : ]
if not self . device_checked :
if not self . device_checked :
waitDialog . start ( " Checking device " )
self . plugin . handler . show_message ( " Checking device " )
try :
try :
nodeData = self . get_client ( ) . getWalletPublicKey ( " 44 ' /0 ' /0 ' " )
nodeData = self . get_client ( ) . getWalletPublicKey ( " 44 ' /0 ' /0 ' " )
except Exception , e :
except Exception , e :
self . give_error ( e , True )
self . give_error ( e , True )
finally :
finally :
waitDialog . emit ( SIGNAL ( ' dongle_done ' ) )
self . plugin . handler . stop ( )
pubKeyDevice = compress_public_key ( nodeData [ ' publicKey ' ] )
pubKeyDevice = compress_public_key ( nodeData [ ' publicKey ' ] )
self . device_checked = True
self . device_checked = True
if pubKey != pubKeyDevice :
if pubKey != pubKeyDevice :
@ -492,40 +501,69 @@ class BTChipWallet(BIP32_HD_Wallet):
" It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor. \r \n \r \n " \
" It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor. \r \n \r \n " \
" Check that summary and then enter the second factor code here. \r \n " \
" Check that summary and then enter the second factor code here. \r \n " \
" Before clicking OK, re-plug the device once more (unplug it and plug it again if you read the second factor code on the same computer) " )
" Before clicking OK, re-plug the device once more (unplug it and plug it again if you read the second factor code on the same computer) " )
d = QDialog ( )
response = self . plugin . handler . prompt_auth ( msg )
d . setModal ( 1 )
if response is None :
d . setLayout ( make_password_dialog ( d , None , msg , False ) )
return False , None , None
return run_password_dialog ( d , None , None )
return True , response , response
class BTChipQTHandler :
def __init__ ( self , win ) :
self . win = win
self . win . connect ( win , SIGNAL ( ' btchip_done ' ) , self . dialog_stop )
self . win . connect ( win , SIGNAL ( ' message_dialog ' ) , self . message_dialog )
self . win . connect ( win , SIGNAL ( ' auth_dialog ' ) , self . auth_dialog )
self . done = threading . Event ( )
class DongleWaitingDialog ( QThread ) :
def stop ( self ) :
def __init__ ( self ) :
self . win . emit ( SIGNAL ( ' btchip_done ' ) )
QThread . __init__ ( self )
self . waiting = False
def show_message ( self , msg ) :
self . message = msg
self . win . emit ( SIGNAL ( ' message_dialog ' ) )
def prompt_auth ( self , msg ) :
self . done . clear ( )
self . message = msg
self . win . emit ( SIGNAL ( ' auth_dialog ' ) )
self . done . wait ( )
return self . response
def auth_dialog ( self ) :
response = QInputDialog . getText ( None , " BTChip Authentication " , self . message , QLineEdit . Password )
if not response [ 1 ] :
self . response = None
else :
self . response = str ( response [ 0 ] )
self . done . set ( )
def start ( self , message ) :
def message_dialog ( self ) :
self . d = QDialog ( )
self . d = QDialog ( )
self . d . setModal ( 1 )
self . d . setModal ( 1 )
self . d . setWindowTitle ( ' Please Wait ' )
self . d . setWindowTitle ( ' BTChip ' )
self . d . setWindowFlags ( self . d . windowFlags ( ) | QtCore . Qt . WindowStaysOnTopHint )
self . d . setWindowFlags ( self . d . windowFlags ( ) | QtCore . Qt . WindowStaysOnTopHint )
l = QLabel ( message )
l = QLabel ( self . message )
vbox = QVBoxLayout ( self . d )
vbox = QVBoxLayout ( self . d )
vbox . addWidget ( l )
vbox . addWidget ( l )
self . d . show ( )
self . d . show ( )
if not self . waiting :
self . waiting = True
self . d . connect ( waitDialog , SIGNAL ( ' dongle_done ' ) , self . stop )
def stop ( self ) :
def dialog_stop ( self ) :
self . d . hide ( )
if self . d is not None :
self . waiting = False
self . d . hide ( )
self . d = None
if BTCHIP :
class BTChipCmdLineHandler :
waitDialog = DongleWaitingDialog ( )
def stop ( self ) :
pass
# Tickle the UI a bit while waiting
def show_message ( self , msg ) :
class DongleWaitQT ( DongleWait ) :
print_msg ( msg )
def __init__ ( self , dongle ) :
self . dongle = dongle
def waitFirstResponse ( self , timeout ) :
def prompt_auth ( self , msg ) :
return self . dongle . waitFirstResponse ( timeout )
import getpass
print_msg ( msg )
response = getpass . getpass ( ' ' )
if len ( response ) == 0 :
return None
return response