2016-08-28 16:38:30 +02:00
from struct import pack , unpack
2016-01-30 18:00:51 +09:00
import hashlib
2016-09-22 10:25:03 +02:00
import sys
import traceback
2014-08-24 19:44:26 +02:00
2017-01-30 10:10:21 +01:00
from electrum import bitcoin
from electrum . bitcoin import TYPE_ADDRESS , int_to_hex , var_int
2014-08-24 19:44:26 +02:00
from electrum . i18n import _
2017-11-12 22:54:04 -06:00
from electrum . plugins import BasePlugin
from electrum . keystore import Hardware_KeyStore
from electrum . transaction import Transaction
2016-01-30 18:33:54 +09:00
from . . hw_wallet import HW_PluginBase
2017-11-12 22:54:04 -06:00
from electrum . util import print_error , is_verbose , bfh , bh2u
2016-01-30 07:46:19 +01:00
2014-08-24 19:44:26 +02:00
try :
2016-08-28 16:38:30 +02:00
import hid
from btchip . btchipComm import HIDDongleHIDAPI , DongleWait
2014-08-24 19:44:26 +02:00
from btchip . btchip import btchip
2016-08-28 16:38:30 +02:00
from btchip . btchipUtils import compress_public_key , format_transaction , get_regular_input_script , get_p2sh_input_script
2014-08-24 19:44:26 +02:00
from btchip . bitcoinTransaction import bitcoinTransaction
2014-09-19 16:02:09 +02:00
from btchip . btchipFirmwareWizard import checkFirmware , updateFirmware
2014-08-27 23:19:14 +02:00
from btchip . btchipException import BTChipException
2014-08-24 19:44:26 +02:00
BTCHIP = True
2017-01-25 01:34:35 +01:00
BTCHIP_DEBUG = is_verbose
2014-08-24 19:44:26 +02:00
except ImportError :
BTCHIP = False
2017-12-16 19:46:45 +01:00
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 '
2018-01-08 23:41:20 -05:00
MULTI_OUTPUT_SUPPORT = ' 1.1.4 '
SEGWIT_SUPPORT = ' 1.1.10 '
SEGWIT_SUPPORT_SPECIAL = ' 1.0.4 '
2017-12-16 19:46:45 +01:00
2016-08-28 16:38:30 +02:00
class Ledger_Client ( ) :
def __init__ ( self , hidDevice ) :
self . dongleObject = btchip ( hidDevice )
self . preflightDone = False
def is_pairable ( self ) :
return True
def close ( self ) :
self . dongleObject . dongle . close ( )
def timeout ( self , cutoff ) :
pass
def is_initialized ( self ) :
return True
def label ( self ) :
return " "
def i4b ( self , x ) :
2017-12-16 19:46:45 +01:00
return pack ( ' >I ' , x )
2016-08-28 16:38:30 +02:00
2018-01-08 23:41:20 -05:00
def versiontuple ( self , v ) :
return tuple ( map ( int , ( v . split ( " . " ) ) ) )
2018-01-12 04:03:43 +01:00
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
2017-10-31 11:45:25 +01:00
def get_xpub ( self , bip32_path , xtype ) :
2016-08-28 16:38:30 +02:00
self . checkDevice ( )
# bip32_path is of the form 44'/0'/1'
# 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
#self.get_client() # prompt for the PIN before displaying the dialog if necessary
#self.handler.show_message("Computing master public key")
2017-11-03 10:32:16 +01:00
if xtype in [ ' p2wpkh ' , ' p2wsh ' ] and not self . supports_native_segwit ( ) :
2017-12-16 19:46:45 +01:00
raise Exception ( MSG_NEEDS_FW_UPDATE_SEGWIT )
2017-11-03 10:32:16 +01:00
if xtype in [ ' p2wpkh-p2sh ' , ' p2wsh-p2sh ' ] and not self . supports_segwit ( ) :
2017-12-16 19:46:45 +01:00
raise Exception ( MSG_NEEDS_FW_UPDATE_SEGWIT )
2017-11-03 10:32:16 +01:00
splitPath = bip32_path . split ( ' / ' )
if splitPath [ 0 ] == ' m ' :
splitPath = splitPath [ 1 : ]
bip32_path = bip32_path [ 2 : ]
fingerprint = 0
if len ( splitPath ) > 1 :
prevPath = " / " . join ( splitPath [ 0 : len ( splitPath ) - 1 ] )
nodeData = self . dongleObject . getWalletPublicKey ( prevPath )
2018-01-12 04:03:43 +01:00
publicKey = compress_public_key ( nodeData [ ' publicKey ' ] )
2017-11-03 10:32:16 +01:00
h = hashlib . new ( ' ripemd160 ' )
h . update ( hashlib . sha256 ( publicKey ) . digest ( ) )
fingerprint = unpack ( " >I " , h . digest ( ) [ 0 : 4 ] ) [ 0 ]
nodeData = self . dongleObject . getWalletPublicKey ( bip32_path )
publicKey = compress_public_key ( nodeData [ ' publicKey ' ] )
depth = len ( splitPath )
lastChild = splitPath [ len ( splitPath ) - 1 ] . split ( ' \' ' )
childnum = int ( lastChild [ 0 ] ) if len ( lastChild ) == 1 else 0x80000000 | int ( lastChild [ 0 ] )
xpub = bitcoin . serialize_xpub ( xtype , nodeData [ ' chainCode ' ] , publicKey , depth , self . i4b ( fingerprint ) , self . i4b ( childnum ) )
return xpub
2016-08-28 16:38:30 +02:00
def has_detached_pin_support ( self , client ) :
try :
client . getVerifyPinRemainingAttempts ( )
return True
2017-02-16 10:54:24 +01:00
except BTChipException as e :
2016-08-28 16:38:30 +02:00
if e . sw == 0x6d00 :
return False
raise e
def is_pin_validated ( self , client ) :
try :
# Invalid SET OPERATION MODE to verify the PIN status
client . dongle . exchange ( bytearray ( [ 0xe0 , 0x26 , 0x00 , 0x00 , 0x01 , 0xAB ] ) )
2017-02-16 10:54:24 +01:00
except BTChipException as e :
2016-08-28 16:38:30 +02:00
if ( e . sw == 0x6982 ) :
return False
if ( e . sw == 0x6A80 ) :
return True
raise e
2017-01-25 01:34:35 +01:00
def supports_multi_output ( self ) :
return self . multiOutputSupported
2017-09-17 18:34:38 +02:00
def supports_segwit ( self ) :
return self . segwitSupported
def supports_native_segwit ( self ) :
return self . nativeSegwitSupported
2016-08-28 16:38:30 +02:00
def perform_hw1_preflight ( self ) :
try :
2017-09-17 18:34:38 +02:00
firmwareInfo = self . dongleObject . getFirmwareVersion ( )
2018-01-08 23:41:20 -05:00
firmware = firmwareInfo [ ' version ' ]
self . multiOutputSupported = self . versiontuple ( firmware ) > = self . versiontuple ( MULTI_OUTPUT_SUPPORT )
self . nativeSegwitSupported = self . versiontuple ( firmware ) > = self . versiontuple ( SEGWIT_SUPPORT )
self . segwitSupported = self . nativeSegwitSupported or ( firmwareInfo [ ' specialVersion ' ] == 0x20 and self . versiontuple ( firmware ) > = self . versiontuple ( SEGWIT_SUPPORT_SPECIAL ) )
2017-09-17 18:34:38 +02:00
2018-01-08 23:41:20 -05:00
if not checkFirmware ( firmwareInfo ) :
2016-10-24 15:45:54 +02:00
self . dongleObject . dongle . close ( )
2017-12-16 19:46:45 +01:00
raise Exception ( MSG_NEEDS_FW_UPDATE_GENERIC )
2016-08-28 16:38:30 +02:00
try :
self . dongleObject . getOperationMode ( )
2017-02-16 10:54:24 +01:00
except BTChipException as e :
2016-08-28 16:38:30 +02:00
if ( e . sw == 0x6985 ) :
2016-10-24 15:45:54 +02:00
self . dongleObject . dongle . close ( )
2016-12-21 12:52:54 +07:00
self . handler . get_setup ( )
2016-08-28 16:38:30 +02:00
# Acquire the new client on the next run
else :
raise e
2017-02-16 10:54:24 +01:00
if self . has_detached_pin_support ( self . dongleObject ) and not self . is_pin_validated ( self . dongleObject ) and ( self . handler is not None ) :
2016-08-28 16:38:30 +02:00
remaining_attempts = self . dongleObject . getVerifyPinRemainingAttempts ( )
2017-02-16 10:54:24 +01:00
if remaining_attempts != 1 :
2016-08-28 16:38:30 +02:00
msg = " Enter your Ledger PIN - remaining attempts : " + str ( remaining_attempts )
else :
msg = " Enter your Ledger PIN - WARNING : LAST ATTEMPT. If the PIN is not correct, the dongle will be wiped. "
confirmed , p , pin = self . password_dialog ( msg )
if not confirmed :
raise Exception ( ' Aborted by user - please unplug the dongle and plug it again before retrying ' )
pin = pin . encode ( )
self . dongleObject . verifyPin ( pin )
2017-02-16 10:54:24 +01:00
except BTChipException as e :
2016-08-28 16:38:30 +02:00
if ( e . sw == 0x6faa ) :
raise Exception ( " Dongle is temporarily locked - please unplug it and replug it again " )
if ( ( e . sw & 0xFFF0 ) == 0x63c0 ) :
raise Exception ( " Invalid PIN - please unplug the dongle and plug it again before retrying " )
raise e
def checkDevice ( self ) :
if not self . preflightDone :
2016-10-20 08:38:13 +02:00
try :
self . perform_hw1_preflight ( )
except BTChipException as e :
if ( e . sw == 0x6d00 ) :
raise BaseException ( " Device not in Bitcoin mode " )
raise e
2016-08-28 16:38:30 +02:00
self . preflightDone = True
def password_dialog ( self , msg = None ) :
response = self . handler . get_word ( msg )
if response is None :
return False , None , None
return True , response , response
2014-08-24 19:44:26 +02:00
2016-08-18 11:34:37 +02:00
class Ledger_KeyStore ( Hardware_KeyStore ) :
2016-08-28 16:38:30 +02:00
hw_type = ' ledger '
2016-01-01 18:38:43 +09:00
device = ' Ledger '
2014-08-24 19:44:26 +02:00
2016-08-21 22:44:42 +02:00
def __init__ ( self , d ) :
Hardware_KeyStore . __init__ ( self , d )
2016-01-11 14:38:45 +09:00
# Errors and other user interaction is done through the wallet's
# handler. The handler is per-window and preserved across
# device reconnects
self . force_watching_only = False
2014-09-20 14:27:13 +02:00
self . signing = False
2016-12-21 12:52:54 +07:00
self . cfg = d . get ( ' cfg ' , { ' mode ' : 0 , ' pair ' : ' ' } )
2017-04-12 19:15:43 +02:00
2016-12-21 12:52:54 +07:00
def dump ( self ) :
obj = Hardware_KeyStore . dump ( self )
obj [ ' cfg ' ] = self . cfg
return obj
2016-08-18 11:34:37 +02:00
2016-08-28 16:38:30 +02:00
def get_derivation ( self ) :
2017-12-16 19:46:45 +01:00
return self . derivation
2014-08-24 19:44:26 +02:00
2016-08-28 16:38:30 +02:00
def get_client ( self ) :
2017-01-25 01:34:35 +01:00
return self . plugin . get_client ( self ) . dongleObject
2017-12-16 19:46:45 +01:00
2017-01-25 01:34:35 +01:00
def get_client_electrum ( self ) :
2016-08-28 16:38:30 +02:00
return self . plugin . get_client ( self )
2017-12-16 19:46:45 +01:00
2014-09-19 13:39:12 +02:00
def give_error ( self , message , clear_client = False ) :
2015-07-05 22:14:53 +02:00
print_error ( message )
2014-09-19 13:39:12 +02:00
if not self . signing :
2016-01-30 07:46:19 +01:00
self . handler . show_error ( message )
2014-09-19 13:39:12 +02:00
else :
self . signing = False
2016-01-11 15:08:12 +09:00
if clear_client :
2016-08-18 11:34:37 +02:00
self . client = None
2015-07-04 16:45:08 +09:00
raise Exception ( message )
2014-09-19 13:39:12 +02:00
2016-08-28 16:38:30 +02:00
def address_id_stripped ( self , address ) :
2016-01-10 19:53:28 +09:00
# Strip the leading "m/"
2016-08-28 16:38:30 +02:00
change , index = self . get_address_index ( address )
derivation = self . derivation
address_path = " %s / %d / %d " % ( derivation , change , index )
return address_path [ 2 : ]
2014-08-24 19:44:26 +02:00
def decrypt_message ( self , pubkey , message , password ) :
2016-08-28 16:38:30 +02:00
raise RuntimeError ( _ ( ' Encryption and decryption are currently not supported for %s ' ) % self . device )
2014-08-24 19:44:26 +02:00
2016-08-28 16:38:30 +02:00
def sign_message ( self , sequence , message , password ) :
2015-02-01 02:29:21 +01:00
self . signing = True
2017-11-05 17:45:55 +01:00
message = message . encode ( ' utf8 ' )
2018-01-01 21:23:37 +00:00
message_hash = hashlib . sha256 ( message ) . hexdigest ( ) . upper ( )
2016-01-11 15:08:12 +09:00
# prompt for the PIN before displaying the dialog if necessary
client = self . get_client ( )
2016-08-28 16:38:30 +02:00
address_path = self . get_derivation ( ) [ 2 : ] + " / %d / %d " % sequence
2018-01-01 20:55:10 +00:00
self . handler . show_message ( " Signing message ... \r \n Message hash: " + message_hash )
2014-08-24 19:44:26 +02:00
try :
info = self . get_client ( ) . signMessagePrepare ( address_path , message )
pin = " "
2015-07-04 16:45:08 +09:00
if info [ ' confirmationNeeded ' ] :
2016-12-21 12:52:54 +07:00
pin = self . handler . get_auth ( info ) # does the authenticate dialog and returns pin
if not pin :
raise UserWarning ( _ ( ' Cancelled by user ' ) )
pin = str ( pin ) . encode ( )
2014-08-24 19:44:26 +02:00
signature = self . get_client ( ) . signMessageSign ( pin )
2017-02-16 10:54:24 +01:00
except BTChipException as e :
2015-02-01 02:29:21 +01:00
if e . sw == 0x6a80 :
2015-08-14 13:34:22 +02: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 16:45:08 +09:00
else :
self . give_error ( e , True )
2016-12-21 12:52:54 +07:00
except UserWarning :
self . handler . show_error ( _ ( ' Cancelled by user ' ) )
return ' '
2017-02-16 10:54:24 +01:00
except Exception as e :
2015-02-23 16:30:33 +01:00
self . give_error ( e , True )
2014-08-24 19:44:26 +02:00
finally :
2017-11-18 04:09:15 +01:00
self . handler . finished ( )
2015-02-01 02:29:21 +01:00
self . signing = False
2014-08-24 19:44:26 +02: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 : ]
# And convert it
2017-11-05 17:45:55 +01:00
return bytes ( [ 27 + 4 + ( signature [ 0 ] & 0x01 ) ] ) + r + s
2014-08-24 19:44:26 +02:00
2014-10-30 21:10:12 +01:00
def sign_transaction ( self , tx , password ) :
2014-10-24 10:53:09 +02:00
if tx . is_complete ( ) :
2014-09-19 13:39:12 +02:00
return
2016-01-27 20:47:49 +01:00
client = self . get_client ( )
2015-07-04 16:45:08 +09:00
self . signing = True
2014-08-24 19:44:26 +02:00
inputs = [ ]
inputsPaths = [ ]
pubKeys = [ ]
2016-08-28 16:38:30 +02:00
chipInputs = [ ]
2015-07-04 16:45:08 +09:00
redeemScripts = [ ]
2014-08-24 19:44:26 +02:00
signatures = [ ]
preparedTrustedInputs = [ ]
2015-07-04 16:45:08 +09:00
changePath = " "
2014-08-24 19:44:26 +02:00
changeAmount = None
output = None
outputAmount = None
2016-08-28 16:38:30 +02:00
p2shTransaction = False
2017-04-13 11:58:42 +02:00
segwitTransaction = False
2014-08-27 23:19:14 +02:00
pin = " "
2016-08-28 16:38:30 +02:00
self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
2016-09-30 14:56:53 +02:00
2014-08-24 19:44:26 +02:00
# Fetch inputs of the transaction to sign
2016-09-30 14:56:53 +02:00
derivations = self . get_tx_derivations ( tx )
2016-09-22 10:25:03 +02:00
for txin in tx . inputs ( ) :
2017-03-21 09:08:16 +01:00
if txin [ ' type ' ] == ' coinbase ' :
2014-09-19 13:39:12 +02:00
self . give_error ( " Coinbase not supported " ) # should never happen
2016-09-22 10:25:03 +02:00
2017-03-02 08:38:06 +01:00
if txin [ ' type ' ] in [ ' p2sh ' ] :
2016-09-22 10:25:03 +02:00
p2shTransaction = True
2016-09-30 14:56:53 +02:00
2017-09-17 18:34:38 +02:00
if txin [ ' type ' ] in [ ' p2wpkh-p2sh ' , ' p2wsh-p2sh ' ] :
if not self . get_client_electrum ( ) . supports_segwit ( ) :
2017-12-16 19:46:45 +01:00
self . give_error ( MSG_NEEDS_FW_UPDATE_SEGWIT )
2017-09-17 18:34:38 +02:00
segwitTransaction = True
if txin [ ' type ' ] in [ ' p2wpkh ' , ' p2wsh ' ] :
if not self . get_client_electrum ( ) . supports_native_segwit ( ) :
2017-12-16 19:46:45 +01:00
self . give_error ( MSG_NEEDS_FW_UPDATE_SEGWIT )
2017-04-12 19:15:43 +02:00
segwitTransaction = True
2017-03-02 08:38:06 +01:00
pubkeys , x_pubkeys = tx . get_sorted_pubkeys ( txin )
for i , x_pubkey in enumerate ( x_pubkeys ) :
2016-09-30 14:56:53 +02:00
if x_pubkey in derivations :
signingPos = i
s = derivations . get ( x_pubkey )
hwAddress = " %s / %d / %d " % ( self . get_derivation ( ) [ 2 : ] , s [ 0 ] , s [ 1 ] )
break
else :
self . give_error ( " No matching x_key for sign_transaction " ) # should never happen
2016-08-28 16:38:30 +02:00
2017-04-18 11:12:46 +02:00
redeemScript = Transaction . get_preimage_script ( txin )
2017-08-08 09:26:05 +02:00
inputs . append ( [ txin [ ' prev_tx ' ] . raw , txin [ ' prevout_n ' ] , redeemScript , txin [ ' prevout_hash ' ] , signingPos , txin . get ( ' sequence ' , 0xffffffff - 1 ) ] )
2016-08-28 16:38:30 +02:00
inputsPaths . append ( hwAddress )
2017-03-02 08:38:06 +01:00
pubKeys . append ( pubkeys )
2016-09-22 10:25:03 +02:00
2016-08-28 16:38:30 +02:00
# Sanity check
if p2shTransaction :
2017-03-02 08:38:06 +01:00
for txin in tx . inputs ( ) :
if txin [ ' type ' ] != ' p2sh ' :
2016-08-28 16:38:30 +02:00
self . give_error ( " P2SH / regular input mixed in same transaction not supported " ) # should never happen
2016-09-22 10:25:03 +02:00
txOutput = var_int ( len ( tx . outputs ( ) ) )
for txout in tx . outputs ( ) :
output_type , addr , amount = txout
txOutput + = int_to_hex ( amount , 8 )
script = tx . pay_script ( output_type , addr )
2017-08-13 12:00:33 +02:00
txOutput + = var_int ( len ( script ) / / 2 )
2016-09-22 10:25:03 +02:00
txOutput + = script
2017-08-13 12:00:33 +02:00
txOutput = bfh ( txOutput )
2014-08-24 19:44:26 +02:00
# Recognize outputs - only one output and one change is authorized
2016-08-28 16:38:30 +02:00
if not p2shTransaction :
2017-01-25 01:34:35 +01:00
if not self . get_client_electrum ( ) . supports_multi_output ( ) :
if len ( tx . outputs ( ) ) > 2 :
self . give_error ( " Transaction with more than 2 outputs not supported " )
2016-10-20 08:23:10 +02:00
for _type , address , amount in tx . outputs ( ) :
2016-08-28 16:38:30 +02:00
assert _type == TYPE_ADDRESS
2016-10-20 08:23:10 +02:00
info = tx . output_info . get ( address )
2017-03-11 13:13:20 +01:00
if ( info is not None ) and ( len ( tx . outputs ( ) ) != 1 ) :
2016-10-20 08:23:10 +02:00
index , xpubs , m = info
changePath = self . get_derivation ( ) [ 2 : ] + " / %d / %d " % index
2016-08-28 16:38:30 +02:00
changeAmount = amount
else :
output = address
outputAmount = amount
2016-09-22 10:25:03 +02:00
2016-09-22 10:54:32 +02:00
self . handler . show_message ( _ ( " Confirm Transaction on your Ledger device... " ) )
2014-08-24 19:44:26 +02:00
try :
# Get trusted inputs from the original transactions
2017-12-16 19:46:45 +01:00
for utxo in inputs :
2017-04-12 19:35:00 +02:00
sequence = int_to_hex ( utxo [ 5 ] , 4 )
2017-04-12 19:15:43 +02:00
if segwitTransaction :
2017-12-16 19:46:45 +01:00
txtmp = bitcoinTransaction ( bfh ( utxo [ 0 ] ) )
2017-08-13 12:00:33 +02:00
tmp = bfh ( utxo [ 3 ] ) [ : : - 1 ]
tmp + = bfh ( int_to_hex ( utxo [ 1 ] , 4 ) )
2017-08-30 22:49:03 +02:00
tmp + = txtmp . outputs [ utxo [ 1 ] ] . amount
2017-08-13 12:00:33 +02:00
chipInputs . append ( { ' value ' : tmp , ' witness ' : True , ' sequence ' : sequence } )
redeemScripts . append ( bfh ( utxo [ 2 ] ) )
2017-11-08 15:01:25 +01:00
elif not p2shTransaction :
txtmp = bitcoinTransaction ( bfh ( utxo [ 0 ] ) )
trustedInput = self . get_client ( ) . getTrustedInput ( txtmp , utxo [ 1 ] )
trustedInput [ ' sequence ' ] = sequence
chipInputs . append ( trustedInput )
redeemScripts . append ( txtmp . outputs [ utxo [ 1 ] ] . script )
2016-08-28 16:38:30 +02:00
else :
2017-08-13 12:00:33 +02:00
tmp = bfh ( utxo [ 3 ] ) [ : : - 1 ]
tmp + = bfh ( int_to_hex ( utxo [ 1 ] , 4 ) )
chipInputs . append ( { ' value ' : tmp , ' sequence ' : sequence } )
redeemScripts . append ( bfh ( utxo [ 2 ] ) )
2016-08-28 16:38:30 +02:00
2014-08-24 19:44:26 +02:00
# Sign all inputs
firstTransaction = True
inputIndex = 0
2016-10-09 09:20:32 +02:00
rawTx = tx . serialize ( )
2016-12-21 12:52:54 +07:00
self . get_client ( ) . enableAlternate2fa ( False )
2017-04-12 19:15:43 +02:00
if segwitTransaction :
self . get_client ( ) . startUntrustedTransaction ( True , inputIndex ,
2016-09-22 10:25:03 +02:00
chipInputs , redeemScripts [ inputIndex ] )
2017-04-12 19:15:43 +02:00
outputData = self . get_client ( ) . finalizeInputFull ( txOutput )
outputData [ ' outputData ' ] = txOutput
transactionOutput = outputData [ ' outputData ' ]
2015-09-04 09:07:18 +09:00
if outputData [ ' confirmationNeeded ' ] :
2016-12-21 12:52:54 +07:00
outputData [ ' address ' ] = output
2017-11-18 04:09:15 +01:00
self . handler . finished ( )
2016-12-21 12:52:54 +07:00
pin = self . handler . get_auth ( outputData ) # does the authenticate dialog and returns pin
if not pin :
raise UserWarning ( )
if pin != ' paired ' :
self . handler . show_message ( _ ( " Confirmed. Signing Transaction... " ) )
2017-11-18 04:09:15 +01:00
while inputIndex < len ( inputs ) :
2017-04-12 19:15:43 +02:00
singleInput = [ chipInputs [ inputIndex ] ]
self . get_client ( ) . startUntrustedTransaction ( False , 0 ,
singleInput , redeemScripts [ inputIndex ] )
2017-11-11 11:37:50 +01:00
inputSignature = self . get_client ( ) . untrustedHashSign ( inputsPaths [ inputIndex ] , pin , lockTime = tx . locktime )
2014-08-27 23:19:14 +02:00
inputSignature [ 0 ] = 0x30 # force for 1.4.9+
signatures . append ( inputSignature )
2014-08-24 19:44:26 +02:00
inputIndex = inputIndex + 1
2017-04-12 19:15:43 +02:00
else :
while inputIndex < len ( inputs ) :
self . get_client ( ) . startUntrustedTransaction ( firstTransaction , inputIndex ,
chipInputs , redeemScripts [ inputIndex ] )
2017-08-31 09:55:44 +02:00
outputData = self . get_client ( ) . finalizeInputFull ( txOutput )
outputData [ ' outputData ' ] = txOutput
2017-04-12 19:15:43 +02:00
if firstTransaction :
transactionOutput = outputData [ ' outputData ' ]
if outputData [ ' confirmationNeeded ' ] :
outputData [ ' address ' ] = output
2017-11-18 04:09:15 +01:00
self . handler . finished ( )
2017-04-12 19:15:43 +02:00
pin = self . handler . get_auth ( outputData ) # does the authenticate dialog and returns pin
if not pin :
raise UserWarning ( )
if pin != ' paired ' :
self . handler . show_message ( _ ( " Confirmed. Signing Transaction... " ) )
else :
# Sign input with the provided PIN
2017-04-19 15:01:31 +02:00
inputSignature = self . get_client ( ) . untrustedHashSign ( inputsPaths [ inputIndex ] , pin , lockTime = tx . locktime )
2017-04-12 19:15:43 +02:00
inputSignature [ 0 ] = 0x30 # force for 1.4.9+
signatures . append ( inputSignature )
inputIndex = inputIndex + 1
if pin != ' paired ' :
firstTransaction = False
2016-12-21 12:52:54 +07:00
except UserWarning :
self . handler . show_error ( _ ( ' Cancelled by user ' ) )
return
2016-09-22 10:25:03 +02:00
except BaseException as e :
traceback . print_exc ( file = sys . stdout )
2014-09-19 13:39:12 +02:00
self . give_error ( e , True )
2014-08-24 19:44:26 +02:00
finally :
2017-11-18 04:09:15 +01:00
self . handler . finished ( )
2014-08-24 19:44:26 +02:00
2017-04-18 10:43:24 +02:00
for i , txin in enumerate ( tx . inputs ( ) ) :
signingPos = inputs [ i ] [ 4 ]
2017-08-13 12:00:33 +02:00
txin [ ' signatures ' ] [ signingPos ] = bh2u ( signatures [ i ] )
2017-04-18 10:43:24 +02:00
tx . raw = tx . serialize ( )
2014-09-19 13:39:12 +02:00
self . signing = False
2014-08-24 19:44:26 +02:00
2018-01-11 04:37:41 +11:00
def show_address ( self , sequence , txin_type ) :
self . signing = True
client = self . get_client ( )
address_path = self . get_derivation ( ) [ 2 : ] + " / %d / %d " % sequence
self . handler . show_message ( _ ( " Showing address ... " ) )
2018-01-10 18:39:25 +01:00
segwit = Transaction . is_segwit_inputtype ( txin_type )
2018-01-21 22:59:27 +08:00
segwitNative = txin_type == ' p2wpkh '
2018-01-11 04:37:41 +11:00
try :
2018-01-21 22:59:27 +08:00
client . getWalletPublicKey ( address_path , showOnScreen = True , segwit = segwit , segwitNative = segwitNative )
2018-01-10 18:39:25 +01:00
except BTChipException as e :
if e . sw == 0x6985 : # cancelled by user
pass
else :
traceback . print_exc ( file = sys . stderr )
self . handler . show_error ( e )
except BaseException as e :
traceback . print_exc ( file = sys . stderr )
self . handler . show_error ( e )
2018-01-11 04:37:41 +11:00
finally :
self . handler . finished ( )
self . signing = False
2016-08-21 22:44:42 +02:00
class LedgerPlugin ( HW_PluginBase ) :
libraries_available = BTCHIP
keystore_class = Ledger_KeyStore
client = None
2017-12-16 19:46:45 +01:00
DEVICE_IDS = [
2016-08-28 16:38:30 +02:00
( 0x2581 , 0x1807 ) , # HW.1 legacy btchip
( 0x2581 , 0x2b7c ) , # HW.1 transitional production
( 0x2581 , 0x3b7c ) , # HW.1 ledger production
( 0x2581 , 0x4b7c ) , # HW.1 ledger test
( 0x2c97 , 0x0000 ) , # Blue
( 0x2c97 , 0x0001 ) # Nano-S
]
def __init__ ( self , parent , config , name ) :
2017-04-12 19:15:43 +02:00
self . segwit = config . get ( " segwit " )
2016-08-28 16:38:30 +02:00
HW_PluginBase . __init__ ( self , parent , config , name )
if self . libraries_available :
self . device_manager ( ) . register_devices ( self . DEVICE_IDS )
2016-08-21 22:44:42 +02:00
def btchip_is_connected ( self , keystore ) :
try :
2016-08-28 16:38:30 +02:00
self . get_client ( keystore ) . getFirmwareVersion ( )
2016-08-21 22:44:42 +02:00
except Exception as e :
return False
return True
2016-08-28 16:38:30 +02:00
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 ) :
2017-12-16 19:46:45 +01:00
ledger = True
2016-08-28 16:38:30 +02:00
dev = hid . device ( )
dev . open_path ( device . path )
dev . set_nonblocking ( True )
return HIDDongleHIDAPI ( dev , ledger , BTCHIP_DEBUG )
2017-12-16 19:46:45 +01:00
2016-08-28 16:38:30 +02:00
def create_client ( self , device , handler ) :
self . handler = handler
client = self . get_btchip_device ( device )
2017-02-16 10:54:24 +01:00
if client is not None :
2016-08-28 16:38:30 +02:00
client = Ledger_Client ( client )
return client
2017-12-16 19:46:45 +01:00
def setup_device ( self , device_info , wizard ) :
2016-08-28 16:38:30 +02:00
devmgr = self . device_manager ( )
device_id = device_info . device . id_
client = devmgr . client_by_id ( device_id )
client . handler = self . create_handler ( wizard )
2017-10-31 11:45:25 +01:00
client . get_xpub ( " m/44 ' /0 ' " , ' standard ' ) # TODO replace by direct derivation once Nano S > 1.1
2016-08-28 16:38:30 +02:00
2017-10-31 11:45:25 +01:00
def get_xpub ( self , device_id , derivation , xtype , wizard ) :
2016-08-28 16:38:30 +02:00
devmgr = self . device_manager ( )
client = devmgr . client_by_id ( device_id )
client . handler = self . create_handler ( wizard )
client . checkDevice ( )
2017-10-31 11:45:25 +01:00
xpub = client . get_xpub ( derivation , xtype )
2016-08-28 16:38:30 +02:00
return xpub
def get_client ( self , keystore , force_pair = True ) :
# All client interaction should not be in the main GUI thread
#assert self.main_thread != threading.current_thread()
devmgr = self . device_manager ( )
handler = keystore . handler
with devmgr . hid_lock :
2017-12-16 19:46:45 +01:00
client = devmgr . client_for_keystore ( self , handler , keystore , force_pair )
2016-08-28 16:38:30 +02:00
# returns the client for a given keystore. can use xpub
#if client:
# client.used()
2017-02-16 10:54:24 +01:00
if client is not None :
2017-12-16 19:46:45 +01:00
client . checkDevice ( )
2016-08-28 16:38:30 +02:00
return client
2018-01-11 04:37:41 +11:00
def show_address ( self , wallet , address ) :
sequence = wallet . get_address_index ( address )
txin_type = wallet . get_txin_type ( address )
wallet . get_keystore ( ) . show_address ( sequence , txin_type )