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
2020-04-08 17:25:18 +02:00
from typing import Optional , Tuple
2014-08-24 19:44:26 +02:00
2019-02-21 22:17:06 +01:00
from electrum import ecc
2019-11-05 21:34:54 +01:00
from electrum import bip32
from electrum . crypto import hash_160
from electrum . bitcoin import int_to_hex , var_int , is_segwit_script_type
2019-10-23 17:09:41 +02:00
from electrum . bip32 import BIP32Node , convert_bip32_intpath_to_strpath
2014-08-24 19:44:26 +02:00
from electrum . i18n import _
2017-11-12 22:54:04 -06:00
from electrum . keystore import Hardware_KeyStore
2019-10-23 17:09:41 +02:00
from electrum . transaction import Transaction , PartialTransaction , PartialTxInput , PartialTxOutput
2018-04-27 03:21:27 +02:00
from electrum . wallet import Standard_Wallet
2019-04-26 18:52:26 +02:00
from electrum . util import bfh , bh2u , versiontuple , UserFacingException
2018-05-09 18:17:49 +02:00
from electrum . base_wizard import ScriptTypeNotSupported
2019-04-26 18:52:26 +02:00
from electrum . logging import get_logger
2016-01-30 07:46:19 +01:00
2019-11-11 17:04:12 +01:00
from . . hw_wallet import HW_PluginBase , HardwareClientBase
2020-07-01 16:13:38 +02:00
from . . hw_wallet . plugin import is_any_tx_output_on_change_branch , validate_op_return_output , LibraryFoundButUnusable
2018-10-25 22:20:33 +02:00
2019-04-26 18:52:26 +02:00
_logger = get_logger ( __name__ )
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
2018-07-19 13:33:57 +02:00
BTCHIP_DEBUG = False
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 '
2020-06-27 18:26:54 +02:00
SEGWIT_TRUSTEDINPUTS = ' 1.4.0 '
2017-12-16 19:46:45 +01:00
2018-04-22 02:15:08 +02: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 :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( _ ( ' Your Ledger is locked. Please unlock it. ' ) )
2018-04-22 02:15:08 +02:00
else :
raise
return catch_exception
2019-11-11 17:04:12 +01:00
class Ledger_Client ( HardwareClientBase ) :
2020-04-17 19:25:18 +02:00
def __init__ ( self , hidDevice , * , product_key : Tuple [ int , int ] ,
plugin : HW_PluginBase ) :
HardwareClientBase . __init__ ( self , plugin = plugin )
2016-08-28 16:38:30 +02:00
self . dongleObject = btchip ( hidDevice )
self . preflightDone = False
2020-04-08 17:25:18 +02:00
self . _product_key = product_key
2020-04-08 14:43:01 +02:00
self . _soft_device_id = None
2016-08-28 16:38:30 +02:00
def is_pairable ( self ) :
return True
def close ( self ) :
2020-04-17 19:05:56 +02:00
with self . device_manager ( ) . hid_lock :
self . dongleObject . dongle . close ( )
2016-08-28 16:38:30 +02:00
def timeout ( self , cutoff ) :
pass
def is_initialized ( self ) :
return True
2020-04-08 14:43:01 +02:00
def get_soft_device_id ( self ) :
if self . _soft_device_id is None :
# modern ledger can provide xpub without user interaction
# (hw1 would prompt for PIN)
if not self . is_hw1 ( ) :
self . _soft_device_id = self . request_root_fingerprint_from_device ( )
return self . _soft_device_id
2019-12-17 21:10:14 +01:00
def is_hw1 ( self ) - > bool :
2020-04-08 17:25:18 +02:00
return self . _product_key [ 0 ] == 0x2581
def device_model_name ( self ) :
if self . is_hw1 ( ) :
return " Ledger HW.1 "
if self . _product_key == ( 0x2c97 , 0x0000 ) :
return " Ledger Blue "
if self . _product_key == ( 0x2c97 , 0x0001 ) :
return " Ledger Nano S "
if self . _product_key == ( 0x2c97 , 0x0004 ) :
return " Ledger Nano X "
return None
2019-12-17 21:10:14 +01:00
2018-03-18 03:54:28 +01:00
def has_usable_connection_with_device ( self ) :
try :
self . dongleObject . getFirmwareVersion ( )
except BaseException :
return False
return True
2018-01-12 04:03:43 +01:00
@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 ( ) :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( 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 ( ) :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( MSG_NEEDS_FW_UPDATE_SEGWIT )
2019-11-05 21:34:54 +01:00
bip32_path = bip32 . normalize_bip32_derivation ( bip32_path )
bip32_intpath = bip32 . convert_bip32_path_to_list_of_uint32 ( bip32_path )
bip32_path = bip32_path [ 2 : ] # cut off "m/"
if len ( bip32_intpath ) > = 1 :
prevPath = bip32 . convert_bip32_intpath_to_strpath ( bip32_intpath [ : - 1 ] ) [ 2 : ]
2017-11-03 10:32:16 +01:00
nodeData = self . dongleObject . getWalletPublicKey ( prevPath )
2018-01-12 04:03:43 +01:00
publicKey = compress_public_key ( nodeData [ ' publicKey ' ] )
2019-11-05 21:34:54 +01:00
fingerprint_bytes = hash_160 ( publicKey ) [ 0 : 4 ]
childnum_bytes = bip32_intpath [ - 1 ] . to_bytes ( length = 4 , byteorder = " big " )
else :
fingerprint_bytes = bytes ( 4 )
childnum_bytes = bytes ( 4 )
2017-11-03 10:32:16 +01:00
nodeData = self . dongleObject . getWalletPublicKey ( bip32_path )
publicKey = compress_public_key ( nodeData [ ' publicKey ' ] )
2019-11-05 21:34:54 +01:00
depth = len ( bip32_intpath )
2019-02-21 22:17:06 +01:00
return BIP32Node ( xtype = xtype ,
ecc.ECPubkey: also accept bytearray in __init__
regression since #5947
Traceback (most recent call last):
File "...\electrum\electrum\base_wizard.py", line 339, in on_device
self.plugin.setup_device(device_info, self, purpose)
File "...\electrum\electrum\plugins\ledger\ledger.py", line 598, in setup_device
client.get_xpub("m/44'/0'", 'standard') # TODO replace by direct derivation once Nano S > 1.1
File "...\electrum\electrum\plugins\ledger\ledger.py", line 55, in catch_exception
return func(self, *args, **kwargs)
File "...\electrum\electrum\plugins\ledger\ledger.py", line 124, in get_xpub
eckey=ecc.ECPubkey(publicKey),
File "...\electrum\electrum\ecc.py", line 145, in __init__
self._x, self._y = _x_and_y_from_pubkey_bytes(b)
File "...\electrum\electrum\ecc.py", line 119, in _x_and_y_from_pubkey_bytes
ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
ctypes.ArgumentError: argument 3: <class 'TypeError'>: wrong type
2020-02-19 00:40:33 +01:00
eckey = ecc . ECPubkey ( bytes ( publicKey ) ) ,
2019-02-21 22:17:06 +01:00
chaincode = nodeData [ ' chainCode ' ] ,
depth = depth ,
2019-11-05 21:34:54 +01:00
fingerprint = fingerprint_bytes ,
child_number = childnum_bytes ) . to_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
2020-06-27 18:26:54 +02:00
def supports_segwit_trustedInputs ( self ) :
return self . segwitTrustedInputs
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 ' ]
2018-02-07 17:51:52 +01:00
self . multiOutputSupported = versiontuple ( firmware ) > = versiontuple ( MULTI_OUTPUT_SUPPORT )
self . nativeSegwitSupported = versiontuple ( firmware ) > = versiontuple ( SEGWIT_SUPPORT )
self . segwitSupported = self . nativeSegwitSupported or ( firmwareInfo [ ' specialVersion ' ] == 0x20 and versiontuple ( firmware ) > = versiontuple ( SEGWIT_SUPPORT_SPECIAL ) )
2020-07-01 16:13:38 +02:00
self . segwitTrustedInputs = versiontuple ( firmware ) > = versiontuple ( SEGWIT_TRUSTEDINPUTS )
2017-09-17 18:34:38 +02:00
2018-01-08 23:41:20 -05:00
if not checkFirmware ( firmwareInfo ) :
2020-04-17 19:05:56 +02:00
self . close ( )
2018-11-08 19:46:15 +01:00
raise UserFacingException ( 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 ) :
2020-04-17 19:05:56 +02:00
self . 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
2020-04-08 14:43:01 +02:00
if self . has_detached_pin_support ( self . dongleObject ) and not self . is_pin_validated ( self . dongleObject ) :
assert self . handler , " no handler for client "
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 :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( ' Aborted by user - please unplug the dongle and plug it again before retrying ' )
2016-08-28 16:38:30 +02:00
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 ) :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( " Dongle is temporarily locked - please unplug it and replug it again " )
2016-08-28 16:38:30 +02:00
if ( ( e . sw & 0xFFF0 ) == 0x63c0 ) :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( " Invalid PIN - please unplug the dongle and plug it again before retrying " )
2018-02-04 21:59:58 +01:00
if e . sw == 0x6f00 and e . message == ' Invalid channel ' :
# based on docs 0x6f00 might be a more general error, hence we also compare message to be sure
2018-11-08 19:46:15 +01:00
raise UserFacingException ( " Invalid channel. \n "
" Please make sure that ' Browser support ' is disabled on your device. " )
2016-08-28 16:38:30 +02:00
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 :
2018-03-22 15:46:23 +01:00
if ( e . sw == 0x6d00 or e . sw == 0x6700 ) :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( _ ( " Device not in Bitcoin mode " ) ) from e
2016-10-20 08:38:13 +02:00
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
2019-10-23 17:09:41 +02:00
plugin : ' LedgerPlugin '
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
2019-08-09 19:54:09 +02:00
self . cfg = d . get ( ' cfg ' , { ' mode ' : 0 } )
2020-04-01 13:31:49 +02:00
self . cfg = dict ( self . cfg ) # convert to dict from StoredDict (see #6066)
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_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
2019-12-17 21:10:14 +01:00
def get_client_electrum ( self ) - > Optional [ Ledger_Client ] :
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 ) :
2019-04-26 18:52:26 +02:00
_logger . info ( 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
2018-11-08 19:46:15 +01:00
raise UserFacingException ( message )
2014-09-19 13:39:12 +02:00
2018-03-14 15:18:26 +01:00
def set_and_unset_signing ( func ) :
""" Function decorator to set and unset self.signing. """
def wrapper ( self , * args , * * kwargs ) :
try :
self . signing = True
return func ( self , * args , * * kwargs )
finally :
self . signing = False
return wrapper
2014-08-24 19:44:26 +02:00
def decrypt_message ( self , pubkey , message , password ) :
2018-11-08 19:46:15 +01:00
raise UserFacingException ( _ ( ' Encryption and decryption are currently not supported for {} ' ) . format ( self . device ) )
2014-08-24 19:44:26 +02:00
2018-04-22 02:15:08 +02:00
@test_pin_unlocked
2018-03-14 15:18:26 +01:00
@set_and_unset_signing
2016-08-28 16:38:30 +02:00
def sign_message ( self , sequence , message , password ) :
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 ( )
2019-10-23 17:09:41 +02:00
address_path = self . get_derivation_prefix ( ) [ 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. " )
2018-03-14 15:18:26 +01:00
elif e . sw == 0x6985 : # cancelled by user
return b ' '
2018-04-22 02:15:08 +02:00
elif e . sw == 0x6982 :
raise # pin lock. decorator will catch it
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 ' ) )
2018-03-14 15:18:26 +01:00
return b ' '
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 ( )
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
2018-04-22 02:15:08 +02:00
@test_pin_unlocked
2018-03-14 15:18:26 +01:00
@set_and_unset_signing
2019-10-23 17:09:41 +02: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
2014-08-24 19:44:26 +02:00
inputs = [ ]
inputsPaths = [ ]
2016-08-28 16:38:30 +02:00
chipInputs = [ ]
2015-07-04 16:45:08 +09:00
redeemScripts = [ ]
changePath = " "
2014-08-24 19:44:26 +02:00
output = 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 = " "
2020-07-01 16:15:13 +02:00
client_ledger = self . get_client ( ) # prompt for the PIN before displaying the dialog if necessary
2019-12-17 21:19:57 +01:00
client_electrum = self . get_client_electrum ( )
assert client_electrum
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-22 10:25:03 +02:00
for txin in tx . inputs ( ) :
2020-01-02 00:43:49 +01:00
if txin . is_coinbase_input ( ) :
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
2019-10-23 17:09:41 +02:00
if txin . script_type in [ ' p2sh ' ] :
2016-09-22 10:25:03 +02:00
p2shTransaction = True
2016-09-30 14:56:53 +02:00
2019-10-23 17:09:41 +02:00
if txin . script_type in [ ' p2wpkh-p2sh ' , ' p2wsh-p2sh ' ] :
2019-12-17 21:19:57 +01:00
if not 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
2019-10-23 17:09:41 +02:00
if txin . script_type in [ ' p2wpkh ' , ' p2wsh ' ] :
2019-12-17 21:19:57 +01:00
if not 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
2019-10-23 17:09:41 +02:00
my_pubkey , full_path = self . find_my_pubkey_in_txinout ( txin )
if not full_path :
self . give_error ( " No matching pubkey for sign_transaction " ) # should never happen
2019-11-05 21:34:54 +01:00
full_path = convert_bip32_intpath_to_strpath ( full_path ) [ 2 : ]
2016-08-28 16:38:30 +02:00
2017-04-18 11:12:46 +02:00
redeemScript = Transaction . get_preimage_script ( txin )
2019-10-23 17:09:41 +02:00
txin_prev_tx = txin . utxo
2018-04-28 22:56:53 +02:00
if txin_prev_tx is None and not Transaction . is_segwit_input ( txin ) :
2019-10-23 17:09:41 +02:00
raise UserFacingException ( _ ( ' Missing previous tx for legacy input. ' ) )
txin_prev_tx_raw = txin_prev_tx . serialize ( ) if txin_prev_tx else None
2018-04-28 22:56:53 +02:00
inputs . append ( [ txin_prev_tx_raw ,
2019-10-23 17:09:41 +02:00
txin . prevout . out_idx ,
2018-04-28 22:56:53 +02:00
redeemScript ,
2019-10-23 17:09:41 +02:00
txin . prevout . txid . hex ( ) ,
my_pubkey ,
txin . nsequence ,
txin . value_sats ( ) ] )
inputsPaths . append ( full_path )
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 ( ) :
2019-10-23 17:09:41 +02:00
if txin . script_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 ( ) ) )
2018-10-02 15:52:24 +02:00
for o in tx . outputs ( ) :
2019-10-23 17:09:41 +02:00
txOutput + = int_to_hex ( o . value , 8 )
script = o . scriptpubkey . hex ( )
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
2019-12-17 21:33:07 +01:00
if not client_electrum . supports_multi_output ( ) :
if len ( tx . outputs ( ) ) > 2 :
self . give_error ( " Transaction with more than 2 outputs not supported " )
for txout in tx . outputs ( ) :
if not txout . address :
if client_electrum . is_hw1 ( ) :
self . give_error ( _ ( " Only address outputs are supported by {} " ) . format ( self . device ) )
# note: max_size based on https://github.com/LedgerHQ/ledger-app-btc/commit/3a78dee9c0484821df58975803e40d58fbfc2c38#diff-c61ccd96a6d8b54d48f54a3bc4dfa7e2R26
validate_op_return_output ( txout , max_size = 190 )
# Output "change" detection
2018-06-15 20:48:57 +02:00
# - only one output and one change is authorized (for hw.1 and nano)
# - at most one output can bypass confirmation (~change) (for all)
2016-08-28 16:38:30 +02:00
if not p2shTransaction :
2018-06-15 20:48:57 +02:00
has_change = False
any_output_on_change_branch = is_any_tx_output_on_change_branch ( tx )
2019-10-23 17:09:41 +02:00
for txout in tx . outputs ( ) :
if txout . is_mine and len ( tx . outputs ( ) ) > 1 \
2018-06-15 20:48:57 +02:00
and not has_change :
# prioritise hiding outputs on the 'change' branch from user
# because no more than one change address allowed
2019-10-23 17:09:41 +02:00
if txout . is_change == any_output_on_change_branch :
my_pubkey , changePath = self . find_my_pubkey_in_txinout ( txout )
assert changePath
2019-11-05 21:34:54 +01:00
changePath = convert_bip32_intpath_to_strpath ( changePath ) [ 2 : ]
2018-06-15 20:48:57 +02:00
has_change = True
else :
2019-10-23 17:09:41 +02:00
output = txout . address
2016-08-28 16:38:30 +02:00
else :
2019-10-23 17:09:41 +02:00
output = txout . address
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 )
2020-06-27 18:26:54 +02:00
if segwitTransaction and not client_electrum . supports_segwit_trustedInputs ( ) :
2017-08-13 12:00:33 +02:00
tmp = bfh ( utxo [ 3 ] ) [ : : - 1 ]
tmp + = bfh ( int_to_hex ( utxo [ 1 ] , 4 ) )
2018-04-28 22:56:53 +02:00
tmp + = bfh ( int_to_hex ( utxo [ 6 ] , 8 ) ) # txin['value']
2017-08-13 12:00:33 +02:00
chipInputs . append ( { ' value ' : tmp , ' witness ' : True , ' sequence ' : sequence } )
redeemScripts . append ( bfh ( utxo [ 2 ] ) )
2020-07-01 16:13:38 +02:00
elif ( not p2shTransaction ) or client_electrum . supports_multi_output ( ) :
2017-11-08 15:01:25 +01:00
txtmp = bitcoinTransaction ( bfh ( utxo [ 0 ] ) )
2020-07-01 16:15:13 +02:00
trustedInput = client_ledger . getTrustedInput ( txtmp , utxo [ 1 ] )
2017-11-08 15:01:25 +01:00
trustedInput [ ' sequence ' ] = sequence
2020-06-27 18:26:54 +02:00
if segwitTransaction :
trustedInput [ ' witness ' ] = True
2017-11-08 15:01:25 +01:00
chipInputs . append ( trustedInput )
2020-06-27 18:26:54 +02:00
if p2shTransaction or segwitTransaction :
2020-07-01 16:15:13 +02:00
redeemScripts . append ( bfh ( utxo [ 2 ] ) )
2020-06-27 18:26:54 +02:00
else :
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
2018-06-15 20:21:29 +02:00
rawTx = tx . serialize_to_network ( )
2020-07-01 16:15:13 +02:00
client_ledger . enableAlternate2fa ( False )
2017-04-12 19:15:43 +02:00
if segwitTransaction :
2020-07-01 16:15:13 +02:00
client_ledger . startUntrustedTransaction ( True , inputIndex ,
2019-01-07 10:49:11 +01:00
chipInputs , redeemScripts [ inputIndex ] , version = tx . version )
2018-10-02 15:52:24 +02:00
# we don't set meaningful outputAddress, amount and fees
# as we only care about the alternateEncoding==True branch
2020-07-01 16:15:13 +02:00
outputData = client_ledger . finalizeInput ( b ' ' , 0 , 0 , changePath , bfh ( rawTx ) )
2017-04-12 19:15:43 +02:00
outputData [ ' outputData ' ] = txOutput
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 ( )
2019-08-09 19:54:09 +02:00
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 ] ]
2020-07-01 16:15:13 +02:00
client_ledger . startUntrustedTransaction ( False , 0 ,
2019-01-07 10:49:11 +01:00
singleInput , redeemScripts [ inputIndex ] , version = tx . version )
2020-07-01 16:15:13 +02:00
inputSignature = client_ledger . untrustedHashSign ( inputsPaths [ inputIndex ] , pin , lockTime = tx . locktime )
2014-08-27 23:19:14 +02:00
inputSignature [ 0 ] = 0x30 # force for 1.4.9+
2019-10-23 17:09:41 +02:00
my_pubkey = inputs [ inputIndex ] [ 4 ]
tx . add_signature_to_txin ( txin_idx = inputIndex ,
signing_pubkey = my_pubkey . hex ( ) ,
sig = inputSignature . hex ( ) )
2014-08-24 19:44:26 +02:00
inputIndex = inputIndex + 1
2017-04-12 19:15:43 +02:00
else :
while inputIndex < len ( inputs ) :
2020-07-01 16:15:13 +02:00
client_ledger . startUntrustedTransaction ( firstTransaction , inputIndex ,
2019-01-07 10:49:11 +01:00
chipInputs , redeemScripts [ inputIndex ] , version = tx . version )
2018-10-02 15:52:24 +02:00
# we don't set meaningful outputAddress, amount and fees
# as we only care about the alternateEncoding==True branch
2020-07-01 16:15:13 +02:00
outputData = client_ledger . finalizeInput ( b ' ' , 0 , 0 , changePath , bfh ( rawTx ) )
2017-08-31 09:55:44 +02:00
outputData [ ' outputData ' ] = txOutput
2017-04-12 19:15:43 +02:00
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 ( )
2019-08-09 19:54:09 +02:00
self . handler . show_message ( _ ( " Confirmed. Signing Transaction... " ) )
2017-04-12 19:15:43 +02:00
else :
# Sign input with the provided PIN
2020-07-01 16:15:13 +02:00
inputSignature = client_ledger . untrustedHashSign ( inputsPaths [ inputIndex ] , pin , lockTime = tx . locktime )
2017-04-12 19:15:43 +02:00
inputSignature [ 0 ] = 0x30 # force for 1.4.9+
2019-10-23 17:09:41 +02:00
my_pubkey = inputs [ inputIndex ] [ 4 ]
tx . add_signature_to_txin ( txin_idx = inputIndex ,
signing_pubkey = my_pubkey . hex ( ) ,
sig = inputSignature . hex ( ) )
2017-04-12 19:15:43 +02:00
inputIndex = inputIndex + 1
2019-08-09 19:54:09 +02:00
firstTransaction = False
2016-12-21 12:52:54 +07:00
except UserWarning :
self . handler . show_error ( _ ( ' Cancelled by user ' ) )
return
2018-03-14 15:18:26 +01:00
except BTChipException as e :
2019-02-28 17:56:08 +01:00
if e . sw in ( 0x6985 , 0x6d00 ) : # cancelled by user
2018-03-14 15:18:26 +01:00
return
2018-04-22 02:15:08 +02:00
elif e . sw == 0x6982 :
raise # pin lock. decorator will catch it
2018-03-14 15:18:26 +01:00
else :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2018-03-14 15:18:26 +01:00
self . give_error ( e , True )
2016-09-22 10:25:03 +02:00
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
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
2018-04-22 02:15:08 +02:00
@test_pin_unlocked
2018-03-14 15:18:26 +01:00
@set_and_unset_signing
2018-01-11 04:37:41 +11:00
def show_address ( self , sequence , txin_type ) :
client = self . get_client ( )
2019-10-23 17:09:41 +02:00
address_path = self . get_derivation_prefix ( ) [ 2 : ] + " / %d / %d " % sequence
2018-01-11 04:37:41 +11:00
self . handler . show_message ( _ ( " Showing address ... " ) )
2019-04-19 00:37:28 +02:00
segwit = is_segwit_script_type ( 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
2018-04-22 02:15:08 +02:00
elif e . sw == 0x6982 :
raise # pin lock. decorator will catch it
2018-04-28 16:34:38 +02:00
elif e . sw == 0x6b00 : # hw.1 raises this
self . handler . show_error ( ' {} \n {} \n {} ' . format (
_ ( ' Error showing address ' ) + ' : ' ,
e ,
_ ( ' Your device might not have support for this functionality. ' ) ) )
2018-01-10 18:39:25 +01:00
else :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2018-01-10 18:39:25 +01:00
self . handler . show_error ( e )
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2018-01-10 18:39:25 +01:00
self . handler . show_error ( e )
2018-01-11 04:37:41 +11:00
finally :
self . handler . finished ( )
2016-08-21 22:44:42 +02:00
class LedgerPlugin ( HW_PluginBase ) :
keystore_class = Ledger_KeyStore
2020-07-01 16:13:38 +02:00
minimum_library = ( 0 , 1 , 30 )
2016-08-21 22:44:42 +02:00
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
2019-02-20 13:46:26 +01:00
( 0x2c97 , 0x0001 ) , # Nano-S
( 0x2c97 , 0x0004 ) , # Nano-X
( 0x2c97 , 0x0005 ) , # RFU
( 0x2c97 , 0x0006 ) , # RFU
( 0x2c97 , 0x0007 ) , # RFU
( 0x2c97 , 0x0008 ) , # RFU
( 0x2c97 , 0x0009 ) , # RFU
( 0x2c97 , 0x000a ) # RFU
2016-08-28 16:38:30 +02:00
]
2018-05-09 18:17:49 +02:00
SUPPORTED_XTYPES = ( ' standard ' , ' p2wpkh-p2sh ' , ' p2wpkh ' , ' p2wsh-p2sh ' , ' p2wsh ' )
2016-08-28 16:38:30 +02:00
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 )
2020-07-01 16:13:38 +02:00
self . libraries_available = self . check_libraries_available ( )
if not self . libraries_available :
return
self . device_manager ( ) . register_devices ( self . DEVICE_IDS , plugin = self )
def get_library_version ( self ) :
try :
import btchip
version = btchip . __version__
except ImportError :
raise
except :
version = " unknown "
if BTCHIP :
return version
else :
raise LibraryFoundButUnusable ( library_version = version )
2016-08-21 22:44:42 +02:00
2016-08-28 16:38:30 +02:00
def get_btchip_device ( self , device ) :
ledger = False
2018-03-22 15:48:48 +01:00
if device . product_key [ 0 ] == 0x2581 and device . product_key [ 1 ] == 0x3b7c :
ledger = True
if device . product_key [ 0 ] == 0x2581 and device . product_key [ 1 ] == 0x4b7c :
ledger = True
if device . product_key [ 0 ] == 0x2c97 :
if device . interface_number == 0 or device . usage_page == 0xffa0 :
ledger = True
else :
2018-04-15 20:45:30 +03:00
return None # non-compatible interface of a Nano S or Blue
2020-04-17 19:05:56 +02:00
with self . device_manager ( ) . hid_lock :
dev = hid . device ( )
dev . open_path ( device . path )
dev . set_nonblocking ( True )
2016-08-28 16:38:30 +02:00
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 ) :
2018-02-10 19:18:48 +01:00
if handler :
self . handler = handler
2016-08-28 16:38:30 +02:00
client = self . get_btchip_device ( device )
2017-02-16 10:54:24 +01:00
if client is not None :
2020-04-17 19:25:18 +02:00
client = Ledger_Client ( client , product_key = device . product_key , plugin = self )
2016-08-28 16:38:30 +02:00
return client
2017-12-07 11:35:10 +01:00
def setup_device ( self , device_info , wizard , purpose ) :
2016-08-28 16:38:30 +02:00
device_id = device_info . device . id_
2020-04-01 20:22:39 +02:00
client = self . scan_and_create_client_for_device ( device_id = device_id , wizard = wizard )
2020-04-01 21:05:19 +02:00
wizard . run_task_without_blocking_gui (
task = lambda : client . get_xpub ( " m/44 ' /0 ' " , ' standard ' ) ) # TODO replace by direct derivation once Nano S > 1.1
2020-04-09 18:00:35 +02:00
return client
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 ) :
2018-05-09 18:17:49 +02:00
if xtype not in self . SUPPORTED_XTYPES :
raise ScriptTypeNotSupported ( _ ( ' This type of script is not supported with {} . ' ) . format ( self . device ) )
2020-04-01 20:22:39 +02:00
client = self . scan_and_create_client_for_device ( device_id = device_id , wizard = wizard )
2016-08-28 16:38:30 +02:00
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
2020-04-08 16:39:46 +02:00
def get_client ( self , keystore , force_pair = True , * ,
devices = None , allow_user_interaction = True ) :
2016-08-28 16:38:30 +02:00
# All client interaction should not be in the main GUI thread
2020-04-08 16:39:46 +02:00
client = super ( ) . get_client ( keystore , force_pair ,
devices = devices ,
allow_user_interaction = allow_user_interaction )
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
2018-04-27 03:21:27 +02:00
def show_address ( self , wallet , address , keystore = None ) :
if keystore is None :
keystore = wallet . get_keystore ( )
if not self . show_address_helper ( wallet , address , keystore ) :
return
if type ( wallet ) is not Standard_Wallet :
keystore . handler . show_error ( _ ( ' This function is only available for standard wallets when using {} . ' ) . format ( self . device ) )
return
2018-01-11 04:37:41 +11:00
sequence = wallet . get_address_index ( address )
txin_type = wallet . get_txin_type ( address )
2018-04-27 03:21:27 +02:00
keystore . show_address ( sequence , txin_type )