Browse Source

commands: add new cmd "getprivatekeyforpath" to export a WIF for a path

related: #6061
hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
900a7631cf
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 2
      electrum/address_synchronizer.py
  2. 4
      electrum/bitcoin.py
  3. 7
      electrum/commands.py
  4. 14
      electrum/tests/test_commands.py
  5. 16
      electrum/wallet.py

2
electrum/address_synchronizer.py

@ -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:

4
electrum/bitcoin.py

@ -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:

7
electrum/commands.py

@ -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"""

14
electrum/tests/test_commands.py

@ -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))

16
electrum/wallet.py

@ -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,
@ -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):
@ -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
@ -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]
@ -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

Loading…
Cancel
Save