Extend Wallet Import Format with txin type. Extend class Imported_Wallet.
This commit is contained in:
parent
4864c802dd
commit
e8b564c0e7
12
electrum
12
electrum
|
@ -137,11 +137,19 @@ def run_non_RPC(config):
|
||||||
wallet = Imported_Wallet(storage)
|
wallet = Imported_Wallet(storage)
|
||||||
for x in text.split():
|
for x in text.split():
|
||||||
wallet.import_address(x)
|
wallet.import_address(x)
|
||||||
|
elif keystore.is_private_key_list(text):
|
||||||
|
k = keystore.Imported_KeyStore({})
|
||||||
|
storage.put('keystore', k.dump())
|
||||||
|
storage.put('use_encryption', bool(password))
|
||||||
|
wallet = Imported_Wallet(storage)
|
||||||
|
for x in text.split():
|
||||||
|
wallet.import_private_key(x, password)
|
||||||
|
storage.write()
|
||||||
else:
|
else:
|
||||||
if keystore.is_seed(text):
|
if keystore.is_seed(text):
|
||||||
k = keystore.from_seed(text, passphrase)
|
k = keystore.from_seed(text, passphrase)
|
||||||
elif keystore.is_any_key(text):
|
elif keystore.is_master_key(text):
|
||||||
k = keystore.from_keys(text)
|
k = keystore.from_master_key(text)
|
||||||
else:
|
else:
|
||||||
sys.exit("Error: Seed or key not recognized")
|
sys.exit("Error: Seed or key not recognized")
|
||||||
if password:
|
if password:
|
||||||
|
|
|
@ -1864,20 +1864,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
if not address:
|
if not address:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
pk_list = self.wallet.get_private_key(address, password)
|
pk, redeem_script = self.wallet.export_private_key(address, password)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
self.show_message(str(e))
|
self.show_message(str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
d = WindowModalDialog(self, _("Private key"))
|
d = WindowModalDialog(self, _("Private key"))
|
||||||
d.setMinimumSize(600, 200)
|
d.setMinimumSize(600, 200)
|
||||||
vbox = QVBoxLayout()
|
vbox = QVBoxLayout()
|
||||||
vbox.addWidget( QLabel(_("Address") + ': ' + address))
|
vbox.addWidget( QLabel(_("Address") + ': ' + address))
|
||||||
vbox.addWidget( QLabel(_("Private key") + ':'))
|
vbox.addWidget( QLabel(_("Private key") + ':'))
|
||||||
keys_e = ShowQRTextEdit(text='\n'.join(pk_list))
|
keys_e = ShowQRTextEdit(text=pk)
|
||||||
keys_e.addCopyButton(self.app)
|
keys_e.addCopyButton(self.app)
|
||||||
vbox.addWidget(keys_e)
|
vbox.addWidget(keys_e)
|
||||||
|
if redeem_script:
|
||||||
|
vbox.addWidget( QLabel(_("Redeem Script") + ':'))
|
||||||
|
rds_e = ShowQRTextEdit(text=redeem_script)
|
||||||
|
rds_e.addCopyButton(self.app)
|
||||||
|
vbox.addWidget(rds_e)
|
||||||
vbox.addLayout(Buttons(CloseButton(d)))
|
vbox.addLayout(Buttons(CloseButton(d)))
|
||||||
d.setLayout(vbox)
|
d.setLayout(vbox)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
@ -2353,7 +2357,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
if not self.wallet.can_import_privkey():
|
if not self.wallet.can_import_privkey():
|
||||||
return
|
return
|
||||||
title, msg = _('Import private keys'), _("Enter private keys")
|
title, msg = _('Import private keys'), _("Enter private keys")
|
||||||
self._do_import(title, msg, lambda x: self.wallet.import_key(x, password))
|
self._do_import(title, msg, lambda x: self.wallet.import_private_key(x, password))
|
||||||
|
|
||||||
def update_fiat(self):
|
def update_fiat(self):
|
||||||
b = self.fx and self.fx.is_enabled()
|
b = self.fx and self.fx.is_enabled()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from .version import ELECTRUM_VERSION
|
from .version import ELECTRUM_VERSION
|
||||||
from .util import format_satoshis, print_msg, print_error, set_verbosity
|
from .util import format_satoshis, print_msg, print_error, set_verbosity
|
||||||
from .wallet import Synchronizer, Wallet, Imported_Wallet
|
from .wallet import Synchronizer, Wallet
|
||||||
from .storage import WalletStorage
|
from .storage import WalletStorage
|
||||||
from .coinchooser import COIN_CHOOSERS
|
from .coinchooser import COIN_CHOOSERS
|
||||||
from .network import Network, pick_random_server
|
from .network import Network, pick_random_server
|
||||||
|
|
|
@ -82,7 +82,7 @@ class BaseWizard(object):
|
||||||
('standard', _("Standard wallet")),
|
('standard', _("Standard wallet")),
|
||||||
('2fa', _("Wallet with two-factor authentication")),
|
('2fa', _("Wallet with two-factor authentication")),
|
||||||
('multisig', _("Multi-signature wallet")),
|
('multisig', _("Multi-signature wallet")),
|
||||||
('imported', _("Watch Bitcoin addresses")),
|
('imported', _("Import Bitcoin addresses or private keys")),
|
||||||
]
|
]
|
||||||
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
|
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
|
||||||
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
|
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
|
||||||
|
@ -102,7 +102,7 @@ class BaseWizard(object):
|
||||||
self.load_2fa()
|
self.load_2fa()
|
||||||
action = self.storage.get_action()
|
action = self.storage.get_action()
|
||||||
elif choice == 'imported':
|
elif choice == 'imported':
|
||||||
action = 'import_addresses'
|
action = 'import_addresses_or_keys'
|
||||||
self.run(action)
|
self.run(action)
|
||||||
|
|
||||||
def choose_multisig(self):
|
def choose_multisig(self):
|
||||||
|
@ -137,26 +137,32 @@ class BaseWizard(object):
|
||||||
|
|
||||||
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
|
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
|
||||||
|
|
||||||
def import_addresses(self):
|
def import_addresses_or_keys(self):
|
||||||
v = keystore.is_address_list
|
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x)
|
||||||
title = _("Import Bitcoin Addresses")
|
title = _("Import Bitcoin Addresses")
|
||||||
message = _("Enter a list of Bitcoin addresses. This will create a watching-only wallet.")
|
message = _("Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.")
|
||||||
self.add_xpub_dialog(title=title, message=message, run_next=self.on_import_addresses, is_valid=v)
|
self.add_xpub_dialog(title=title, message=message, run_next=self.on_import, is_valid=v)
|
||||||
|
|
||||||
def on_import_addresses(self, text):
|
def on_import(self, text):
|
||||||
assert keystore.is_address_list(text)
|
if keystore.is_address_list(text):
|
||||||
self.wallet = Imported_Wallet(self.storage)
|
self.wallet = Imported_Wallet(self.storage)
|
||||||
for x in text.split():
|
for x in text.split():
|
||||||
self.wallet.import_address(x)
|
self.wallet.import_address(x)
|
||||||
|
elif keystore.is_private_key_list(text):
|
||||||
|
k = keystore.Imported_KeyStore({})
|
||||||
|
self.storage.put('keystore', k.dump())
|
||||||
|
self.wallet = Imported_Wallet(self.storage)
|
||||||
|
for x in text.split():
|
||||||
|
self.wallet.import_private_key(x, None)
|
||||||
self.terminate()
|
self.terminate()
|
||||||
|
|
||||||
def restore_from_key(self):
|
def restore_from_key(self):
|
||||||
if self.wallet_type == 'standard':
|
if self.wallet_type == 'standard':
|
||||||
v = keystore.is_any_key
|
v = keystore.is_master_key
|
||||||
title = _("Create keystore from keys")
|
title = _("Create keystore from a master key")
|
||||||
message = ' '.join([
|
message = ' '.join([
|
||||||
_("To create a watching-only wallet, please enter your master public key (xpub)."),
|
_("To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub)."),
|
||||||
_("To create a spending wallet, please enter a master private key (xprv), or a list of Bitcoin private keys.")
|
_("To create a spending wallet, please enter a master private key (xprv/yprv/zprv).")
|
||||||
])
|
])
|
||||||
self.add_xpub_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v)
|
self.add_xpub_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v)
|
||||||
else:
|
else:
|
||||||
|
@ -164,7 +170,7 @@ class BaseWizard(object):
|
||||||
self.add_cosigner_dialog(index=i, run_next=self.on_restore_from_key, is_valid=keystore.is_bip32_key)
|
self.add_cosigner_dialog(index=i, run_next=self.on_restore_from_key, is_valid=keystore.is_bip32_key)
|
||||||
|
|
||||||
def on_restore_from_key(self, text):
|
def on_restore_from_key(self, text):
|
||||||
k = keystore.from_keys(text)
|
k = keystore.from_master_key(text)
|
||||||
self.on_keystore(k)
|
self.on_keystore(k)
|
||||||
|
|
||||||
def choose_hw_device(self):
|
def choose_hw_device(self):
|
||||||
|
@ -357,7 +363,7 @@ class BaseWizard(object):
|
||||||
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
|
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
|
||||||
|
|
||||||
def on_cosigner(self, text, password, i):
|
def on_cosigner(self, text, password, i):
|
||||||
k = keystore.from_keys(text, password)
|
k = keystore.from_master_key(text, password)
|
||||||
self.on_keystore(k)
|
self.on_keystore(k)
|
||||||
|
|
||||||
def choose_seed_type(self):
|
def choose_seed_type(self):
|
||||||
|
|
|
@ -35,7 +35,7 @@ import pyaes
|
||||||
|
|
||||||
from .util import bfh, bh2u, to_string
|
from .util import bfh, bh2u, to_string
|
||||||
from . import version
|
from . import version
|
||||||
from .util import print_error, InvalidPassword, assert_bytes, to_bytes
|
from .util import print_error, InvalidPassword, assert_bytes, to_bytes, inv_dict
|
||||||
from . import segwit_addr
|
from . import segwit_addr
|
||||||
|
|
||||||
def read_json_dict(filename):
|
def read_json_dict(filename):
|
||||||
|
@ -65,7 +65,6 @@ XPUB_HEADERS = {
|
||||||
|
|
||||||
# Bitcoin network constants
|
# Bitcoin network constants
|
||||||
TESTNET = False
|
TESTNET = False
|
||||||
NOLNET = False
|
|
||||||
ADDRTYPE_P2PKH = 0
|
ADDRTYPE_P2PKH = 0
|
||||||
ADDRTYPE_P2SH = 5
|
ADDRTYPE_P2SH = 5
|
||||||
SEGWIT_HRP = "bc"
|
SEGWIT_HRP = "bc"
|
||||||
|
@ -334,6 +333,30 @@ def script_to_p2wsh(script):
|
||||||
return hash_to_segwit_addr(sha256(bfh(script)))
|
return hash_to_segwit_addr(sha256(bfh(script)))
|
||||||
|
|
||||||
|
|
||||||
|
def pubkey_to_address(txin_type, pubkey):
|
||||||
|
if txin_type == 'p2pkh':
|
||||||
|
return public_key_to_p2pkh(bfh(pubkey))
|
||||||
|
elif txin_type == 'p2wpkh':
|
||||||
|
return hash_to_segwit_addr(hash_160(bfh(pubkey)))
|
||||||
|
elif txin_type == 'p2wpkh-p2sh':
|
||||||
|
scriptSig = transaction.p2wpkh_nested_script(pubkey)
|
||||||
|
return hash160_to_p2sh(hash_160(bfh(scriptSig)))
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(txin_type)
|
||||||
|
|
||||||
|
def redeem_script_to_address(txin_type, redeem_script):
|
||||||
|
if txin_type == 'p2sh':
|
||||||
|
return hash160_to_p2sh(hash_160(bfh(redeem_script)))
|
||||||
|
elif txin_type == 'p2wsh':
|
||||||
|
return script_to_p2wsh(redeem_script)
|
||||||
|
elif txin_type == 'p2wsh-p2sh':
|
||||||
|
scriptSig = transaction.p2wsh_nested_script(redeem_script)
|
||||||
|
return hash160_to_p2sh(hash_160(bfh(scriptSig)))
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(txin_type)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def address_to_script(addr):
|
def address_to_script(addr):
|
||||||
witver, witprog = segwit_addr.decode(SEGWIT_HRP, addr)
|
witver, witprog = segwit_addr.decode(SEGWIT_HRP, addr)
|
||||||
if witprog is not None:
|
if witprog is not None:
|
||||||
|
@ -448,33 +471,42 @@ def DecodeBase58Check(psz):
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
def PrivKeyToSecret(privkey):
|
|
||||||
return privkey[9:9+32]
|
# extended key export format for segwit
|
||||||
|
|
||||||
|
SCRIPT_TYPES = {
|
||||||
|
'p2pkh':0,
|
||||||
|
'p2wpkh':1,
|
||||||
|
'p2wpkh-p2sh':2,
|
||||||
|
'p2sh':5,
|
||||||
|
'p2wsh':6,
|
||||||
|
'p2wsh-p2sh':7
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def SecretToASecret(secret, compressed=False):
|
def serialize_privkey(secret, compressed, txin_type):
|
||||||
addrtype = ADDRTYPE_P2PKH
|
prefix = bytes([(SCRIPT_TYPES[txin_type]+128)&255])
|
||||||
vchIn = bytes([(addrtype+128)&255]) + secret
|
suffix = b'\01' if compressed else b''
|
||||||
if compressed: vchIn += b'\01'
|
vchIn = prefix + secret + suffix
|
||||||
return EncodeBase58Check(vchIn)
|
return EncodeBase58Check(vchIn)
|
||||||
|
|
||||||
|
|
||||||
def ASecretToSecret(key):
|
def deserialize_privkey(key):
|
||||||
addrtype = ADDRTYPE_P2PKH
|
# whether the pubkey is compressed should be visible from the keystore
|
||||||
vch = DecodeBase58Check(key)
|
vch = DecodeBase58Check(key)
|
||||||
if vch and vch[0] == ((addrtype+128)&255):
|
if is_minikey(key):
|
||||||
return vch[1:]
|
return 'p2pkh', minikey_to_private_key(key), True
|
||||||
elif is_minikey(key):
|
elif vch:
|
||||||
return minikey_to_private_key(key)
|
txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - 128]
|
||||||
|
assert len(vch) in [33, 34]
|
||||||
|
compressed = len(vch) == 34
|
||||||
|
return txin_type, vch[1:33], compressed
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def regenerate_key(sec):
|
def regenerate_key(pk):
|
||||||
b = ASecretToSecret(sec)
|
assert len(pk) == 32
|
||||||
if not b:
|
return EC_KEY(pk)
|
||||||
return False
|
|
||||||
b = b[0:32]
|
|
||||||
return EC_KEY(b)
|
|
||||||
|
|
||||||
|
|
||||||
def GetPubKey(pubkey, compressed=False):
|
def GetPubKey(pubkey, compressed=False):
|
||||||
|
@ -486,15 +518,12 @@ def GetSecret(pkey):
|
||||||
|
|
||||||
|
|
||||||
def is_compressed(sec):
|
def is_compressed(sec):
|
||||||
b = ASecretToSecret(sec)
|
return deserialize_privkey(sec)[2]
|
||||||
return len(b) == 33
|
|
||||||
|
|
||||||
|
|
||||||
def public_key_from_private_key(sec):
|
def public_key_from_private_key(pk, compressed):
|
||||||
# rebuild public key from private key, compressed or uncompressed
|
# rebuild public key from private key, compressed or uncompressed
|
||||||
pkey = regenerate_key(sec)
|
pkey = regenerate_key(pk)
|
||||||
assert pkey
|
|
||||||
compressed = is_compressed(sec)
|
|
||||||
public_key = GetPubKey(pkey.pubkey, compressed)
|
public_key = GetPubKey(pkey.pubkey, compressed)
|
||||||
return bh2u(public_key)
|
return bh2u(public_key)
|
||||||
|
|
||||||
|
@ -533,7 +562,7 @@ def is_p2sh(addr):
|
||||||
|
|
||||||
def is_private_key(key):
|
def is_private_key(key):
|
||||||
try:
|
try:
|
||||||
k = ASecretToSecret(key)
|
k = deserialize_privkey(key)
|
||||||
return k is not False
|
return k is not False
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
@ -970,5 +999,4 @@ def bip32_public_derivation(xpub, branch, sequence):
|
||||||
def bip32_private_key(sequence, k, chain):
|
def bip32_private_key(sequence, k, chain):
|
||||||
for i in sequence:
|
for i in sequence:
|
||||||
k, chain = CKD_priv(k, chain, i)
|
k, chain = CKD_priv(k, chain, i)
|
||||||
return SecretToASecret(k, True)
|
return k
|
||||||
|
|
||||||
|
|
|
@ -364,11 +364,11 @@ class Commands:
|
||||||
|
|
||||||
@command('wp')
|
@command('wp')
|
||||||
def importprivkey(self, privkey, password=None):
|
def importprivkey(self, privkey, password=None):
|
||||||
"""Import a private key. """
|
"""Import a private key."""
|
||||||
if not self.wallet.can_import_privkey():
|
if not self.wallet.can_import_privkey():
|
||||||
return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key."
|
return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key."
|
||||||
try:
|
try:
|
||||||
addr = self.wallet.import_key(privkey, password)
|
addr = self.wallet.import_private_key(privkey, password)
|
||||||
out = "Keypair imported: " + addr
|
out = "Keypair imported: " + addr
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
out = "Error: " + str(e)
|
out = "Error: " + str(e)
|
||||||
|
@ -687,6 +687,7 @@ param_descriptions = {
|
||||||
'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.',
|
'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.',
|
||||||
'requested_amount': 'Requested amount (in BTC).',
|
'requested_amount': 'Requested amount (in BTC).',
|
||||||
'outputs': 'list of ["address", amount]',
|
'outputs': 'list of ["address", amount]',
|
||||||
|
'redeem_script': 'redeem script (hexadecimal)',
|
||||||
}
|
}
|
||||||
|
|
||||||
command_options = {
|
command_options = {
|
||||||
|
|
|
@ -141,24 +141,22 @@ class Imported_KeyStore(Software_KeyStore):
|
||||||
pubkey = list(self.keypairs.keys())[0]
|
pubkey = list(self.keypairs.keys())[0]
|
||||||
self.get_private_key(pubkey, password)
|
self.get_private_key(pubkey, password)
|
||||||
|
|
||||||
def import_key(self, sec, password):
|
def import_privkey(self, sec, password):
|
||||||
try:
|
txin_type, privkey, compressed = deserialize_privkey(sec)
|
||||||
pubkey = public_key_from_private_key(sec)
|
pubkey = public_key_from_private_key(privkey, compressed)
|
||||||
except Exception:
|
|
||||||
raise BaseException('Invalid private key')
|
|
||||||
# allow overwrite
|
|
||||||
self.keypairs[pubkey] = pw_encode(sec, password)
|
self.keypairs[pubkey] = pw_encode(sec, password)
|
||||||
return pubkey
|
return txin_type, pubkey
|
||||||
|
|
||||||
def delete_imported_key(self, key):
|
def delete_imported_key(self, key):
|
||||||
self.keypairs.pop(key)
|
self.keypairs.pop(key)
|
||||||
|
|
||||||
def get_private_key(self, pubkey, password):
|
def get_private_key(self, pubkey, password):
|
||||||
pk = pw_decode(self.keypairs[pubkey], password)
|
sec = pw_decode(self.keypairs[pubkey], password)
|
||||||
|
txin_type, privkey, compressed = deserialize_privkey(sec)
|
||||||
# this checks the password
|
# this checks the password
|
||||||
if pubkey != public_key_from_private_key(pk):
|
if pubkey != public_key_from_private_key(privkey, compressed):
|
||||||
raise InvalidPassword()
|
raise InvalidPassword()
|
||||||
return pk
|
return privkey
|
||||||
|
|
||||||
def get_pubkey_derivation(self, x_pubkey):
|
def get_pubkey_derivation(self, x_pubkey):
|
||||||
if x_pubkey[0:2] in ['02', '03', '04']:
|
if x_pubkey[0:2] in ['02', '03', '04']:
|
||||||
|
@ -180,8 +178,6 @@ class Imported_KeyStore(Software_KeyStore):
|
||||||
c = pw_encode(b, new_password)
|
c = pw_encode(b, new_password)
|
||||||
self.keypairs[k] = c
|
self.keypairs[k] = c
|
||||||
|
|
||||||
def txin_type(self):
|
|
||||||
return 'p2pkh'
|
|
||||||
|
|
||||||
|
|
||||||
class Deterministic_KeyStore(Software_KeyStore):
|
class Deterministic_KeyStore(Software_KeyStore):
|
||||||
|
@ -277,17 +273,6 @@ class Xpub:
|
||||||
return
|
return
|
||||||
return derivation
|
return derivation
|
||||||
|
|
||||||
def txin_type(self):
|
|
||||||
xtype = deserialize_xpub(self.xpub)[0]
|
|
||||||
if xtype == 'standard':
|
|
||||||
return 'p2pkh'
|
|
||||||
elif xtype == 'segwit':
|
|
||||||
return 'p2wpkh'
|
|
||||||
elif xtype == 'segwit_p2sh':
|
|
||||||
return 'p2wpkh-p2sh'
|
|
||||||
else:
|
|
||||||
raise BaseException('unknown txin_type', xtype)
|
|
||||||
|
|
||||||
|
|
||||||
class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
|
|
||||||
|
@ -411,11 +396,6 @@ class Old_KeyStore(Deterministic_KeyStore):
|
||||||
def get_sequence(self, mpk, for_change, n):
|
def get_sequence(self, mpk, for_change, n):
|
||||||
return string_to_number(Hash(("%d:%d:"%(n, for_change)).encode('ascii') + bfh(mpk)))
|
return string_to_number(Hash(("%d:%d:"%(n, for_change)).encode('ascii') + bfh(mpk)))
|
||||||
|
|
||||||
def get_address(self, for_change, n):
|
|
||||||
pubkey = self.get_pubkey(for_change, n)
|
|
||||||
address = public_key_to_p2pkh(bfh(pubkey))
|
|
||||||
return address
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_pubkey_from_mpk(self, mpk, for_change, n):
|
def get_pubkey_from_mpk(self, mpk, for_change, n):
|
||||||
z = self.get_sequence(mpk, for_change, n)
|
z = self.get_sequence(mpk, for_change, n)
|
||||||
|
@ -431,8 +411,7 @@ class Old_KeyStore(Deterministic_KeyStore):
|
||||||
order = generator_secp256k1.order()
|
order = generator_secp256k1.order()
|
||||||
secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % order
|
secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % order
|
||||||
pk = number_to_string(secexp, generator_secp256k1.order())
|
pk = number_to_string(secexp, generator_secp256k1.order())
|
||||||
compressed = False
|
return pk
|
||||||
return SecretToASecret(pk, compressed)
|
|
||||||
|
|
||||||
def get_private_key(self, sequence, password):
|
def get_private_key(self, sequence, password):
|
||||||
seed = self.get_hex_seed(password)
|
seed = self.get_hex_seed(password)
|
||||||
|
@ -491,8 +470,6 @@ class Old_KeyStore(Deterministic_KeyStore):
|
||||||
decoded = pw_decode(self.seed, old_password)
|
decoded = pw_decode(self.seed, old_password)
|
||||||
self.seed = pw_encode(decoded, new_password)
|
self.seed = pw_encode(decoded, new_password)
|
||||||
|
|
||||||
def txin_type(self):
|
|
||||||
return 'p2phk'
|
|
||||||
|
|
||||||
|
|
||||||
class Hardware_KeyStore(KeyStore, Xpub):
|
class Hardware_KeyStore(KeyStore, Xpub):
|
||||||
|
@ -692,7 +669,7 @@ def is_private_key_list(text):
|
||||||
|
|
||||||
is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
|
is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
|
||||||
is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
|
is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
|
||||||
is_any_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x) or is_private_key_list(x)
|
is_master_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x)
|
||||||
is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
|
is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
|
||||||
is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
|
is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
|
||||||
|
|
||||||
|
@ -740,15 +717,13 @@ def from_xprv(xprv):
|
||||||
k.xpub = xpub
|
k.xpub = xpub
|
||||||
return k
|
return k
|
||||||
|
|
||||||
def from_keys(text):
|
def from_master_key(text):
|
||||||
if is_xprv(text):
|
if is_xprv(text):
|
||||||
k = from_xprv(text)
|
k = from_xprv(text)
|
||||||
elif is_old_mpk(text):
|
elif is_old_mpk(text):
|
||||||
k = from_old_mpk(text)
|
k = from_old_mpk(text)
|
||||||
elif is_xpub(text):
|
elif is_xpub(text):
|
||||||
k = from_xpub(text)
|
k = from_xpub(text)
|
||||||
elif is_private_key_list(text):
|
|
||||||
k = from_private_key_list(text)
|
|
||||||
else:
|
else:
|
||||||
raise BaseException('Invalid key')
|
raise BaseException('Invalid key')
|
||||||
return k
|
return k
|
||||||
|
|
|
@ -890,7 +890,8 @@ class Transaction:
|
||||||
if x_pubkey in keypairs.keys():
|
if x_pubkey in keypairs.keys():
|
||||||
print_error("adding signature for", x_pubkey)
|
print_error("adding signature for", x_pubkey)
|
||||||
sec = keypairs.get(x_pubkey)
|
sec = keypairs.get(x_pubkey)
|
||||||
pubkey = public_key_from_private_key(sec)
|
compressed = True
|
||||||
|
pubkey = public_key_from_private_key(sec, compressed)
|
||||||
# add signature
|
# add signature
|
||||||
pre_hash = Hash(bfh(self.serialize_preimage(i)))
|
pre_hash = Hash(bfh(self.serialize_preimage(i)))
|
||||||
pkey = regenerate_key(sec)
|
pkey = regenerate_key(sec)
|
||||||
|
|
|
@ -40,6 +40,10 @@ from .i18n import _
|
||||||
import urllib.request, urllib.parse, urllib.error
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
|
def inv_dict(d):
|
||||||
|
return {v: k for k, v in d.items()}
|
||||||
|
|
||||||
|
|
||||||
base_units = {'BTC':8, 'mBTC':5, 'uBTC':2}
|
base_units = {'BTC':8, 'mBTC':5, 'uBTC':2}
|
||||||
fee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')]
|
fee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')]
|
||||||
|
|
||||||
|
|
193
lib/wallet.py
193
lib/wallet.py
|
@ -262,22 +262,24 @@ class Abstract_Wallet(PrintError):
|
||||||
return address in self.change_addresses
|
return address in self.change_addresses
|
||||||
|
|
||||||
def get_address_index(self, address):
|
def get_address_index(self, address):
|
||||||
if self.keystore.can_import():
|
if address in self.receiving_addresses:
|
||||||
for pubkey in self.keystore.keypairs.keys():
|
|
||||||
if self.pubkeys_to_address(pubkey) == address:
|
|
||||||
return pubkey
|
|
||||||
elif address in self.receiving_addresses:
|
|
||||||
return False, self.receiving_addresses.index(address)
|
return False, self.receiving_addresses.index(address)
|
||||||
if address in self.change_addresses:
|
if address in self.change_addresses:
|
||||||
return True, self.change_addresses.index(address)
|
return True, self.change_addresses.index(address)
|
||||||
raise Exception("Address not found", address)
|
raise Exception("Address not found", address)
|
||||||
|
|
||||||
def get_private_key(self, address, password):
|
def export_private_key(self, address, password):
|
||||||
|
""" extended WIF format """
|
||||||
if self.is_watching_only():
|
if self.is_watching_only():
|
||||||
return []
|
return []
|
||||||
index = self.get_address_index(address)
|
index = self.get_address_index(address)
|
||||||
pk = self.keystore.get_private_key(index, password)
|
pk = self.keystore.get_private_key(index, password)
|
||||||
return [pk]
|
if self.txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
|
||||||
|
pubkeys = self.get_public_keys(address)
|
||||||
|
redeem_script = self.pubkeys_to_redeem_script(pubkeys)
|
||||||
|
else:
|
||||||
|
redeem_script = None
|
||||||
|
return bitcoin.serialize_privkey(pk, True, self.txin_type), redeem_script
|
||||||
|
|
||||||
def get_public_key(self, address):
|
def get_public_key(self, address):
|
||||||
if self.keystore.can_import():
|
if self.keystore.can_import():
|
||||||
|
@ -1343,28 +1345,41 @@ class Imported_Wallet(Abstract_Wallet):
|
||||||
def __init__(self, storage):
|
def __init__(self, storage):
|
||||||
Abstract_Wallet.__init__(self, storage)
|
Abstract_Wallet.__init__(self, storage)
|
||||||
|
|
||||||
def load_keystore(self):
|
def is_watching_only(self):
|
||||||
pass
|
return self.keystore is None
|
||||||
|
|
||||||
def load_addresses(self):
|
|
||||||
self.addresses = self.storage.get('addresses', [])
|
|
||||||
self.receiving_addresses = self.addresses
|
|
||||||
self.change_addresses = []
|
|
||||||
|
|
||||||
def get_keystores(self):
|
def get_keystores(self):
|
||||||
return []
|
return [self.keystore] if self.keystore else []
|
||||||
|
|
||||||
def has_password(self):
|
def check_password(self, password):
|
||||||
return False
|
self.keystore.check_password(password)
|
||||||
|
|
||||||
|
def can_import_privkey(self):
|
||||||
|
return bool(self.keystore)
|
||||||
|
|
||||||
|
def load_keystore(self):
|
||||||
|
self.keystore = load_keystore(self.storage, 'keystore') if self.storage.get('keystore') else None
|
||||||
|
|
||||||
|
def save_keystore(self):
|
||||||
|
self.storage.put('keystore', self.keystore.dump())
|
||||||
|
|
||||||
|
def load_addresses(self):
|
||||||
|
self.addresses = self.storage.get('addresses', {})
|
||||||
|
# convert list
|
||||||
|
if type(self.addresses) is list:
|
||||||
|
self.addresses = dict([(x, None) for x in self.addresses])
|
||||||
|
|
||||||
|
def save_addresses(self):
|
||||||
|
self.storage.put('addresses', self.addresses)
|
||||||
|
|
||||||
def can_change_password(self):
|
def can_change_password(self):
|
||||||
return False
|
return not self.is_watching_only()
|
||||||
|
|
||||||
def can_import_address(self):
|
def can_import_address(self):
|
||||||
return True
|
return self.is_watching_only()
|
||||||
|
|
||||||
def is_watching_only(self):
|
def can_delete_address(self):
|
||||||
return True
|
return self.is_watching_only()
|
||||||
|
|
||||||
def has_seed(self):
|
def has_seed(self):
|
||||||
return False
|
return False
|
||||||
|
@ -1375,6 +1390,9 @@ class Imported_Wallet(Abstract_Wallet):
|
||||||
def is_used(self, address):
|
def is_used(self, address):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_change(self, address):
|
||||||
|
return False
|
||||||
|
|
||||||
def get_master_public_keys(self):
|
def get_master_public_keys(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -1385,38 +1403,84 @@ class Imported_Wallet(Abstract_Wallet):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def get_addresses(self, include_change=False):
|
def get_addresses(self, include_change=False):
|
||||||
return self.addresses
|
return sorted(self.addresses.keys())
|
||||||
|
|
||||||
|
def get_receiving_addresses(self):
|
||||||
|
return self.get_addresses()
|
||||||
|
|
||||||
|
def get_change_addresses(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def import_address(self, address):
|
def import_address(self, address):
|
||||||
if address in self.addresses:
|
if address in self.addresses:
|
||||||
return
|
return ''
|
||||||
self.addresses.append(address)
|
self.addresses[address] = {}
|
||||||
self.storage.put('addresses', self.addresses)
|
self.storage.put('addresses', self.addresses)
|
||||||
self.storage.write()
|
self.storage.write()
|
||||||
self.add_address(address)
|
self.add_address(address)
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def can_delete_address(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def delete_address(self, address):
|
def delete_address(self, address):
|
||||||
if address not in self.addresses:
|
if address not in self.addresses:
|
||||||
return
|
return
|
||||||
self.addresses.remove(address)
|
self.addresses.pop(address)
|
||||||
self.storage.put('addresses', self.addresses)
|
self.storage.put('addresses', self.addresses)
|
||||||
self.storage.write()
|
self.storage.write()
|
||||||
|
|
||||||
def get_receiving_addresses(self):
|
def get_address_index(self, address):
|
||||||
return self.addresses[:]
|
if self.keystore.can_import():
|
||||||
|
return self.addresses[address]['pubkey']
|
||||||
|
|
||||||
def get_change_addresses(self):
|
def import_private_key(self, sec, pw, redeem_script=None):
|
||||||
return []
|
try:
|
||||||
|
txin_type, pubkey = self.keystore.import_privkey(sec, pw)
|
||||||
|
except Exception:
|
||||||
|
raise BaseException('Invalid private key', sec)
|
||||||
|
if txin_type in ['p2pkh', 'p2wpkh', 'p2wpkh-p2sh']:
|
||||||
|
if redeem_script is not None:
|
||||||
|
raise BaseException('Cannot use redeem script with', txin_type, sec)
|
||||||
|
addr = bitcoin.pubkey_to_address(txin_type, pubkey)
|
||||||
|
elif txin_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
|
||||||
|
if redeem_script is None:
|
||||||
|
raise BaseException('Redeem script required for', txin_type, sec)
|
||||||
|
addr = bitcoin.redeem_script_to_address(txin_type, redeem_script)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(self.txin_type)
|
||||||
|
self.addresses[addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':redeem_script}
|
||||||
|
self.save_keystore()
|
||||||
|
self.save_addresses()
|
||||||
|
self.storage.write()
|
||||||
|
self.add_address(addr)
|
||||||
|
return addr
|
||||||
|
|
||||||
|
def export_private_key(self, address, password):
|
||||||
|
txin_type, pubkey, redeem_script = self.addresses[address]
|
||||||
|
sec = pw_decode(self.keystore.keypairs[pubkey], password)
|
||||||
|
return sec, redeem_script
|
||||||
|
|
||||||
def add_input_sig_info(self, txin, address):
|
def add_input_sig_info(self, txin, address):
|
||||||
addrtype, hash160 = b58_address_to_hash160(address)
|
if self.is_watching_only():
|
||||||
x_pubkey = 'fd' + bh2u(bytes([addrtype]) + hash160)
|
addrtype, hash160 = b58_address_to_hash160(address)
|
||||||
txin['x_pubkeys'] = [x_pubkey]
|
x_pubkey = 'fd' + bh2u(bytes([addrtype]) + hash160)
|
||||||
txin['signatures'] = [None]
|
txin['x_pubkeys'] = [x_pubkey]
|
||||||
|
txin['signatures'] = [None]
|
||||||
|
return
|
||||||
|
|
||||||
|
txin_type = self.addresses[address]['txin_type']
|
||||||
|
txin['type'] = txin_type
|
||||||
|
if txin_type in ['p2pkh', 'p2wkh', 'p2wkh-p2sh']:
|
||||||
|
pubkey = self.addresses[address]['pubkey']
|
||||||
|
txin['num_sig'] = 1
|
||||||
|
txin['x_pubkeys'] = [pubkey]
|
||||||
|
txin['signatures'] = [None]
|
||||||
|
else:
|
||||||
|
redeem_script = self.addresses[address]['redeem_script']
|
||||||
|
num_sig = 2
|
||||||
|
num_keys = 3
|
||||||
|
txin['num_sig'] = num_sig
|
||||||
|
txin['redeem_script'] = redeem_script
|
||||||
|
txin['signatures'] = [None] * num_keys
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Deterministic_Wallet(Abstract_Wallet):
|
class Deterministic_Wallet(Abstract_Wallet):
|
||||||
|
@ -1544,7 +1608,18 @@ class Simple_Wallet(Abstract_Wallet):
|
||||||
|
|
||||||
def load_keystore(self):
|
def load_keystore(self):
|
||||||
self.keystore = load_keystore(self.storage, 'keystore')
|
self.keystore = load_keystore(self.storage, 'keystore')
|
||||||
self.txin_type = self.keystore.txin_type()
|
try:
|
||||||
|
xtype = deserialize_xpub(self.keystore.xpub)[0]
|
||||||
|
except:
|
||||||
|
xtype = 'standard'
|
||||||
|
if xtype == 'standard':
|
||||||
|
self.txin_type = 'p2pkh'
|
||||||
|
elif xtype == 'segwit':
|
||||||
|
self.txin_type = 'p2wpkh'
|
||||||
|
elif xtype == 'segwit_p2sh':
|
||||||
|
self.txin_type = 'p2wpkh-p2sh'
|
||||||
|
else:
|
||||||
|
raise BaseException('unknown txin_type', xtype)
|
||||||
|
|
||||||
def get_pubkey(self, c, i):
|
def get_pubkey(self, c, i):
|
||||||
return self.derive_pubkeys(c, i)
|
return self.derive_pubkeys(c, i)
|
||||||
|
@ -1623,15 +1698,6 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
|
||||||
def can_import_privkey(self):
|
def can_import_privkey(self):
|
||||||
return self.keystore.can_import()
|
return self.keystore.can_import()
|
||||||
|
|
||||||
def import_key(self, pk, pw):
|
|
||||||
pubkey = self.keystore.import_key(pk, pw)
|
|
||||||
self.save_keystore()
|
|
||||||
addr = self.pubkeys_to_address(pubkey)
|
|
||||||
self.receiving_addresses.append(addr)
|
|
||||||
self.save_addresses()
|
|
||||||
self.storage.write()
|
|
||||||
self.add_address(addr)
|
|
||||||
return addr
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1639,16 +1705,7 @@ class Standard_Wallet(Simple_Deterministic_Wallet):
|
||||||
wallet_type = 'standard'
|
wallet_type = 'standard'
|
||||||
|
|
||||||
def pubkeys_to_address(self, pubkey):
|
def pubkeys_to_address(self, pubkey):
|
||||||
if self.txin_type == 'p2pkh':
|
return bitcoin.pubkey_to_address(self.txin_type, pubkey)
|
||||||
return bitcoin.public_key_to_p2pkh(bfh(pubkey))
|
|
||||||
elif self.txin_type == 'p2wpkh':
|
|
||||||
return bitcoin.hash_to_segwit_addr(hash_160(bfh(pubkey)))
|
|
||||||
elif self.txin_type == 'p2wpkh-p2sh':
|
|
||||||
scriptSig = transaction.p2wpkh_nested_script(pubkey)
|
|
||||||
return bitcoin.hash160_to_p2sh(hash_160(bfh(scriptSig)))
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(self.txin_type)
|
|
||||||
|
|
||||||
|
|
||||||
class Multisig_Wallet(Deterministic_Wallet):
|
class Multisig_Wallet(Deterministic_Wallet):
|
||||||
# generic m of n
|
# generic m of n
|
||||||
|
@ -1663,18 +1720,8 @@ class Multisig_Wallet(Deterministic_Wallet):
|
||||||
return self.derive_pubkeys(c, i)
|
return self.derive_pubkeys(c, i)
|
||||||
|
|
||||||
def pubkeys_to_address(self, pubkeys):
|
def pubkeys_to_address(self, pubkeys):
|
||||||
if self.txin_type == 'p2sh':
|
redeem_script = self.pubkeys_to_redeem_script(pubkeys)
|
||||||
redeem_script = self.pubkeys_to_redeem_script(pubkeys)
|
return bitcoin.redeem_script_to_address(self.txin_type, redeem_script)
|
||||||
return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
|
|
||||||
elif self.txin_type == 'p2wsh':
|
|
||||||
witness_script = self.pubkeys_to_redeem_script(pubkeys)
|
|
||||||
return bitcoin.script_to_p2wsh(witness_script)
|
|
||||||
elif self.txin_type == 'p2wsh-p2sh':
|
|
||||||
witness_script = self.pubkeys_to_redeem_script(pubkeys)
|
|
||||||
scriptSig = transaction.p2wsh_nested_script(witness_script)
|
|
||||||
return bitcoin.hash160_to_p2sh(hash_160(bfh(scriptSig)))
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(self.txin_type)
|
|
||||||
|
|
||||||
def pubkeys_to_redeem_script(self, pubkeys):
|
def pubkeys_to_redeem_script(self, pubkeys):
|
||||||
return transaction.multisig_script(sorted(pubkeys), self.m)
|
return transaction.multisig_script(sorted(pubkeys), self.m)
|
||||||
|
@ -1688,7 +1735,15 @@ class Multisig_Wallet(Deterministic_Wallet):
|
||||||
name = 'x%d/'%(i+1)
|
name = 'x%d/'%(i+1)
|
||||||
self.keystores[name] = load_keystore(self.storage, name)
|
self.keystores[name] = load_keystore(self.storage, name)
|
||||||
self.keystore = self.keystores['x1/']
|
self.keystore = self.keystores['x1/']
|
||||||
self.txin_type = self.keystore.txin_type()
|
xtype = deserialize_xpub(self.keystore.xpub)[0]
|
||||||
|
if xtype == 'standard':
|
||||||
|
self.txin_type = 'p2sh'
|
||||||
|
elif xtype == 'segwit':
|
||||||
|
self.txin_type = 'p2wsh'
|
||||||
|
elif xtype == 'segwit_p2sh':
|
||||||
|
self.txin_type = 'p2wsh-p2sh'
|
||||||
|
else:
|
||||||
|
raise BaseException('unknown txin_type', xtype)
|
||||||
|
|
||||||
def save_keystore(self):
|
def save_keystore(self):
|
||||||
for name, k in self.keystores.items():
|
for name, k in self.keystores.items():
|
||||||
|
|
Loading…
Reference in New Issue