2015-07-05 13:14:53 -07:00
from PyQt4 . Qt import QApplication , QMessageBox , QDialog , QInputDialog , QLineEdit , QVBoxLayout , QLabel , QThread , SIGNAL
2014-08-24 10:44:26 -07:00
import PyQt4 . QtCore as QtCore
from binascii import unhexlify
from binascii import hexlify
from struct import pack , unpack
from sys import stderr
from time import sleep
2014-08-31 06:33:20 -07:00
import electrum
2014-08-24 10:44:26 -07:00
from electrum_gui . qt . password_dialog import make_password_dialog , run_password_dialog
from electrum . account import BIP32_Account
2014-08-28 15:38:16 -07:00
from electrum . bitcoin import EncodeBase58Check , DecodeBase58Check , public_key_to_bc_address , bc_address_to_hash_160
2014-08-24 10:44:26 -07:00
from electrum . i18n import _
2014-08-31 02:42:40 -07:00
from electrum . plugins import BasePlugin , hook
2014-08-24 10:44:26 -07:00
from electrum . transaction import deserialize
2015-05-25 15:09:26 -07:00
from electrum . wallet import BIP32_HD_Wallet , BIP32_Wallet
2014-08-24 10:44:26 -07:00
2015-07-05 13:14:53 -07:00
from electrum . util import format_satoshis_plain , print_error , print_msg
2014-08-24 10:44:26 -07:00
import hashlib
2015-07-05 13:14:53 -07:00
import threading
2014-08-24 10:44:26 -07:00
try :
from btchip . btchipComm import getDongle , DongleWait
from btchip . btchip import btchip
from btchip . btchipUtils import compress_public_key , format_transaction , get_regular_input_script
from btchip . bitcoinTransaction import bitcoinTransaction
2014-08-27 14:19:14 -07:00
from btchip . btchipPersoWizard import StartBTChipPersoDialog
2014-09-19 07:02:09 -07:00
from btchip . btchipFirmwareWizard import checkFirmware , updateFirmware
2014-08-27 14:19:14 -07:00
from btchip . btchipException import BTChipException
2014-08-24 10:44:26 -07:00
BTCHIP = True
2015-07-04 00:45:08 -07:00
BTCHIP_DEBUG = False
2014-08-24 10:44:26 -07:00
except ImportError :
BTCHIP = False
class Plugin ( BasePlugin ) :
2015-09-03 17:07:18 -07:00
def __init__ ( self , parent , config , name ) :
BasePlugin . __init__ ( self , parent , config , name )
2014-08-24 10:44:26 -07:00
self . _is_available = self . _init ( )
2015-02-01 01:44:29 -08:00
self . wallet = None
2015-07-05 13:14:53 -07:00
self . handler = None
2015-05-23 01:38:19 -07:00
2015-05-24 11:37:05 -07:00
def constructor ( self , s ) :
return BTChipWallet ( s )
2014-08-24 10:44:26 -07:00
def _init ( self ) :
return BTCHIP
2015-07-04 00:45:08 -07:00
def is_available ( self ) :
2015-01-31 17:29:21 -08:00
if not self . _is_available :
return False
if not self . wallet :
return False
if self . wallet . storage . get ( ' wallet_type ' ) != ' btchip ' :
return False
return True
2014-08-24 10:44:26 -07:00
def set_enabled ( self , enabled ) :
self . wallet . storage . put ( ' use_ ' + self . name , enabled )
def is_enabled ( self ) :
if not self . is_available ( ) :
return False
2015-01-31 17:29:21 -08:00
if self . wallet . has_seed ( ) :
return False
return True
2014-08-24 10:44:26 -07:00
2015-01-31 17:29:21 -08:00
def btchip_is_connected ( self ) :
try :
self . wallet . get_client ( ) . getFirmwareVersion ( )
except :
return False
return True
2015-07-05 13:14:53 -07:00
@hook
def cmdline_load_wallet ( self , wallet ) :
self . wallet = wallet
self . wallet . plugin = self
if self . handler is None :
self . handler = BTChipCmdLineHandler ( )
2014-08-31 02:42:40 -07:00
@hook
2015-06-09 00:58:40 -07:00
def load_wallet ( self , wallet , window ) :
2015-06-10 17:13:12 -07:00
self . wallet = wallet
2015-07-05 13:14:53 -07:00
self . wallet . plugin = self
2015-06-10 17:13:12 -07:00
self . window = window
2015-07-05 13:14:53 -07:00
if self . handler is None :
self . handler = BTChipQTHandler ( self . window . app )
2015-01-31 17:29:21 -08:00
if self . btchip_is_connected ( ) :
if not self . wallet . check_proper_device ( ) :
2015-08-14 04:34:22 -07:00
QMessageBox . information ( self . window , _ ( ' Error ' ) , _ ( " This wallet does not match your Ledger device " ) , _ ( ' OK ' ) )
2015-01-31 17:29:21 -08:00
self . wallet . force_watching_only = True
else :
2015-08-14 04:34:22 -07:00
QMessageBox . information ( self . window , _ ( ' Error ' ) , _ ( " Ledger device not detected. \n Continuing in watching-only mode. " ) , _ ( ' OK ' ) )
2015-01-31 17:29:21 -08:00
self . wallet . force_watching_only = True
2015-07-04 00:45:08 -07:00
2015-08-13 14:28:36 -07:00
@hook
def installwizard_load_wallet ( self , wallet , window ) :
self . load_wallet ( wallet , window )
2014-08-31 02:42:40 -07:00
@hook
2014-08-24 10:44:26 -07:00
def installwizard_restore ( self , wizard , storage ) :
2014-08-31 03:40:57 -07:00
if storage . get ( ' wallet_type ' ) != ' btchip ' :
return
2014-08-24 10:44:26 -07:00
wallet = BTChipWallet ( storage )
try :
wallet . create_main_account ( None )
except BaseException as e :
QMessageBox . information ( None , _ ( ' Error ' ) , str ( e ) , _ ( ' OK ' ) )
return
return wallet
2014-08-31 02:42:40 -07:00
@hook
2015-07-04 00:45:08 -07:00
def sign_tx ( self , tx ) :
2014-10-23 22:49:20 -07:00
tx . error = None
2014-08-24 10:44:26 -07:00
try :
2014-10-30 13:10:12 -07:00
self . wallet . sign_transaction ( tx , None )
2014-08-24 10:44:26 -07:00
except Exception as e :
tx . error = str ( e )
2015-01-31 17:29:21 -08:00
class BTChipWallet ( BIP32_HD_Wallet ) :
2014-08-24 10:44:26 -07:00
wallet_type = ' btchip '
2015-01-31 17:29:21 -08:00
root_derivation = " m/44 ' /0 ' "
2014-08-24 10:44:26 -07:00
def __init__ ( self , storage ) :
2015-01-31 17:29:21 -08:00
BIP32_HD_Wallet . __init__ ( self , storage )
2014-08-24 10:44:26 -07:00
self . transport = None
self . client = None
self . mpk = None
self . device_checked = False
2014-09-20 05:27:13 -07:00
self . signing = False
2015-01-31 17:29:21 -08:00
self . force_watching_only = False
2014-08-24 10:44:26 -07:00
2014-09-19 04:39:12 -07:00
def give_error ( self , message , clear_client = False ) :
2015-07-05 13:14:53 -07:00
print_error ( message )
2014-09-19 04:39:12 -07:00
if not self . signing :
QMessageBox . warning ( QDialog ( ) , _ ( ' Warning ' ) , _ ( message ) , _ ( ' OK ' ) )
else :
self . signing = False
2014-09-20 15:49:36 -07:00
if clear_client and self . client is not None :
2014-09-19 04:39:12 -07:00
self . client . bad = True
2014-09-20 15:49:36 -07:00
self . device_checked = False
2015-07-04 00:45:08 -07:00
raise Exception ( message )
2014-09-19 04:39:12 -07:00
2014-08-24 10:44:26 -07:00
def get_action ( self ) :
if not self . accounts :
return ' create_accounts '
2015-07-05 09:33:16 -07:00
def can_sign_xpubkey ( self , x_pubkey ) :
xpub , sequence = BIP32_Account . parse_xpubkey ( x_pubkey )
return xpub in self . master_public_keys . values ( )
2014-08-24 10:44:26 -07:00
def can_create_accounts ( self ) :
2015-05-25 15:06:28 -07:00
return False
def synchronize ( self ) :
# synchronize existing accounts
BIP32_Wallet . synchronize ( self )
# no further accounts for the moment
2014-08-24 10:44:26 -07:00
def can_change_password ( self ) :
return False
def is_watching_only ( self ) :
2015-01-31 17:29:21 -08:00
return self . force_watching_only
2014-08-24 10:44:26 -07:00
def get_client ( self , noPin = False ) :
if not BTCHIP :
2014-09-19 04:39:12 -07:00
self . give_error ( ' please install github.com/btchip/btchip-python ' )
2014-08-24 10:44:26 -07:00
aborted = False
if not self . client or self . client . bad :
2015-09-03 17:07:18 -07:00
try :
2014-08-31 09:55:31 -07:00
d = getDongle ( BTCHIP_DEBUG )
2014-08-24 10:44:26 -07:00
self . client = btchip ( d )
2015-09-03 17:07:18 -07:00
self . client . handler = self . plugin . handler
2014-08-24 10:44:26 -07:00
firmware = self . client . getFirmwareVersion ( ) [ ' version ' ] . split ( " . " )
2015-07-04 00:45:08 -07:00
if not checkFirmware ( firmware ) :
2014-09-19 06:51:10 -07:00
d . close ( )
try :
updateFirmware ( )
except Exception , e :
aborted = True
raise e
d = getDongle ( BTCHIP_DEBUG )
2015-07-04 00:45:08 -07:00
self . client = btchip ( d )
2014-08-27 14:19:14 -07:00
try :
self . client . getOperationMode ( )
2014-08-28 15:38:16 -07:00
except BTChipException , e :
2014-08-27 14:19:14 -07:00
if ( e . sw == 0x6985 ) :
d . close ( )
2015-07-04 00:45:08 -07:00
dialog = StartBTChipPersoDialog ( )
2014-08-27 14:19:14 -07:00
dialog . exec_ ( )
# Then fetch the reference again as it was invalidated
2014-08-31 09:55:31 -07:00
d = getDongle ( BTCHIP_DEBUG )
2014-08-27 14:19:14 -07:00
self . client = btchip ( d )
else :
raise e
2015-07-04 00:45:08 -07:00
if not noPin :
2014-08-24 10:44:26 -07:00
# Immediately prompts for the PIN
2015-07-04 00:45:08 -07:00
remaining_attempts = self . client . getVerifyPinRemainingAttempts ( )
2014-08-28 15:38:16 -07:00
if remaining_attempts < > 1 :
2015-08-14 04:34:22 -07:00
msg = " Enter your Ledger PIN - remaining attempts : " + str ( remaining_attempts )
2014-08-28 15:38:16 -07:00
else :
2015-08-14 04:34:22 -07:00
msg = " Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped. "
2015-07-04 00:45:08 -07:00
confirmed , p , pin = self . password_dialog ( msg )
2014-08-24 10:44:26 -07:00
if not confirmed :
aborted = True
2014-08-28 15:38:16 -07:00
raise Exception ( ' Aborted by user - please unplug the dongle and plug it again before retrying ' )
2015-07-04 00:45:08 -07:00
pin = pin . encode ( )
2014-08-24 10:44:26 -07:00
self . client . verifyPin ( pin )
2014-08-28 15:38:16 -07:00
except BTChipException , e :
try :
self . client . dongle . close ( )
except :
pass
2015-07-04 00:45:08 -07:00
self . client = None
2014-08-28 15:38:16 -07:00
if ( e . sw == 0x6faa ) :
2015-07-04 00:45:08 -07:00
raise Exception ( " Dongle is temporarily locked - please unplug it and replug it again " )
2014-08-28 15:38:16 -07:00
if ( ( e . sw & 0xFFF0 ) == 0x63c0 ) :
raise Exception ( " Invalid PIN - please unplug the dongle and plug it again before retrying " )
raise e
2014-08-24 10:44:26 -07:00
except Exception , e :
2015-07-04 00:45:08 -07:00
try :
2014-08-28 15:38:16 -07:00
self . client . dongle . close ( )
except :
2015-07-04 00:45:08 -07:00
pass
self . client = None
2014-08-24 10:44:26 -07:00
if not aborted :
2015-08-14 04:34:22 -07:00
raise Exception ( " Could not connect to your Ledger wallet. Please verify access permissions, PIN, or unplug the dongle and plug it again " )
2014-08-24 10:44:26 -07:00
else :
raise e
self . client . bad = False
self . device_checked = False
self . proper_device = False
return self . client
def address_id ( self , address ) :
account_id , ( change , address_index ) = self . get_address_index ( address )
return " 44 ' /0 ' / %s ' / %d / %d " % ( account_id , change , address_index )
def create_main_account ( self , password ) :
self . create_account ( ' Main account ' , None ) #name, empty password
def derive_xkeys ( self , root , derivation , password ) :
derivation = derivation . replace ( self . root_name , " 44 ' /0 ' / " )
xpub = self . get_public_key ( derivation )
return xpub , None
2014-09-20 05:46:26 -07:00
def get_private_key ( self , address , password ) :
return [ ]
2014-08-24 10:44:26 -07:00
def get_public_key ( self , bip32_path ) :
2015-07-04 00:45:08 -07:00
# S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node
2014-08-24 10:44:26 -07:00
# This only happens once so it's bearable
2015-07-04 00:45:08 -07:00
self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
2015-07-05 13:14:53 -07:00
self . plugin . handler . show_message ( " Computing master public key " )
2015-07-04 00:45:08 -07:00
try :
2014-08-24 10:44:26 -07:00
splitPath = bip32_path . split ( ' / ' )
2015-07-04 00:45:08 -07:00
fingerprint = 0
2014-08-24 10:44:26 -07:00
if len ( splitPath ) > 1 :
prevPath = " / " . join ( splitPath [ 0 : len ( splitPath ) - 1 ] )
nodeData = self . get_client ( ) . getWalletPublicKey ( prevPath )
publicKey = compress_public_key ( nodeData [ ' publicKey ' ] )
h = hashlib . new ( ' ripemd160 ' )
h . update ( hashlib . sha256 ( publicKey ) . digest ( ) )
2015-07-04 00:45:08 -07:00
fingerprint = unpack ( " >I " , h . digest ( ) [ 0 : 4 ] ) [ 0 ]
2014-08-24 10:44:26 -07:00
nodeData = self . get_client ( ) . getWalletPublicKey ( bip32_path )
publicKey = compress_public_key ( nodeData [ ' publicKey ' ] )
depth = len ( splitPath )
lastChild = splitPath [ len ( splitPath ) - 1 ] . split ( ' \' ' )
if len ( lastChild ) == 1 :
childnum = int ( lastChild [ 0 ] )
else :
2015-07-04 00:45:08 -07:00
childnum = 0x80000000 | int ( lastChild [ 0 ] )
2014-08-24 10:44:26 -07:00
xpub = " 0488B21E " . decode ( ' hex ' ) + chr ( depth ) + self . i4b ( fingerprint ) + self . i4b ( childnum ) + str ( nodeData [ ' chainCode ' ] ) + str ( publicKey )
except Exception , e :
2014-09-19 04:39:12 -07:00
self . give_error ( e , True )
2014-08-24 10:44:26 -07:00
finally :
2015-07-05 13:14:53 -07:00
self . plugin . handler . stop ( )
2014-08-24 10:44:26 -07:00
return EncodeBase58Check ( xpub )
def get_master_public_key ( self ) :
2014-09-20 15:49:36 -07:00
try :
if not self . mpk :
self . mpk = self . get_public_key ( " 44 ' /0 ' " )
return self . mpk
except Exception , e :
2015-07-04 00:45:08 -07:00
self . give_error ( e , True )
2014-08-24 10:44:26 -07:00
def i4b ( self , x ) :
return pack ( ' >I ' , x )
def add_keypairs ( self , tx , keypairs , password ) :
#do nothing - no priv keys available
pass
def decrypt_message ( self , pubkey , message , password ) :
2014-09-19 04:39:12 -07:00
self . give_error ( " Not supported " )
2014-08-24 10:44:26 -07:00
def sign_message ( self , address , message , password ) :
2014-08-28 15:38:16 -07:00
use2FA = False
2015-01-31 17:29:21 -08:00
self . signing = True
2014-08-26 04:04:38 -07:00
self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
if not self . check_proper_device ( ) :
2015-07-04 00:45:08 -07:00
self . give_error ( ' Wrong device or password ' )
2014-08-24 10:44:26 -07:00
address_path = self . address_id ( address )
2015-07-05 13:14:53 -07:00
self . plugin . handler . show_message ( " Signing message ... " )
2014-08-24 10:44:26 -07:00
try :
info = self . get_client ( ) . signMessagePrepare ( address_path , message )
pin = " "
2015-07-04 00:45:08 -07:00
if info [ ' confirmationNeeded ' ] :
2014-08-24 10:44:26 -07:00
# TODO : handle different confirmation types. For the time being only supports keyboard 2FA
2014-08-28 15:38:16 -07:00
use2FA = True
2014-08-24 10:44:26 -07:00
confirmed , p , pin = self . password_dialog ( )
if not confirmed :
2014-08-28 15:38:16 -07:00
raise Exception ( ' Aborted by user ' )
2014-08-24 10:44:26 -07:00
pin = pin . encode ( )
self . client . bad = True
2014-09-20 15:49:36 -07:00
self . device_checked = False
2014-08-24 10:44:26 -07:00
self . get_client ( True )
signature = self . get_client ( ) . signMessageSign ( pin )
2015-02-23 07:30:33 -08:00
except BTChipException , e :
2015-01-31 17:29:21 -08:00
if e . sw == 0x6a80 :
2015-08-14 04:34:22 -07:00
self . give_error ( " Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry. " )
2015-07-04 00:45:08 -07:00
else :
self . give_error ( e , True )
2015-02-23 07:30:33 -08:00
except Exception , e :
self . give_error ( e , True )
2014-08-24 10:44:26 -07:00
finally :
2015-07-05 13:14:53 -07:00
self . plugin . handler . stop ( )
2014-08-28 15:38:16 -07:00
self . client . bad = use2FA
2015-01-31 17:29:21 -08:00
self . signing = False
2014-08-24 10:44:26 -07:00
# Parse the ASN.1 signature
rLength = signature [ 3 ]
r = signature [ 4 : 4 + rLength ]
sLength = signature [ 4 + rLength + 1 ]
s = signature [ 4 + rLength + 2 : ]
if rLength == 33 :
r = r [ 1 : ]
if sLength == 33 :
s = s [ 1 : ]
r = str ( r )
s = str ( s )
# And convert it
2015-07-14 07:37:04 -07:00
return chr ( 27 + 4 + ( signature [ 0 ] & 0x01 ) ) + r + s
2014-08-24 10:44:26 -07:00
2014-10-30 13:10:12 -07:00
def sign_transaction ( self , tx , password ) :
2014-10-24 01:53:09 -07:00
if tx . is_complete ( ) :
2014-09-19 04:39:12 -07:00
return
2015-07-05 13:14:53 -07:00
#if tx.error:
# raise BaseException(tx.error)
2015-07-04 00:45:08 -07:00
self . signing = True
2014-08-24 10:44:26 -07:00
inputs = [ ]
inputsPaths = [ ]
pubKeys = [ ]
trustedInputs = [ ]
2015-07-04 00:45:08 -07:00
redeemScripts = [ ]
2014-08-24 10:44:26 -07:00
signatures = [ ]
preparedTrustedInputs = [ ]
2015-07-04 00:45:08 -07:00
changePath = " "
2014-08-24 10:44:26 -07:00
changeAmount = None
output = None
outputAmount = None
use2FA = False
2014-08-27 14:19:14 -07:00
pin = " "
2015-06-14 02:25:26 -07:00
rawTx = tx . serialize ( )
2014-08-24 10:44:26 -07:00
# Fetch inputs of the transaction to sign
for txinput in tx . inputs :
if ( ' is_coinbase ' in txinput and txinput [ ' is_coinbase ' ] ) :
2014-09-19 04:39:12 -07:00
self . give_error ( " Coinbase not supported " ) # should never happen
2015-07-04 00:45:08 -07:00
inputs . append ( [ self . transactions [ txinput [ ' prevout_hash ' ] ] . raw ,
txinput [ ' prevout_n ' ] ] )
2014-08-24 10:44:26 -07:00
address = txinput [ ' address ' ]
inputsPaths . append ( self . address_id ( address ) )
pubKeys . append ( self . get_public_keys ( address ) )
# Recognize outputs - only one output and one change is authorized
if len ( tx . outputs ) > 2 : # should never happen
2014-09-19 04:39:12 -07:00
self . give_error ( " Transaction with more than 2 outputs not supported " )
2015-07-04 00:45:08 -07:00
for type , address , amount in tx . outputs :
2014-08-24 10:44:26 -07:00
assert type == ' address '
if self . is_change ( address ) :
changePath = self . address_id ( address )
changeAmount = amount
else :
if output < > None : # should never happen
2014-09-19 04:39:12 -07:00
self . give_error ( " Multiple outputs with no change not supported " )
2014-08-24 10:44:26 -07:00
output = address
outputAmount = amount
self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
if not self . check_proper_device ( ) :
2014-09-19 04:39:12 -07:00
self . give_error ( ' Wrong device or password ' )
2014-08-24 10:44:26 -07:00
2015-07-05 13:14:53 -07:00
self . plugin . handler . show_message ( " Signing Transaction ... " )
2014-08-24 10:44:26 -07:00
try :
# Get trusted inputs from the original transactions
for utxo in inputs :
2015-07-04 00:45:08 -07:00
txtmp = bitcoinTransaction ( bytearray ( utxo [ 0 ] . decode ( ' hex ' ) ) )
2014-08-24 10:44:26 -07:00
trustedInputs . append ( self . get_client ( ) . getTrustedInput ( txtmp , utxo [ 1 ] ) )
# TODO : Support P2SH later
redeemScripts . append ( txtmp . outputs [ utxo [ 1 ] ] . script )
# Sign all inputs
firstTransaction = True
inputIndex = 0
while inputIndex < len ( inputs ) :
2015-07-04 00:45:08 -07:00
self . get_client ( ) . startUntrustedTransaction ( firstTransaction , inputIndex ,
2014-08-24 10:44:26 -07:00
trustedInputs , redeemScripts [ inputIndex ] )
2015-07-04 00:45:08 -07:00
outputData = self . get_client ( ) . finalizeInput ( output , format_satoshis_plain ( outputAmount ) ,
2015-06-23 15:39:39 -07:00
format_satoshis_plain ( self . get_tx_fee ( tx ) ) , changePath , bytearray ( rawTx . decode ( ' hex ' ) ) )
2014-08-24 10:44:26 -07:00
if firstTransaction :
transactionOutput = outputData [ ' outputData ' ]
2015-09-03 17:07:18 -07:00
if outputData [ ' confirmationNeeded ' ] :
2014-08-24 10:44:26 -07:00
# TODO : handle different confirmation types. For the time being only supports keyboard 2FA
2015-07-05 13:14:53 -07:00
self . plugin . handler . stop ( )
2015-06-14 02:25:26 -07:00
if ' keycardData ' in outputData :
pin2 = " "
for keycardIndex in range ( len ( outputData [ ' keycardData ' ] ) ) :
msg = " Do not enter your device PIN here ! \r \n \r \n " + \
2015-08-14 04:34:22 -07:00
" Your Ledger Wallet wants to talk to you and tell you a unique second factor code. \r \n " + \
2015-06-14 02:25:26 -07:00
" For this to work, please match the character between stars of the output address using your security card \r \n \r \n " + \
2015-07-04 00:45:08 -07:00
" Output address : "
2015-06-14 02:25:26 -07:00
for index in range ( len ( output ) ) :
if index == outputData [ ' keycardData ' ] [ keycardIndex ] :
msg = msg + " * " + output [ index ] + " * "
else :
msg = msg + output [ index ]
2015-07-04 00:45:08 -07:00
msg = msg + " \r \n "
2015-06-14 02:25:26 -07:00
confirmed , p , pin = self . password_dialog ( msg )
if not confirmed :
2015-07-04 00:45:08 -07:00
raise Exception ( ' Aborted by user ' )
2015-06-14 02:25:26 -07:00
try :
pin2 = pin2 + chr ( int ( pin [ 0 ] , 16 ) )
except :
raise Exception ( ' Invalid PIN character ' )
pin = pin2
else :
2015-07-05 13:14:53 -07:00
use2FA = True
2015-06-14 02:25:26 -07:00
confirmed , p , pin = self . password_dialog ( )
if not confirmed :
2015-07-04 00:45:08 -07:00
raise Exception ( ' Aborted by user ' )
2015-06-14 02:25:26 -07:00
pin = pin . encode ( )
2015-06-24 23:17:45 -07:00
self . client . bad = True
self . device_checked = False
self . get_client ( True )
2015-07-05 13:14:53 -07:00
self . plugin . handler . show_message ( " Signing ... " )
2014-08-24 10:44:26 -07:00
else :
# Sign input with the provided PIN
2014-08-27 14:19:14 -07:00
inputSignature = self . get_client ( ) . untrustedHashSign ( inputsPaths [ inputIndex ] ,
pin )
inputSignature [ 0 ] = 0x30 # force for 1.4.9+
signatures . append ( inputSignature )
2014-08-24 10:44:26 -07:00
inputIndex = inputIndex + 1
firstTransaction = False
except Exception , e :
2014-09-19 04:39:12 -07:00
self . give_error ( e , True )
2014-08-24 10:44:26 -07:00
finally :
2015-07-05 13:14:53 -07:00
self . plugin . handler . stop ( )
2014-08-24 10:44:26 -07:00
# Reformat transaction
inputIndex = 0
while inputIndex < len ( inputs ) :
# TODO : Support P2SH later
2015-07-04 00:45:08 -07:00
inputScript = get_regular_input_script ( signatures [ inputIndex ] , pubKeys [ inputIndex ] [ 0 ] . decode ( ' hex ' ) )
2014-08-24 10:44:26 -07:00
preparedTrustedInputs . append ( [ trustedInputs [ inputIndex ] [ ' value ' ] , inputScript ] )
inputIndex = inputIndex + 1
updatedTransaction = format_transaction ( transactionOutput , preparedTrustedInputs )
updatedTransaction = hexlify ( updatedTransaction )
tx . update ( updatedTransaction )
self . client . bad = use2FA
2014-09-19 04:39:12 -07:00
self . signing = False
2014-08-24 10:44:26 -07:00
def check_proper_device ( self ) :
pubKey = DecodeBase58Check ( self . master_public_keys [ " x/0 ' " ] ) [ 45 : ]
if not self . device_checked :
2015-07-05 13:14:53 -07:00
self . plugin . handler . show_message ( " Checking device " )
2014-08-24 10:44:26 -07:00
try :
nodeData = self . get_client ( ) . getWalletPublicKey ( " 44 ' /0 ' /0 ' " )
except Exception , e :
2014-09-19 04:39:12 -07:00
self . give_error ( e , True )
2014-08-24 10:44:26 -07:00
finally :
2015-07-05 13:14:53 -07:00
self . plugin . handler . stop ( )
2014-08-24 10:44:26 -07:00
pubKeyDevice = compress_public_key ( nodeData [ ' publicKey ' ] )
self . device_checked = True
if pubKey != pubKeyDevice :
self . proper_device = False
else :
self . proper_device = True
return self . proper_device
def password_dialog ( self , msg = None ) :
if not msg :
2014-09-19 04:39:12 -07:00
msg = _ ( " Do not enter your device PIN here ! \r \n \r \n " \
2015-08-14 04:34:22 -07:00
" Your Ledger Wallet wants to talk to you and tell you a unique second factor code. \r \n " \
" For this to work, please open a text editor (on a different computer / device if you believe this computer is compromised) and put your cursor into it, unplug your Ledger Wallet and plug it back in. \r \n " \
2014-09-19 11:23:28 -07:00
" 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 " \
2014-09-19 04:39:12 -07:00
" 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) " )
2015-07-05 13:14:53 -07:00
response = self . plugin . handler . prompt_auth ( msg )
if response is None :
return False , 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 )
2015-07-06 06:46:12 -07:00
self . win . connect ( win , SIGNAL ( ' btchip_message_dialog ' ) , self . message_dialog )
self . win . connect ( win , SIGNAL ( ' btchip_auth_dialog ' ) , self . auth_dialog )
2015-07-05 13:14:53 -07:00
self . done = threading . Event ( )
2014-08-24 10:44:26 -07:00
2015-07-05 13:14:53 -07:00
def stop ( self ) :
self . win . emit ( SIGNAL ( ' btchip_done ' ) )
def show_message ( self , msg ) :
self . message = msg
2015-07-06 06:46:12 -07:00
self . win . emit ( SIGNAL ( ' btchip_message_dialog ' ) )
2015-07-05 13:14:53 -07:00
def prompt_auth ( self , msg ) :
self . done . clear ( )
self . message = msg
2015-07-06 06:46:12 -07:00
self . win . emit ( SIGNAL ( ' btchip_auth_dialog ' ) )
2015-07-05 13:14:53 -07:00
self . done . wait ( )
return self . response
def auth_dialog ( self ) :
2015-09-03 17:07:18 -07:00
response = QInputDialog . getText ( None , " Ledger Wallet Authentication " , self . message , QLineEdit . Password )
2015-07-05 13:14:53 -07:00
if not response [ 1 ] :
self . response = None
else :
self . response = str ( response [ 0 ] )
self . done . set ( )
2014-08-24 10:44:26 -07:00
2015-07-05 13:14:53 -07:00
def message_dialog ( self ) :
2014-08-24 10:44:26 -07:00
self . d = QDialog ( )
self . d . setModal ( 1 )
2015-08-14 04:34:22 -07:00
self . d . setWindowTitle ( ' Ledger ' )
2014-08-24 10:44:26 -07:00
self . d . setWindowFlags ( self . d . windowFlags ( ) | QtCore . Qt . WindowStaysOnTopHint )
2015-07-05 13:14:53 -07:00
l = QLabel ( self . message )
2014-08-24 10:44:26 -07:00
vbox = QVBoxLayout ( self . d )
vbox . addWidget ( l )
self . d . show ( )
2015-07-05 13:14:53 -07:00
def dialog_stop ( self ) :
if self . d is not None :
self . d . hide ( )
self . d = None
2014-08-24 10:44:26 -07:00
2015-07-05 13:14:53 -07:00
class BTChipCmdLineHandler :
def stop ( self ) :
pass
2014-08-24 10:44:26 -07:00
2015-07-05 13:14:53 -07:00
def show_message ( self , msg ) :
print_msg ( msg )
2014-09-02 00:00:20 -07:00
2015-07-05 13:14:53 -07:00
def prompt_auth ( self , msg ) :
2015-09-03 17:07:18 -07:00
import getpass
2015-07-05 13:14:53 -07:00
print_msg ( msg )
response = getpass . getpass ( ' ' )
if len ( response ) == 0 :
return None
return response