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) domain = set(domain)
# 1. Get the history of each address in the domain, maintain the # 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 # 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: for addr in domain:
h = self.get_address_history(addr) h = self.get_address_history(addr)
for tx_hash, height in h: 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') return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
def serialize_privkey(secret: bytes, compressed: bool, txin_type: str, def serialize_privkey(secret: bytes, compressed: bool, txin_type: str, *,
internal_use: bool=False) -> str: internal_use: bool = False) -> str:
# we only export secrets inside curve range # we only export secrets inside curve range
secret = ecc.ECPrivkey.normalize_secret_bytes(secret) secret = ecc.ECPrivkey.normalize_secret_bytes(secret)
if internal_use: if internal_use:

7
electrum/commands.py

@ -414,6 +414,13 @@ class Commands:
domain = address domain = address
return [wallet.export_private_key(address, password) for address in domain] 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') @command('w')
async def ismine(self, address, wallet: Abstract_Wallet = None): async def ismine(self, address, wallet: Abstract_Wallet = None):
"""Check if address is in wallet. Return true if and only address is in wallet""" """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", self.assertEqual("0200000000010139c5375fe9da7bd377c1783002b129f8c57d3e724d62f5eacb9739ca691a229d0100000000feffffff01301b0f0000000000160014ac0e2d229200bffb2167ed6fd196aef9d687d8bb0247304402206367fb2ddd723985f5f51e0f2435084c0a66f5c26f4403a75d3dd417b71a20450220545dc3637bcb49beedbbdf5063e05cad63be91af4f839886451c30ecd6edf1d20121021f110909ded653828a254515b58498a6bafc96799fb0851554463ed44ca7d9da00000000",
cmds._run('serialize', (jsontx,))) 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 import itertools
from .i18n import _ 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 .crypto import sha256
from .util import (NotEnoughFunds, UserCancelled, profiler, from .util import (NotEnoughFunds, UserCancelled, profiler,
format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates, format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
@ -462,7 +462,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
"""Return script type of wallet address.""" """Return script type of wallet address."""
pass 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(): if self.is_watching_only():
raise Exception(_("This is a watching-only wallet")) raise Exception(_("This is a watching-only wallet"))
if not is_address(address): if not is_address(address):
@ -475,6 +475,9 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type) serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type)
return serialized_privkey 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 @abstractmethod
def get_public_keys(self, address: str) -> Sequence[str]: def get_public_keys(self, address: str) -> Sequence[str]:
pass pass
@ -2201,6 +2204,13 @@ class Deterministic_Wallet(Abstract_Wallet):
pubkeys = self.derive_pubkeys(for_change, n) pubkeys = self.derive_pubkeys(for_change, n)
return self.pubkeys_to_address(pubkeys) 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): def get_public_keys_with_deriv_info(self, address: str):
der_suffix = self.get_address_index(address) der_suffix = self.get_address_index(address)
der_suffix = [int(x) for x in der_suffix] der_suffix = [int(x) for x in der_suffix]
@ -2301,7 +2311,7 @@ class Deterministic_Wallet(Abstract_Wallet):
def get_fingerprint(self): def get_fingerprint(self):
return self.get_master_public_key() return self.get_master_public_key()
def get_txin_type(self, address): def get_txin_type(self, address=None):
return self.txin_type return self.txin_type

Loading…
Cancel
Save