From d35ed6935d86b9c58cbd262c88cafc721d8609c9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 31 Mar 2021 04:33:33 +0200 Subject: [PATCH] commands: extend "importprivkey" to allow importing a list of keys This is most useful if the user wants to import a significant number of keys, as every invocation of `importprivkey` results in rewriting the wallet file to disk. The API of "importprivkey" is left unchanged for the single privkey case. --- electrum/commands.py | 30 +++++++++++++++++++++--------- tests/test_commands.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 68c1c7fdb..e66d1f10d 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -40,7 +40,8 @@ from decimal import Decimal, InvalidOperation from typing import Optional, TYPE_CHECKING, Dict, List import os -from .import util, ecc +from . import util, ecc +from . import keystore from .util import (bfh, format_satoshis, json_decode, json_normalize, is_hash256_str, is_hex_str, to_bytes, parse_max_spend, to_decimal, UserFacingException) @@ -53,7 +54,7 @@ from .transaction import (Transaction, multisig_script, TxOutput, PartialTransac from . import transaction from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED from .synchronizer import Notifier -from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text, Deterministic_Wallet, BumpFeeStrategy +from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text, Deterministic_Wallet, BumpFeeStrategy, Imported_Wallet from .address_synchronizer import TX_HEIGHT_LOCAL from .mnemonic import Mnemonic from .lnutil import SENT, RECEIVED @@ -666,15 +667,26 @@ class Commands: @command('wp') async def importprivkey(self, privkey, password=None, wallet: Abstract_Wallet = None): - """Import a private key.""" + """Import a private key or a list of private keys.""" if not wallet.can_import_privkey(): return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key." - try: - addr = wallet.import_private_key(privkey, password) - out = "Keypair imported: " + addr - except Exception as e: - out = "Error: " + repr(e) - return out + assert isinstance(wallet, Imported_Wallet) + keys = privkey.split() + if not keys: + return "Error: no keys given" + elif len(keys) == 1: + try: + addr = wallet.import_private_key(keys[0], password) + out = "Keypair imported: " + addr + except Exception as e: + out = "Error: " + repr(e) + return out + else: + good_inputs, bad_inputs = wallet.import_private_keys(keys, password) + return { + "good_keys": len(good_inputs), + "bad_keys": len(bad_inputs), + } def _resolver(self, x, wallet: Abstract_Wallet): if x is None: diff --git a/tests/test_commands.py b/tests/test_commands.py index d7a132dee..0898ab996 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -357,3 +357,41 @@ class TestCommandsTestnet(ElectrumTestCase): # test "from_coins" arg self.assertEqual("02000000000101b9723dfc69af058ef6613539a000d2cd098a2c8a74e802b6d8739db708ba8c9a0100000000fdffffff02a00f00000000000016001429e1fd187f0cac845946ae1b11dc136c536bfc0f84b2000000000000160014100611bcb3aee7aad176936cf4ed56ade03027aa0247304402203aa63539b673a3bd70a76482b17f35f8843974fab28f84143a00450789010bc40220779c2ce2d0217f973f1f6c9f718e19fc7ebd14dd8821a962f002437cda3082ec012102ee3f00141178006c78b0b458aab21588388335078c655459afe544211f15aee000000000", await cmds.bumpfee(tx=orig_rawtx, new_fee_rate='1.6', from_coins="9a8cba08b79d73d8b602e8748a2c8a09cdd200a0393561f68e05af69fc3d72b9:1", wallet=wallet)) + + @mock.patch.object(wallet.Abstract_Wallet, 'save_db') + async def test_importprivkey(self, mock_save_db): + wallet = restore_wallet_from_text('p2wpkh:cQUdWZehnGDwGn7CSc911cJBcWTAcnyzpLoJYTsFNYW1w6iaq7Nw p2wpkh:cNHsDLo137ngrr2wGf3mwqpwTUvpuDVAZrqzan9heHcMTK4rP5JB', + path='if_this_exists_mocking_failed_648151893', + config=self.config)['wallet'] + cmds = Commands(config=self.config) + self.assertEqual(2, len(wallet.get_addresses())) + # try importing a single bad privkey + out = await cmds.importprivkey("asdasd", wallet=wallet) # type: str + self.assertTrue(out.startswith("Error: ")) + self.assertTrue("cannot deserialize privkey" in out) + # try importing empty string + self.assertEqual("Error: no keys given", + await cmds.importprivkey("", wallet=wallet)) + # try importing a single good privkey + self.assertEqual("Keypair imported: mfgn4NuNberN5D9gvXaYwkqA6Q6WmF7wtD", + await cmds.importprivkey("cVam1duhd5wSxPPFJFKHNoDA2ZjRq7okvnBWyajsnAEcfPjC6Wbm", wallet=wallet)) + # try importing a list of good privkeys + privkeys1_str = " ".join([ + "p2pkh:cR1C6p34Gt9gxNJ57rUy96jgN3HQcZCgQzDWtCDNCnx4iLXM2S6g", + "p2pkh:cR1xqAf2hhhfxwAzquDss7ALrMeUN5gR82qp1nRWjqSQppnCNa27", + "cMnMgCvkELEmmnpK8MbcdE8aWRMSCxFMCJU61YReXVXiqjgjhee8", + "p2wpkh:cUfjuZDxEoATQwPmWCBH9kGArALfPij5JruQNfM6NTtYF12fds8Y", + "p2wpkh:cP2U7f2jgaQf1zBAWzNUrhs6mGRCg3uyTvNFUUQ9Q8eyXnpkXSqo", + "p2wpkh:cThVmpx3VgZRhbKQqK1FmLzaFTiUsN1Kp1CBwZVL6VfR33mNMxok", + ]) + self.assertEqual({"good_keys": 6, "bad_keys": 0}, + await cmds.importprivkey(privkeys1_str, wallet=wallet)) + # try importing a list of mixed good/bad privkeys + privkeys2_str = " ".join([ + "qweqwe", + "p2wpkh:cRFfD1EqocayY3xsw343inJ47LVsZHLbUgPzLmUbXhE6XNJ46Swn", + "p2pkh:cThVmpx3VgZRhbKQqK1FmLzaBAAADDDDkeeeeeeeeeeeeeeeeeeeey", + ]) + self.assertEqual({"good_keys": 1, "bad_keys": 2}, + await cmds.importprivkey(privkeys2_str, wallet=wallet)) + self.assertEqual(10, len(wallet.get_addresses()))