URI: 
       tcommands: add new cmd "getprivatekeyforpath" to export a WIF for a path - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 900a7631cf3588147857688cb5f8efe11c4b2924
   DIR parent e1e5167ca9a1b0147c4332ab7c238fd97dadfa4f
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Tue, 31 Mar 2020 05:50:18 +0200
       
       commands: add new cmd "getprivatekeyforpath" to export a WIF for a path
       
       related: #6061
       
       Diffstat:
         M electrum/address_synchronizer.py    |       2 +-
         M electrum/bitcoin.py                 |       4 ++--
         M electrum/commands.py                |       7 +++++++
         M electrum/tests/test_commands.py     |      14 ++++++++++++++
         M electrum/wallet.py                  |      16 +++++++++++++---
       
       5 files changed, 37 insertions(+), 6 deletions(-)
       ---
   DIR diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py
       t@@ -449,7 +449,7 @@ class AddressSynchronizer(Logger):
                domain = set(domain)
                # 1. Get the history of each address in the domain, maintain the
                #    delta of a tx as the sum of its deltas on domain addresses
       -        tx_deltas = defaultdict(int)
       +        tx_deltas = defaultdict(int)  # type: Dict[str, Optional[int]]
                for addr in domain:
                    h = self.get_address_history(addr)
                    for tx_hash, height in h:
   DIR diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py
       t@@ -565,8 +565,8 @@ def is_segwit_script_type(txin_type: str) -> bool:
            return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
        
        
       -def serialize_privkey(secret: bytes, compressed: bool, txin_type: str,
       -                      internal_use: bool=False) -> str:
       +def serialize_privkey(secret: bytes, compressed: bool, txin_type: str, *,
       +                      internal_use: bool = False) -> str:
            # we only export secrets inside curve range
            secret = ecc.ECPrivkey.normalize_secret_bytes(secret)
            if internal_use:
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -414,6 +414,13 @@ class Commands:
                domain = address
                return [wallet.export_private_key(address, password) for address in domain]
        
       +    @command('wp')
       +    async def getprivatekeyforpath(self, path, password=None, wallet: Abstract_Wallet = None):
       +        """Get private key corresponding to derivation path (address index).
       +        'path' can be either a str such as "m/0/50", or a list of ints such as [0, 50].
       +        """
       +        return wallet.export_private_key_for_path(path, password)
       +
            @command('w')
            async def ismine(self, address, wallet: Abstract_Wallet = None):
                """Check if address is in wallet. Return true if and only address is in wallet"""
   DIR diff --git a/electrum/tests/test_commands.py b/electrum/tests/test_commands.py
       t@@ -180,3 +180,17 @@ class TestCommandsTestnet(TestCaseForTestnet):
                }
                self.assertEqual("0200000000010139c5375fe9da7bd377c1783002b129f8c57d3e724d62f5eacb9739ca691a229d0100000000feffffff01301b0f0000000000160014ac0e2d229200bffb2167ed6fd196aef9d687d8bb0247304402206367fb2ddd723985f5f51e0f2435084c0a66f5c26f4403a75d3dd417b71a20450220545dc3637bcb49beedbbdf5063e05cad63be91af4f839886451c30ecd6edf1d20121021f110909ded653828a254515b58498a6bafc96799fb0851554463ed44ca7d9da00000000",
                                 cmds._run('serialize', (jsontx,)))
       +
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_getprivatekeyforpath(self, mock_save_db):
       +        wallet = restore_wallet_from_text('north rent dawn bunker hamster invest wagon market romance pig either squeeze',
       +                                          gap_limit=2,
       +                                          path='if_this_exists_mocking_failed_648151893',
       +                                          config=self.config)['wallet']
       +        cmds = Commands(config=self.config)
       +        self.assertEqual("p2wpkh:cUzm7zPpWgLYeURgff4EsoMjhskCpsviBH4Y3aZcrBX8UJSRPjC2",
       +                         cmds._run('getprivatekeyforpath', ([0, 10000],), wallet=wallet))
       +        self.assertEqual("p2wpkh:cUzm7zPpWgLYeURgff4EsoMjhskCpsviBH4Y3aZcrBX8UJSRPjC2",
       +                         cmds._run('getprivatekeyforpath', ("m/0/10000",), wallet=wallet))
       +        self.assertEqual("p2wpkh:cQAj4WGf1socCPCJNMjXYCJ8Bs5JUAk5pbDr4ris44QdgAXcV24S",
       +                         cmds._run('getprivatekeyforpath', ("m/5h/100000/88h/7",), wallet=wallet))
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -44,7 +44,7 @@ from abc import ABC, abstractmethod
        import itertools
        
        from .i18n import _
       -from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath
       +from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_path_to_list_of_uint32
        from .crypto import sha256
        from .util import (NotEnoughFunds, UserCancelled, profiler,
                           format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
       t@@ -462,7 +462,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                """Return script type of wallet address."""
                pass
        
       -    def export_private_key(self, address, password) -> str:
       +    def export_private_key(self, address: str, password: Optional[str]) -> str:
                if self.is_watching_only():
                    raise Exception(_("This is a watching-only wallet"))
                if not is_address(address):
       t@@ -475,6 +475,9 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type)
                return serialized_privkey
        
       +    def export_private_key_for_path(self, path: Union[Sequence[int], str], password: Optional[str]) -> str:
       +        raise Exception("this wallet is not deterministic")
       +
            @abstractmethod
            def get_public_keys(self, address: str) -> Sequence[str]:
                pass
       t@@ -2201,6 +2204,13 @@ class Deterministic_Wallet(Abstract_Wallet):
                pubkeys = self.derive_pubkeys(for_change, n)
                return self.pubkeys_to_address(pubkeys)
        
       +    def export_private_key_for_path(self, path: Union[Sequence[int], str], password: Optional[str]) -> str:
       +        if isinstance(path, str):
       +            path = convert_bip32_path_to_list_of_uint32(path)
       +        pk, compressed = self.keystore.get_private_key(path, password)
       +        txin_type = self.get_txin_type()  # assumes no mixed-scripts in wallet
       +        return bitcoin.serialize_privkey(pk, compressed, txin_type)
       +
            def get_public_keys_with_deriv_info(self, address: str):
                der_suffix = self.get_address_index(address)
                der_suffix = [int(x) for x in der_suffix]
       t@@ -2301,7 +2311,7 @@ class Deterministic_Wallet(Abstract_Wallet):
            def get_fingerprint(self):
                return self.get_master_public_key()
        
       -    def get_txin_type(self, address):
       +    def get_txin_type(self, address=None):
                return self.txin_type