2017-02-04 17:48:13 +03:00
# -*- coding: utf-8 -*-
2016-06-16 19:25:44 +02:00
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2016 Thomas Voegtlin
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
2017-12-07 11:35:10 +01:00
import sys
2019-02-23 15:59:01 +01:00
import copy
2017-12-07 11:35:10 +01:00
import traceback
2018-05-31 19:37:55 +02:00
from functools import partial
2019-03-04 17:23:43 +01:00
from typing import List , TYPE_CHECKING , Tuple , NamedTuple , Any , Dict , Optional
2017-12-07 11:35:10 +01:00
2017-01-22 21:25:24 +03:00
from . import bitcoin
from . import keystore
2019-02-22 18:01:54 +01:00
from . import mnemonic
2019-02-22 00:13:37 +01:00
from . bip32 import is_bip32_derivation , xpub_type , normalize_bip32_derivation
2018-06-26 19:31:05 +02:00
from . keystore import bip44_derivation , purpose48_derivation
2018-10-22 16:41:25 +02:00
from . wallet import ( Imported_Wallet , Standard_Wallet , Multisig_Wallet ,
wallet_types , Wallet , Abstract_Wallet )
from . storage import ( WalletStorage , STO_EV_USER_PW , STO_EV_XPUB_PW ,
get_derivation_used_for_hw_device_encryption )
2017-01-22 21:25:24 +03:00
from . i18n import _
2018-07-24 18:57:49 +02:00
from . util import UserCancelled , InvalidPassword , WalletFileException
2018-10-22 16:41:25 +02:00
from . simple_config import SimpleConfig
from . plugin import Plugins
2019-04-26 18:52:26 +02:00
from . logging import Logger
2018-10-22 16:41:25 +02:00
trezor: don't let bridge transport failing block all other transports
[trezor] connecting to device at bridge:hid...
[trezor] connected to device at bridge:hid...
Traceback (most recent call last):
File "...\electrum\electrum\base_wizard.py", line 255, in choose_hw_device
u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices)
File "...\electrum\electrum\plugin.py", line 501, in unpaired_device_infos
client = self.create_client(device, handler, plugin)
File "...\electrum\electrum\plugin.py", line 374, in create_client
client = plugin.create_client(device, handler)
File "...\electrum\electrum\plugins\trezor\trezor.py", line 124, in create_client
client = self.client_class(transport, handler, self)
File "...\electrum\electrum\plugins\trezor\client.py", line 7, in __init__
ProtocolMixin.__init__(self, transport=transport)
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 444, in __init__
self.init_device()
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 454, in init_device
self.features = expect(proto.Features)(self.call)(init_msg)
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 115, in wrapped_f
ret = f(*args, **kwargs)
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 129, in wrapped_f
client.transport.session_begin()
File "...\Python36-32\lib\site-packages\trezorlib\transport\__init__.py", line 42, in session_begin
self.open()
File "...\Python36-32\lib\site-packages\trezorlib\transport\bridge.py", line 69, in open
raise TransportException('trezord: Could not acquire session' + get_error(r))
trezorlib.transport.TransportException: trezord: Could not acquire session (error=400 str=wrong previous session)
[DeviceMgr] error getting device infos for trezor: trezord: Could not acquire session (error=400 str=wrong previous session)
2018-11-08 17:07:05 +01:00
if TYPE_CHECKING :
from . plugin import DeviceInfo
2016-07-01 11:44:26 +02:00
2017-12-07 11:35:10 +01:00
# hardware device setup purpose
HWD_SETUP_NEW_WALLET , HWD_SETUP_DECRYPT_WALLET = range ( 0 , 2 )
2017-02-04 17:48:13 +03:00
2018-05-01 14:39:01 +02:00
2018-01-09 21:10:11 +01:00
class ScriptTypeNotSupported ( Exception ) : pass
2018-05-01 14:39:01 +02:00
class GoBack ( Exception ) : pass
2019-02-04 17:07:49 +01:00
class WizardStackItem ( NamedTuple ) :
action : Any
args : Any
2019-03-03 17:32:00 +01:00
kwargs : Dict [ str , Any ]
2019-02-04 17:07:49 +01:00
storage_data : dict
2019-04-26 18:52:26 +02:00
class BaseWizard ( Logger ) :
2016-06-16 19:25:44 +02:00
2019-02-23 15:59:01 +01:00
def __init__ ( self , config : SimpleConfig , plugins : Plugins ) :
2016-06-16 19:25:44 +02:00
super ( BaseWizard , self ) . __init__ ( )
2019-04-26 18:52:26 +02:00
Logger . __init__ ( self )
2016-07-02 08:58:56 +02:00
self . config = config
2018-05-18 18:07:52 +02:00
self . plugins = plugins
2019-02-23 15:59:01 +01:00
self . data = { }
self . pw_args = None
2019-02-04 17:07:49 +01:00
self . _stack = [ ] # type: List[WizardStackItem]
2016-07-02 08:58:56 +02:00
self . plugin = None
2016-08-24 09:13:21 +02:00
self . keystores = [ ]
2016-08-29 15:33:16 +02:00
self . is_kivy = config . get ( ' gui ' ) == ' kivy '
2017-01-16 09:48:38 +01:00
self . seed_type = None
2016-06-16 19:25:44 +02:00
2018-05-18 18:07:52 +02:00
def set_icon ( self , icon ) :
pass
2019-03-03 17:32:00 +01:00
def run ( self , * args , * * kwargs ) :
2016-07-30 15:04:15 +02:00
action = args [ 0 ]
args = args [ 1 : ]
2019-02-23 15:59:01 +01:00
storage_data = copy . deepcopy ( self . data )
2019-03-03 17:32:00 +01:00
self . _stack . append ( WizardStackItem ( action , args , kwargs , storage_data ) )
2016-06-16 19:25:44 +02:00
if not action :
return
2016-07-02 08:58:56 +02:00
if type ( action ) is tuple :
self . plugin , action = action
if self . plugin and hasattr ( self . plugin , action ) :
f = getattr ( self . plugin , action )
2019-03-03 17:32:00 +01:00
f ( self , * args , * * kwargs )
2016-06-20 16:25:11 +02:00
elif hasattr ( self , action ) :
2016-06-16 19:25:44 +02:00
f = getattr ( self , action )
2019-03-03 17:32:00 +01:00
f ( * args , * * kwargs )
2016-06-16 19:25:44 +02:00
else :
2018-04-07 17:10:30 +02:00
raise Exception ( " unknown action " , action )
2016-06-16 19:25:44 +02:00
2016-06-20 16:25:11 +02:00
def can_go_back ( self ) :
2019-02-04 16:51:19 +01:00
return len ( self . _stack ) > 1
2016-06-20 16:25:11 +02:00
def go_back ( self ) :
if not self . can_go_back ( ) :
return
2019-02-04 17:07:49 +01:00
# pop 'current' frame
2019-02-04 16:51:19 +01:00
self . _stack . pop ( )
2019-02-04 17:07:49 +01:00
# pop 'previous' frame
stack_item = self . _stack . pop ( )
# try to undo side effects since we last entered 'previous' frame
# FIXME only self.storage is properly restored
2019-02-23 15:59:01 +01:00
self . data = copy . deepcopy ( stack_item . storage_data )
2019-02-04 17:07:49 +01:00
# rerun 'previous' frame
2019-03-03 17:32:00 +01:00
self . run ( stack_item . action , * stack_item . args , * * stack_item . kwargs )
2016-06-20 16:25:11 +02:00
2019-02-04 16:51:19 +01:00
def reset_stack ( self ) :
self . _stack = [ ]
2016-06-16 19:25:44 +02:00
def new ( self ) :
2019-02-23 15:59:01 +01:00
title = _ ( " Create new wallet " )
2016-06-20 16:25:11 +02:00
message = ' \n ' . join ( [
2016-06-16 19:25:44 +02:00
_ ( " What kind of wallet do you want to create? " )
] )
2016-06-20 16:25:11 +02:00
wallet_kinds = [
( ' standard ' , _ ( " Standard wallet " ) ) ,
2016-08-19 17:26:57 +02:00
( ' 2fa ' , _ ( " Wallet with two-factor authentication " ) ) ,
2016-06-20 16:25:11 +02:00
( ' multisig ' , _ ( " Multi-signature wallet " ) ) ,
2017-09-25 21:35:14 +02:00
( ' imported ' , _ ( " Import Bitcoin addresses or private keys " ) ) ,
2016-06-16 19:25:44 +02:00
]
2016-08-19 17:26:57 +02:00
choices = [ pair for pair in wallet_kinds if pair [ 0 ] in wallet_types ]
2016-07-02 08:58:56 +02:00
self . choice_dialog ( title = title , message = message , choices = choices , run_next = self . on_wallet_type )
2016-06-20 16:25:11 +02:00
2019-02-23 15:59:01 +01:00
def upgrade_storage ( self , storage ) :
2018-07-24 18:57:49 +02:00
exc = None
2018-05-31 19:37:55 +02:00
def on_finished ( ) :
2018-07-24 18:57:49 +02:00
if exc is None :
2019-03-04 17:23:43 +01:00
self . terminate ( storage = storage )
2018-07-24 18:57:49 +02:00
else :
raise exc
def do_upgrade ( ) :
nonlocal exc
try :
2019-02-23 15:59:01 +01:00
storage . upgrade ( )
2018-07-24 18:57:49 +02:00
except Exception as e :
exc = e
self . waiting_dialog ( do_upgrade , _ ( ' Upgrading wallet format... ' ) , on_finished = on_finished )
2018-05-31 19:37:55 +02:00
2016-09-28 06:30:00 +02:00
def load_2fa ( self ) :
2019-02-23 15:59:01 +01:00
self . data [ ' wallet_type ' ] = ' 2fa '
self . data [ ' use_trustedcoin ' ] = True
2016-09-28 06:30:00 +02:00
self . plugin = self . plugins . load_plugin ( ' trustedcoin ' )
2016-06-20 16:25:11 +02:00
def on_wallet_type ( self , choice ) :
2019-02-23 15:59:01 +01:00
self . data [ ' wallet_type ' ] = self . wallet_type = choice
2016-06-20 16:25:11 +02:00
if choice == ' standard ' :
2016-08-19 11:47:07 +02:00
action = ' choose_keystore '
2016-06-20 16:25:11 +02:00
elif choice == ' multisig ' :
action = ' choose_multisig '
2016-08-19 17:26:57 +02:00
elif choice == ' 2fa ' :
2016-09-28 06:30:00 +02:00
self . load_2fa ( )
2019-02-23 15:59:01 +01:00
action = self . plugin . get_action ( self . data )
2016-08-22 12:50:24 +02:00
elif choice == ' imported ' :
2017-09-25 21:35:14 +02:00
action = ' import_addresses_or_keys '
2016-06-20 16:25:11 +02:00
self . run ( action )
def choose_multisig ( self ) :
def on_multisig ( m , n ) :
2019-02-04 17:07:49 +01:00
multisig_type = " %d of %d " % ( m , n )
2019-02-23 15:59:01 +01:00
self . data [ ' wallet_type ' ] = multisig_type
2016-07-02 08:58:56 +02:00
self . n = n
2016-08-19 11:47:07 +02:00
self . run ( ' choose_keystore ' )
2016-06-20 16:25:11 +02:00
self . multisig_dialog ( run_next = on_multisig )
2016-06-16 19:25:44 +02:00
2016-08-19 11:47:07 +02:00
def choose_keystore ( self ) :
2016-08-19 14:45:52 +02:00
assert self . wallet_type in [ ' standard ' , ' multisig ' ]
2016-08-24 09:13:21 +02:00
i = len ( self . keystores )
title = _ ( ' Add cosigner ' ) + ' ( %d of %d ) ' % ( i + 1 , self . n ) if self . wallet_type == ' multisig ' else _ ( ' Keystore ' )
if self . wallet_type == ' standard ' or i == 0 :
message = _ ( ' Do you want to create a new seed, or to restore a wallet using an existing seed? ' )
2016-08-22 12:50:24 +02:00
choices = [
2017-09-14 12:20:11 +02:00
( ' choose_seed_type ' , _ ( ' Create a new seed ' ) ) ,
2016-08-25 06:43:27 +02:00
( ' restore_from_seed ' , _ ( ' I already have a seed ' ) ) ,
2018-03-05 12:58:03 +01:00
( ' restore_from_key ' , _ ( ' Use a master key ' ) ) ,
2016-08-22 12:50:24 +02:00
]
2016-08-30 11:19:30 +02:00
if not self . is_kivy :
choices . append ( ( ' choose_hw_device ' , _ ( ' Use a hardware device ' ) ) )
2016-08-22 12:50:24 +02:00
else :
2016-08-24 09:13:21 +02:00
message = _ ( ' Add a cosigner to your multi-sig wallet ' )
2016-08-22 12:50:24 +02:00
choices = [
2016-08-28 11:29:16 +02:00
( ' restore_from_key ' , _ ( ' Enter cosigner key ' ) ) ,
2016-09-28 17:03:02 +02:00
( ' restore_from_seed ' , _ ( ' Enter cosigner seed ' ) ) ,
2016-08-22 12:50:24 +02:00
]
2016-08-30 11:19:30 +02:00
if not self . is_kivy :
choices . append ( ( ' choose_hw_device ' , _ ( ' Cosign with hardware device ' ) ) )
2016-08-22 12:50:24 +02:00
2016-08-19 14:45:52 +02:00
self . choice_dialog ( title = title , message = message , choices = choices , run_next = self . run )
2016-06-16 19:25:44 +02:00
2017-09-25 21:35:14 +02:00
def import_addresses_or_keys ( self ) :
2019-04-19 00:15:45 +02:00
v = lambda x : keystore . is_address_list ( x ) or keystore . is_private_key_list ( x , raise_on_error = True )
2016-08-22 12:50:24 +02:00
title = _ ( " Import Bitcoin Addresses " )
2017-09-25 21:35:14 +02:00
message = _ ( " Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys. " )
2017-12-24 03:30:04 +01:00
self . add_xpub_dialog ( title = title , message = message , run_next = self . on_import ,
2018-06-20 15:58:37 +02:00
is_valid = v , allow_multi = True , show_wif_help = True )
2017-09-25 21:35:14 +02:00
def on_import ( self , text ) :
2019-02-23 15:59:01 +01:00
# text is already sanitized by is_address_list and is_private_keys_list
2017-09-25 21:35:14 +02:00
if keystore . is_address_list ( text ) :
2019-02-23 15:59:01 +01:00
self . data [ ' addresses ' ] = { }
for addr in text . split ( ) :
assert bitcoin . is_address ( addr )
self . data [ ' addresses ' ] [ addr ] = { }
2017-09-25 21:35:14 +02:00
elif keystore . is_private_key_list ( text ) :
2019-02-23 15:59:01 +01:00
self . data [ ' addresses ' ] = { }
2017-09-25 21:35:14 +02:00
k = keystore . Imported_KeyStore ( { } )
2018-10-27 17:36:10 +02:00
keys = keystore . get_private_keys ( text )
2019-02-23 15:59:01 +01:00
for pk in keys :
assert bitcoin . is_private_key ( pk )
txin_type , pubkey = k . import_privkey ( pk , None )
addr = bitcoin . pubkey_to_address ( txin_type , pubkey )
self . data [ ' addresses ' ] [ addr ] = { ' type ' : txin_type , ' pubkey ' : pubkey , ' redeem_script ' : None }
self . keystores . append ( k )
2017-12-07 11:35:10 +01:00
else :
return self . terminate ( )
return self . run ( ' create_wallet ' )
2016-08-22 12:50:24 +02:00
2016-07-01 11:44:26 +02:00
def restore_from_key ( self ) :
if self . wallet_type == ' standard ' :
2017-09-25 21:35:14 +02:00
v = keystore . is_master_key
title = _ ( " Create keystore from a master key " )
2016-07-01 11:44:26 +02:00
message = ' ' . join ( [
2017-09-25 21:35:14 +02:00
_ ( " To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub). " ) ,
_ ( " To create a spending wallet, please enter a master private key (xprv/yprv/zprv). " )
2016-07-01 11:44:26 +02:00
] )
2016-09-26 12:02:54 +02:00
self . add_xpub_dialog ( title = title , message = message , run_next = self . on_restore_from_key , is_valid = v )
2016-07-01 11:44:26 +02:00
else :
2016-09-26 12:02:54 +02:00
i = len ( self . keystores ) + 1
2017-09-14 14:38:19 +02:00
self . add_cosigner_dialog ( index = i , run_next = self . on_restore_from_key , is_valid = keystore . is_bip32_key )
2016-08-25 06:43:27 +02:00
def on_restore_from_key ( self , text ) :
2017-09-25 21:35:14 +02:00
k = keystore . from_master_key ( text )
2016-08-25 12:18:51 +02:00
self . on_keystore ( k )
2016-06-20 16:25:11 +02:00
2019-02-25 20:22:23 +01:00
def choose_hw_device ( self , purpose = HWD_SETUP_NEW_WALLET , * , storage = None ) :
2016-08-19 11:47:07 +02:00
title = _ ( ' Hardware Keystore ' )
2016-08-23 13:40:11 +02:00
# check available plugins
2018-10-31 17:58:47 +01:00
supported_plugins = self . plugins . get_hardware_support ( )
trezor: don't let bridge transport failing block all other transports
[trezor] connecting to device at bridge:hid...
[trezor] connected to device at bridge:hid...
Traceback (most recent call last):
File "...\electrum\electrum\base_wizard.py", line 255, in choose_hw_device
u = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices)
File "...\electrum\electrum\plugin.py", line 501, in unpaired_device_infos
client = self.create_client(device, handler, plugin)
File "...\electrum\electrum\plugin.py", line 374, in create_client
client = plugin.create_client(device, handler)
File "...\electrum\electrum\plugins\trezor\trezor.py", line 124, in create_client
client = self.client_class(transport, handler, self)
File "...\electrum\electrum\plugins\trezor\client.py", line 7, in __init__
ProtocolMixin.__init__(self, transport=transport)
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 444, in __init__
self.init_device()
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 454, in init_device
self.features = expect(proto.Features)(self.call)(init_msg)
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 115, in wrapped_f
ret = f(*args, **kwargs)
File "...\Python36-32\lib\site-packages\trezorlib\client.py", line 129, in wrapped_f
client.transport.session_begin()
File "...\Python36-32\lib\site-packages\trezorlib\transport\__init__.py", line 42, in session_begin
self.open()
File "...\Python36-32\lib\site-packages\trezorlib\transport\bridge.py", line 69, in open
raise TransportException('trezord: Could not acquire session' + get_error(r))
trezorlib.transport.TransportException: trezord: Could not acquire session (error=400 str=wrong previous session)
[DeviceMgr] error getting device infos for trezor: trezord: Could not acquire session (error=400 str=wrong previous session)
2018-11-08 17:07:05 +01:00
devices = [ ] # type: List[Tuple[str, DeviceInfo]]
2016-08-24 05:58:41 +02:00
devmgr = self . plugins . device_manager
2019-01-21 18:44:36 +01:00
debug_msg = ' '
def failed_getting_device_infos ( name , e ) :
nonlocal debug_msg
2019-04-26 18:52:26 +02:00
self . logger . info ( f ' error getting device infos for { name } : { e } ' )
2019-01-21 18:44:36 +01:00
indented_error_msg = ' ' . join ( [ ' ' ] + str ( e ) . splitlines ( keepends = True ) )
debug_msg + = f ' { name } : (error getting device infos) \n { indented_error_msg } \n '
# scan devices
2018-03-15 20:03:12 +01:00
try :
scanned_devices = devmgr . scan_devices ( )
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . info ( ' error scanning devices: {} ' . format ( repr ( e ) ) )
2018-04-03 14:21:22 +02:00
debug_msg = ' {} : \n {} ' . format ( _ ( ' Error scanning devices ' ) , e )
2018-03-15 20:03:12 +01:00
else :
2018-10-31 17:58:47 +01:00
for splugin in supported_plugins :
name , plugin = splugin . name , splugin . plugin
# plugin init errored?
if not plugin :
e = splugin . exception
indented_error_msg = ' ' . join ( [ ' ' ] + str ( e ) . splitlines ( keepends = True ) )
debug_msg + = f ' { name } : (error during plugin init) \n '
debug_msg + = ' {} \n ' . format ( _ ( ' You might have an incompatible library. ' ) )
debug_msg + = f ' { indented_error_msg } \n '
continue
# see if plugin recognizes 'scanned_devices'
2018-03-15 20:03:12 +01:00
try :
# FIXME: side-effect: unpaired_device_info sets client.handler
2019-01-21 18:44:36 +01:00
device_infos = devmgr . unpaired_device_infos ( None , plugin , devices = scanned_devices ,
include_failing_clients = True )
2018-03-15 20:03:12 +01:00
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2019-01-21 18:44:36 +01:00
failed_getting_device_infos ( name , e )
2018-03-15 20:03:12 +01:00
continue
2019-01-21 18:44:36 +01:00
device_infos_failing = list ( filter ( lambda di : di . exception is not None , device_infos ) )
for di in device_infos_failing :
failed_getting_device_infos ( name , di . exception )
device_infos_working = list ( filter ( lambda di : di . exception is None , device_infos ) )
devices + = list ( map ( lambda x : ( name , x ) , device_infos_working ) )
2018-04-03 14:21:22 +02:00
if not debug_msg :
debug_msg = ' {} ' . format ( _ ( ' No exceptions encountered. ' ) )
2016-08-23 13:40:11 +02:00
if not devices :
2017-03-21 10:07:31 +01:00
msg = ' ' . join ( [
_ ( ' No hardware device detected. ' ) + ' \n ' ,
_ ( ' To trigger a rescan, press \' Next \' . ' ) + ' \n \n ' ,
_ ( ' If your device is not detected on Windows, go to " Settings " , " Devices " , " Connected devices " , and do " Remove device " . Then, plug your device again. ' ) + ' ' ,
2018-04-03 14:21:22 +02:00
_ ( ' On Linux, you might have to add a new permission to your udev rules. ' ) + ' \n \n ' ,
_ ( ' Debug message ' ) + ' \n ' ,
debug_msg
2016-08-23 13:40:11 +02:00
] )
2019-03-04 02:08:23 +01:00
self . confirm_dialog ( title = title , message = msg ,
run_next = lambda x : self . choose_hw_device ( purpose , storage = storage ) )
2016-08-23 13:40:11 +02:00
return
# select device
self . devices = devices
choices = [ ]
2016-08-25 15:31:21 +02:00
for name , info in devices :
state = _ ( " initialized " ) if info . initialized else _ ( " wiped " )
2018-02-04 07:26:55 +01:00
label = info . label or _ ( " An unnamed {} " ) . format ( name )
2018-11-30 20:45:54 +01:00
try : transport_str = info . device . transport_ui_string [ : 20 ]
except : transport_str = ' unknown transport '
descr = f " { label } [ { name } , { state } , { transport_str } ] "
2016-08-25 15:31:21 +02:00
choices . append ( ( ( name , info ) , descr ) )
2016-08-23 13:40:11 +02:00
msg = _ ( ' Select a device ' ) + ' : '
2019-02-25 20:22:23 +01:00
self . choice_dialog ( title = title , message = msg , choices = choices ,
run_next = lambda * args : self . on_device ( * args , purpose = purpose , storage = storage ) )
2016-08-23 13:40:11 +02:00
2019-02-25 20:22:23 +01:00
def on_device ( self , name , device_info , * , purpose , storage = None ) :
2016-08-24 10:47:27 +02:00
self . plugin = self . plugins . get_plugin ( name )
2016-10-20 08:32:44 +02:00
try :
2017-12-07 11:35:10 +01:00
self . plugin . setup_device ( device_info , self , purpose )
2018-02-04 21:59:58 +01:00
except OSError as e :
self . show_error ( _ ( ' We encountered an error while connecting to your device: ' )
+ ' \n ' + str ( e ) + ' \n '
+ _ ( ' To try to fix this, we will now re-pair with your device. ' ) + ' \n '
+ _ ( ' Please try again. ' ) )
devmgr = self . plugins . device_manager
devmgr . unpair_id ( device_info . device . id_ )
2019-03-04 02:08:23 +01:00
self . choose_hw_device ( purpose , storage = storage )
2018-02-04 21:59:58 +01:00
return
2018-05-01 14:39:01 +02:00
except ( UserCancelled , GoBack ) :
2019-03-04 02:08:23 +01:00
self . choose_hw_device ( purpose , storage = storage )
2018-03-15 06:08:13 +01:00
return
2016-10-20 08:32:44 +02:00
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2016-10-20 08:32:44 +02:00
self . show_error ( str ( e ) )
2019-03-04 02:08:23 +01:00
self . choose_hw_device ( purpose , storage = storage )
2016-10-20 08:32:44 +02:00
return
2017-12-07 11:35:10 +01:00
if purpose == HWD_SETUP_NEW_WALLET :
2018-06-26 19:31:05 +02:00
def f ( derivation , script_type ) :
2019-02-22 00:13:37 +01:00
derivation = normalize_bip32_derivation ( derivation )
2018-06-26 19:31:05 +02:00
self . run ( ' on_hw_derivation ' , name , device_info , derivation , script_type )
self . derivation_and_script_type_dialog ( f )
2017-12-07 11:35:10 +01:00
elif purpose == HWD_SETUP_DECRYPT_WALLET :
derivation = get_derivation_used_for_hw_device_encryption ( )
xpub = self . plugin . get_xpub ( device_info . device . id_ , derivation , ' standard ' , self )
password = keystore . Xpub . get_pubkey_from_xpub ( xpub , ( ) )
2018-03-10 03:59:01 +01:00
try :
2019-02-23 15:59:01 +01:00
storage . decrypt ( password )
2018-03-10 03:59:01 +01:00
except InvalidPassword :
# try to clear session so that user can type another passphrase
devmgr = self . plugins . device_manager
client = devmgr . client_by_id ( device_info . device . id_ )
if hasattr ( client , ' clear_session ' ) : # FIXME not all hw wallet plugins have this
client . clear_session ( )
raise
2016-09-23 19:00:42 +02:00
else :
2017-12-07 11:35:10 +01:00
raise Exception ( ' unknown purpose: %s ' % purpose )
2016-08-30 09:51:53 +02:00
2018-06-26 19:31:05 +02:00
def derivation_and_script_type_dialog ( self , f ) :
message1 = _ ( ' Choose the type of addresses in your wallet. ' )
message2 = ' \n ' . join ( [
_ ( ' You can override the suggested derivation path. ' ) ,
2017-06-20 10:47:02 +02:00
_ ( ' If you are not sure what this is, leave this field unchanged. ' )
2016-08-30 09:51:53 +02:00
] )
2018-06-26 19:31:05 +02:00
if self . wallet_type == ' multisig ' :
# There is no general standard for HD multisig.
# For legacy, this is partially compatible with BIP45; assumes index=0
# For segwit, a custom path is used, as there is no standard at all.
2018-11-08 13:01:40 +01:00
default_choice_idx = 2
2018-06-26 19:31:05 +02:00
choices = [
( ' standard ' , ' legacy multisig (p2sh) ' , " m/45 ' /0 " ) ,
( ' p2wsh-p2sh ' , ' p2sh-segwit multisig (p2wsh-p2sh) ' , purpose48_derivation ( 0 , xtype = ' p2wsh-p2sh ' ) ) ,
( ' p2wsh ' , ' native segwit multisig (p2wsh) ' , purpose48_derivation ( 0 , xtype = ' p2wsh ' ) ) ,
]
else :
2018-11-08 13:01:40 +01:00
default_choice_idx = 2
2018-06-26 19:31:05 +02:00
choices = [
( ' standard ' , ' legacy (p2pkh) ' , bip44_derivation ( 0 , bip43_purpose = 44 ) ) ,
( ' p2wpkh-p2sh ' , ' p2sh-segwit (p2wpkh-p2sh) ' , bip44_derivation ( 0 , bip43_purpose = 49 ) ) ,
( ' p2wpkh ' , ' native segwit (p2wpkh) ' , bip44_derivation ( 0 , bip43_purpose = 84 ) ) ,
]
2018-01-09 21:10:11 +01:00
while True :
try :
2018-06-26 19:31:05 +02:00
self . choice_and_line_dialog (
run_next = f , title = _ ( ' Script type and Derivation path ' ) , message1 = message1 ,
2018-11-08 13:01:40 +01:00
message2 = message2 , choices = choices , test_text = is_bip32_derivation ,
default_choice_idx = default_choice_idx )
2018-01-09 21:10:11 +01:00
return
except ScriptTypeNotSupported as e :
self . show_error ( e )
# let the user choose again
2016-08-15 11:48:33 +02:00
2018-06-26 19:31:05 +02:00
def on_hw_derivation ( self , name , device_info , derivation , xtype ) :
2017-02-05 13:38:44 +03:00
from . keystore import hardware_keystore
2017-11-03 10:32:16 +01:00
try :
xpub = self . plugin . get_xpub ( device_info . device . id_ , derivation , xtype , self )
2018-01-09 21:10:11 +01:00
except ScriptTypeNotSupported :
raise # this is handled in derivation_dialog
2017-11-03 10:32:16 +01:00
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2017-11-03 10:32:16 +01:00
self . show_error ( e )
2016-08-29 08:47:48 +02:00
return
2016-08-23 09:21:24 +02:00
d = {
' type ' : ' hardware ' ,
2016-08-24 08:52:21 +02:00
' hw_type ' : name ,
2016-08-23 09:21:24 +02:00
' derivation ' : derivation ,
' xpub ' : xpub ,
2016-08-26 11:45:12 +02:00
' label ' : device_info . label ,
2016-08-23 09:21:24 +02:00
}
2016-08-31 09:35:27 +02:00
k = hardware_keystore ( d )
2016-08-25 12:42:00 +02:00
self . on_keystore ( k )
2016-07-02 08:58:56 +02:00
2018-07-18 18:42:04 +02:00
def passphrase_dialog ( self , run_next , is_restoring = False ) :
2016-10-10 17:11:46 +02:00
title = _ ( ' Seed extension ' )
2016-09-28 06:30:00 +02:00
message = ' \n ' . join ( [
2016-10-10 17:11:46 +02:00
_ ( ' You may extend your seed with custom words. ' ) ,
_ ( ' Your seed extension must be saved together with your seed. ' ) ,
2016-09-28 06:30:00 +02:00
] )
warning = ' \n ' . join ( [
_ ( ' Note that this is NOT your encryption password. ' ) ,
_ ( ' If you do not know what this is, leave this field empty. ' ) ,
] )
2018-07-18 18:42:04 +02:00
warn_issue4566 = is_restoring and self . seed_type == ' bip39 '
self . line_dialog ( title = title , message = message , warning = warning ,
default = ' ' , test = lambda x : True , run_next = run_next ,
warn_issue4566 = warn_issue4566 )
2016-08-30 09:51:53 +02:00
2016-09-28 06:30:00 +02:00
def restore_from_seed ( self ) :
2016-09-28 09:53:17 +02:00
self . opt_bip39 = True
2016-09-30 01:15:28 +02:00
self . opt_ext = True
2019-02-22 18:01:54 +01:00
is_cosigning_seed = lambda x : mnemonic . seed_type ( x ) in [ ' standard ' , ' segwit ' ]
test = mnemonic . is_seed if self . wallet_type == ' standard ' else is_cosigning_seed
2016-09-28 06:30:00 +02:00
self . restore_seed_dialog ( run_next = self . on_restore_seed , test = test )
2016-08-15 11:48:33 +02:00
2016-09-30 01:15:28 +02:00
def on_restore_seed ( self , seed , is_bip39 , is_ext ) :
2019-02-22 18:01:54 +01:00
self . seed_type = ' bip39 ' if is_bip39 else mnemonic . seed_type ( seed )
2017-01-16 09:48:38 +01:00
if self . seed_type == ' bip39 ' :
2016-09-30 01:15:28 +02:00
f = lambda passphrase : self . on_restore_bip39 ( seed , passphrase )
2018-07-18 18:42:04 +02:00
self . passphrase_dialog ( run_next = f , is_restoring = True ) if is_ext else f ( ' ' )
2017-01-16 09:48:38 +01:00
elif self . seed_type in [ ' standard ' , ' segwit ' ] :
f = lambda passphrase : self . run ( ' create_keystore ' , seed , passphrase )
2018-07-18 18:42:04 +02:00
self . passphrase_dialog ( run_next = f , is_restoring = True ) if is_ext else f ( ' ' )
2017-01-16 09:48:38 +01:00
elif self . seed_type == ' old ' :
self . run ( ' create_keystore ' , seed , ' ' )
2019-02-22 18:01:54 +01:00
elif mnemonic . is_any_2fa_seed_type ( self . seed_type ) :
2018-05-18 18:07:52 +02:00
self . load_2fa ( )
self . run ( ' on_restore_seed ' , seed , is_ext )
2017-01-16 09:48:38 +01:00
else :
2018-04-07 17:10:30 +02:00
raise Exception ( ' Unknown seed type ' , self . seed_type )
2016-09-28 06:30:00 +02:00
def on_restore_bip39 ( self , seed , passphrase ) :
2018-06-26 19:31:05 +02:00
def f ( derivation , script_type ) :
2019-02-22 00:13:37 +01:00
derivation = normalize_bip32_derivation ( derivation )
2018-06-26 19:31:05 +02:00
self . run ( ' on_bip43 ' , seed , passphrase , derivation , script_type )
self . derivation_and_script_type_dialog ( f )
2016-08-15 11:48:33 +02:00
2016-08-25 12:18:51 +02:00
def create_keystore ( self , seed , passphrase ) :
2017-10-25 17:33:49 +02:00
k = keystore . from_seed ( seed , passphrase , self . wallet_type == ' multisig ' )
2016-08-30 09:51:53 +02:00
self . on_keystore ( k )
2016-08-25 06:43:27 +02:00
2018-06-26 19:31:05 +02:00
def on_bip43 ( self , seed , passphrase , derivation , script_type ) :
k = keystore . from_bip39_seed ( seed , passphrase , derivation , xtype = script_type )
2016-08-25 12:18:51 +02:00
self . on_keystore ( k )
2016-07-02 08:58:56 +02:00
2016-08-25 12:18:51 +02:00
def on_keystore ( self , k ) :
2017-10-26 20:30:39 +02:00
has_xpub = isinstance ( k , keystore . Xpub )
if has_xpub :
t1 = xpub_type ( k . xpub )
2016-06-16 19:25:44 +02:00
if self . wallet_type == ' standard ' :
2017-10-26 20:30:39 +02:00
if has_xpub and t1 not in [ ' standard ' , ' p2wpkh ' , ' p2wpkh-p2sh ' ] :
2017-10-26 17:43:41 +02:00
self . show_error ( _ ( ' Wrong key type ' ) + ' %s ' % t1 )
self . run ( ' choose_keystore ' )
return
2016-08-25 12:18:51 +02:00
self . keystores . append ( k )
self . run ( ' create_wallet ' )
2016-06-20 16:25:11 +02:00
elif self . wallet_type == ' multisig ' :
2017-10-26 20:30:39 +02:00
assert has_xpub
2017-10-26 17:43:41 +02:00
if t1 not in [ ' standard ' , ' p2wsh ' , ' p2wsh-p2sh ' ] :
self . show_error ( _ ( ' Wrong key type ' ) + ' %s ' % t1 )
self . run ( ' choose_keystore ' )
return
2016-08-22 12:50:24 +02:00
if k . xpub in map ( lambda x : x . xpub , self . keystores ) :
2016-08-30 11:19:30 +02:00
self . show_error ( _ ( ' Error: duplicate master public key ' ) )
self . run ( ' choose_keystore ' )
return
2017-09-14 14:38:19 +02:00
if len ( self . keystores ) > 0 :
t2 = xpub_type ( self . keystores [ 0 ] . xpub )
if t1 != t2 :
self . show_error ( _ ( ' Cannot add this cosigner: ' ) + ' \n ' + " Their key type is ' %s ' , we are ' %s ' " % ( t1 , t2 ) )
self . run ( ' choose_keystore ' )
return
2016-08-22 12:50:24 +02:00
self . keystores . append ( k )
if len ( self . keystores ) == 1 :
xpub = k . get_master_public_key ( )
2019-02-04 16:51:19 +01:00
self . reset_stack ( )
2016-08-22 12:50:24 +02:00
self . run ( ' show_xpub_and_add_cosigners ' , xpub )
elif len ( self . keystores ) < self . n :
self . run ( ' choose_keystore ' )
else :
2016-08-25 12:18:51 +02:00
self . run ( ' create_wallet ' )
def create_wallet ( self ) :
2017-12-07 11:35:10 +01:00
encrypt_keystore = any ( k . may_have_password ( ) for k in self . keystores )
# note: the following condition ("if") is duplicated logic from
# wallet.get_available_storage_encryption_version()
if self . wallet_type == ' standard ' and isinstance ( self . keystores [ 0 ] , keystore . Hardware_KeyStore ) :
# offer encrypting with a pw derived from the hw device
k = self . keystores [ 0 ]
try :
k . handler = self . plugin . create_handler ( self )
password = k . get_password_for_storage_encryption ( )
except UserCancelled :
devmgr = self . plugins . device_manager
devmgr . unpair_xpub ( k . xpub )
self . choose_hw_device ( )
return
except BaseException as e :
2019-04-26 18:52:26 +02:00
self . logger . exception ( ' ' )
2017-12-07 11:35:10 +01:00
self . show_error ( str ( e ) )
return
self . request_storage_encryption (
run_next = lambda encrypt_storage : self . on_password (
password ,
encrypt_storage = encrypt_storage ,
storage_enc_version = STO_EV_XPUB_PW ,
encrypt_keystore = False ) )
2016-08-25 12:18:51 +02:00
else :
2017-12-07 11:35:10 +01:00
# prompt the user to set an arbitrary password
self . request_password (
run_next = lambda password , encrypt_storage : self . on_password (
password ,
encrypt_storage = encrypt_storage ,
storage_enc_version = STO_EV_USER_PW ,
encrypt_keystore = encrypt_keystore ) ,
force_disable_encrypt_cb = not encrypt_keystore )
def on_password ( self , password , * , encrypt_storage ,
storage_enc_version = STO_EV_USER_PW , encrypt_keystore ) :
2016-08-25 12:18:51 +02:00
for k in self . keystores :
if k . may_have_password ( ) :
k . update_password ( None , password )
if self . wallet_type == ' standard ' :
2019-02-23 15:59:01 +01:00
self . data [ ' seed_type ' ] = self . seed_type
2017-02-05 13:38:44 +03:00
keys = self . keystores [ 0 ] . dump ( )
2019-02-23 15:59:01 +01:00
self . data [ ' keystore ' ] = keys
2016-08-25 12:18:51 +02:00
elif self . wallet_type == ' multisig ' :
for i , k in enumerate ( self . keystores ) :
2019-02-23 15:59:01 +01:00
self . data [ ' x %d / ' % ( i + 1 ) ] = k . dump ( )
2017-12-07 11:35:10 +01:00
elif self . wallet_type == ' imported ' :
if len ( self . keystores ) > 0 :
keys = self . keystores [ 0 ] . dump ( )
2019-02-23 15:59:01 +01:00
self . data [ ' keystore ' ] = keys
else :
2019-02-25 20:22:23 +01:00
raise Exception ( ' Unknown wallet type ' )
2019-02-23 15:59:01 +01:00
self . pw_args = password , encrypt_storage , storage_enc_version
self . terminate ( )
def create_storage ( self , path ) :
if not self . pw_args :
return
password , encrypt_storage , storage_enc_version = self . pw_args
storage = WalletStorage ( path )
2019-02-25 20:22:23 +01:00
storage . set_keystore_encryption ( bool ( password ) ) # and encrypt_keystore)
2019-02-23 15:59:01 +01:00
if encrypt_storage :
storage . set_password ( password , enc_version = storage_enc_version )
2019-02-25 20:22:23 +01:00
for key , value in self . data . items ( ) :
storage . put ( key , value )
2019-02-23 15:59:01 +01:00
storage . write ( )
2019-02-28 13:11:00 +01:00
storage . load_plugins ( )
2019-02-23 15:59:01 +01:00
return storage
2016-08-22 12:50:24 +02:00
2019-03-04 17:23:43 +01:00
def terminate ( self , * , storage : Optional [ WalletStorage ] = None ) :
raise NotImplementedError ( ) # implemented by subclasses
2016-08-22 12:50:24 +02:00
def show_xpub_and_add_cosigners ( self , xpub ) :
self . show_xpub_dialog ( xpub = xpub , run_next = lambda x : self . run ( ' choose_keystore ' ) )
2016-07-01 11:44:26 +02:00
2018-11-28 16:24:18 +01:00
def choose_seed_type ( self , message = None , choices = None ) :
2017-09-14 12:20:11 +02:00
title = _ ( ' Choose Seed type ' )
2018-11-28 16:24:18 +01:00
if message is None :
message = ' ' . join ( [
_ ( " The type of addresses used by your wallet will depend on your seed. " ) ,
_ ( " Segwit wallets use bech32 addresses, defined in BIP173. " ) ,
_ ( " Please note that websites and other wallets may not support these addresses yet. " ) ,
_ ( " Thus, you might want to keep using a non-segwit wallet in order to be able to receive bitcoins during the transition period. " )
] )
if choices is None :
choices = [
( ' create_segwit_seed ' , _ ( ' Segwit ' ) ) ,
( ' create_standard_seed ' , _ ( ' Legacy ' ) ) ,
]
2017-09-14 12:20:11 +02:00
self . choice_dialog ( title = title , message = message , choices = choices , run_next = self . run )
def create_segwit_seed ( self ) : self . create_seed ( ' segwit ' )
def create_standard_seed ( self ) : self . create_seed ( ' standard ' )
def create_seed ( self , seed_type ) :
2017-02-05 13:38:44 +03:00
from . import mnemonic
2017-09-14 12:20:11 +02:00
self . seed_type = seed_type
2017-01-16 09:48:38 +01:00
seed = mnemonic . Mnemonic ( ' en ' ) . make_seed ( self . seed_type )
2016-08-28 10:33:01 +02:00
self . opt_bip39 = False
2016-09-30 01:15:28 +02:00
f = lambda x : self . request_passphrase ( seed , x )
self . show_seed_dialog ( run_next = f , seed_text = seed )
2016-08-30 09:51:53 +02:00
2016-09-30 01:15:28 +02:00
def request_passphrase ( self , seed , opt_passphrase ) :
if opt_passphrase :
f = lambda x : self . confirm_seed ( seed , x )
self . passphrase_dialog ( run_next = f )
else :
self . run ( ' confirm_seed ' , seed , ' ' )
2016-06-16 19:25:44 +02:00
2016-08-29 15:33:16 +02:00
def confirm_seed ( self , seed , passphrase ) :
2016-08-30 09:51:53 +02:00
f = lambda x : self . confirm_passphrase ( seed , passphrase )
self . confirm_seed_dialog ( run_next = f , test = lambda x : x == seed )
def confirm_passphrase ( self , seed , passphrase ) :
2016-08-30 10:36:51 +02:00
f = lambda x : self . run ( ' create_keystore ' , seed , x )
2016-08-30 09:51:53 +02:00
if passphrase :
2016-10-10 17:11:46 +02:00
title = _ ( ' Confirm Seed Extension ' )
2016-08-30 09:51:53 +02:00
message = ' \n ' . join ( [
2016-10-10 17:11:46 +02:00
_ ( ' Your seed extension must be saved together with your seed. ' ) ,
2016-08-30 09:51:53 +02:00
_ ( ' Please type it here. ' ) ,
] )
self . line_dialog ( run_next = f , title = title , message = message , default = ' ' , test = lambda x : x == passphrase )
else :
2016-08-30 10:36:51 +02:00
f ( ' ' )