URI: 
       tMerge pull request #3882 from SomberNight/storage_hw_encrypt_cli_support - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 0fbcb8229b58b974dec63beaae472c831d993b21
   DIR parent c559077007237300a4c4509636631714f766a7e4
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Fri, 23 Feb 2018 12:07:08 +0100
       
       Merge pull request #3882 from SomberNight/storage_hw_encrypt_cli_support
       
       cli support for hw encrypted wallets
       Diffstat:
         M electrum                            |      58 +++++++++++++++++++++++++------
         M lib/commands.py                     |       2 ++
         M plugins/digitalbitbox/cmdline.py    |       3 +++
         M plugins/digitalbitbox/digitalbitbo… |       3 ++-
         M plugins/keepkey/cmdline.py          |       3 +++
         M plugins/ledger/cmdline.py           |       3 +++
         M plugins/ledger/ledger.py            |       3 ++-
         M plugins/trezor/cmdline.py           |       3 +++
       
       8 files changed, 66 insertions(+), 12 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -91,7 +91,7 @@ if is_local or is_android:
        from electrum import bitcoin, util
        from electrum import SimpleConfig, Network
        from electrum.wallet import Wallet, Imported_Wallet
       -from electrum.storage import WalletStorage
       +from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption
        from electrum.util import print_msg, print_stderr, json_encode, json_decode
        from electrum.util import set_verbosity, InvalidPassword
        from electrum.commands import get_parser, known_commands, Commands, config_variables
       t@@ -194,8 +194,9 @@ def init_daemon(config_options):
                sys.exit(0)
            if storage.is_encrypted():
                if storage.is_encrypted_with_hw_device():
       -            raise NotImplementedError("CLI functionality of encrypted hw wallets")
       -        if config.get('password'):
       +            plugins = init_plugins(config, 'cmdline')
       +            password = get_password_for_hw_device_encrypted_storage(plugins)
       +        elif config.get('password'):
                    password = config.get('password')
                else:
                    password = prompt_password('Password:', False)
       t@@ -222,7 +223,7 @@ def init_cmdline(config_options, server):
            if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):
                cmd.requires_network = True
        
       -    # instanciate wallet for command-line
       +    # instantiate wallet for command-line
            storage = WalletStorage(config.get_wallet_path())
        
            if cmd.requires_wallet and not storage.file_exists():
       t@@ -240,8 +241,9 @@ def init_cmdline(config_options, server):
            if (cmd.requires_wallet and storage.is_encrypted() and server is None)\
               or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())):
                if storage.is_encrypted_with_hw_device():
       -            raise NotImplementedError("CLI functionality of encrypted hw wallets")
       -        if config.get('password'):
       +            # this case is handled later in the control flow
       +            password = None
       +        elif config.get('password'):
                    password = config.get('password')
                else:
                    password = prompt_password('Password:', False)
       t@@ -260,7 +262,42 @@ def init_cmdline(config_options, server):
            return cmd, password
        
        
       -def run_offline_command(config, config_options):
       +def get_connected_hw_devices(plugins):
       +    support = plugins.get_hardware_support()
       +    if not support:
       +        print_msg('No hardware wallet support found on your system.')
       +        sys.exit(1)
       +    # scan devices
       +    devices = []
       +    devmgr = plugins.device_manager
       +    for name, description, plugin in support:
       +        try:
       +            u = devmgr.unpaired_device_infos(None, plugin)
       +        except:
       +            devmgr.print_error("error", name)
       +            continue
       +        devices += list(map(lambda x: (name, x), u))
       +    return devices
       +
       +
       +def get_password_for_hw_device_encrypted_storage(plugins):
       +    devices = get_connected_hw_devices(plugins)
       +    if len(devices) == 0:
       +        print_msg("Error: No connected hw device found. Can not decrypt this wallet.")
       +        sys.exit(1)
       +    elif len(devices) > 1:
       +        print_msg("Warning: multiple hardware devices detected. "
       +                  "The first one will be used to decrypt the wallet.")
       +    # FIXME we use the "first" device, in case of multiple ones
       +    name, device_info = devices[0]
       +    plugin = plugins.get_plugin(name)
       +    derivation = get_derivation_used_for_hw_device_encryption()
       +    xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler)
       +    password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
       +    return password
       +
       +
       +def run_offline_command(config, config_options, plugins):
            cmdname = config.get('cmd')
            cmd = known_commands[cmdname]
            password = config_options.get('password')
       t@@ -268,7 +305,8 @@ def run_offline_command(config, config_options):
                storage = WalletStorage(config.get_wallet_path())
                if storage.is_encrypted():
                    if storage.is_encrypted_with_hw_device():
       -                raise NotImplementedError("CLI functionality of encrypted hw wallets")
       +                password = get_password_for_hw_device_encrypted_storage(plugins)
       +                config_options['password'] = password
                    storage.decrypt(password)
                wallet = Wallet(storage)
            else:
       t@@ -437,8 +475,8 @@ if __name__ == '__main__':
                        print_msg("Daemon not running; try 'electrum daemon start'")
                        sys.exit(1)
                    else:
       -                init_plugins(config, 'cmdline')
       -                result = run_offline_command(config, config_options)
       +                plugins = init_plugins(config, 'cmdline')
       +                result = run_offline_command(config, config_options, plugins)
                        # print result
            if isinstance(result, str):
                print_msg(result)
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -138,6 +138,8 @@ class Commands:
            @command('wp')
            def password(self, password=None, new_password=None):
                """Change wallet password. """
       +        if self.wallet.storage.is_encrypted_with_hw_device() and new_password:
       +            raise Exception("Can't change the password of a wallet encrypted with a hw device.")
                b = self.wallet.storage.is_encrypted()
                self.wallet.update_password(password, new_password, b)
                self.wallet.storage.write()
   DIR diff --git a/plugins/digitalbitbox/cmdline.py b/plugins/digitalbitbox/cmdline.py
       t@@ -9,3 +9,6 @@ class Plugin(DigitalBitboxPlugin):
                if not isinstance(keystore, self.keystore_class):
                    return
                keystore.handler = self.handler
       +
       +    def create_handler(self, window):
       +        return self.handler
   DIR diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py
       t@@ -661,7 +661,8 @@ class DigitalBitboxPlugin(HW_PluginBase):
        
            def create_client(self, device, handler):
                if device.interface_number == 0 or device.usage_page == 0xffff:
       -            self.handler = handler
       +            if handler:
       +                self.handler = handler
                    client = self.get_dbb_device(device)
                    if client is not None:
                        client = DigitalBitbox_Client(self, client)
   DIR diff --git a/plugins/keepkey/cmdline.py b/plugins/keepkey/cmdline.py
       t@@ -9,3 +9,6 @@ class Plugin(KeepKeyPlugin):
                if not isinstance(keystore, self.keystore_class):
                    return
                keystore.handler = self.handler
       +
       +    def create_handler(self, window):
       +        return self.handler
   DIR diff --git a/plugins/ledger/cmdline.py b/plugins/ledger/cmdline.py
       t@@ -9,3 +9,6 @@ class Plugin(LedgerPlugin):
                if not isinstance(keystore, self.keystore_class):
                    return
                keystore.handler = self.handler
       +
       +    def create_handler(self, window):
       +        return self.handler
   DIR diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
       t@@ -516,7 +516,8 @@ class LedgerPlugin(HW_PluginBase):
                return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
        
            def create_client(self, device, handler):
       -        self.handler = handler
       +        if handler:
       +            self.handler = handler
        
                client = self.get_btchip_device(device)
                if client is not None:
   DIR diff --git a/plugins/trezor/cmdline.py b/plugins/trezor/cmdline.py
       t@@ -9,3 +9,6 @@ class Plugin(TrezorPlugin):
                if not isinstance(keystore, self.keystore_class):
                    return
                keystore.handler = self.handler
       +
       +    def create_handler(self, window):
       +        return self.handler