From 51c235a8be5bc0cce04ebea2faac4561150ec16d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 19 Feb 2018 18:58:27 +0100 Subject: [PATCH] privkeys WIF: store in extended WIF internally; export as "txin_type:old_wif" --- gui/qt/main_window.py | 4 +--- gui/qt/util.py | 2 +- lib/bitcoin.py | 44 +++++++++++++++++++++++++++++-------------- lib/keystore.py | 5 ++++- lib/wallet.py | 36 ++++++++++++++++++----------------- 5 files changed, 55 insertions(+), 36 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index ad0dd77b8..231d18915 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -2094,8 +2094,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): rds_e = ShowQRTextEdit(text=redeem_script) rds_e.addCopyButton(self.app) vbox.addWidget(rds_e) - if xtype in ['p2wpkh', 'p2wsh', 'p2wpkh-p2sh', 'p2wsh-p2sh']: - vbox.addWidget(WWLabel(_("Warning: the format of private keys associated to segwit addresses may not be compatible with other wallets"))) vbox.addLayout(Buttons(CloseButton(d))) d.setLayout(vbox) d.exec_() @@ -2334,7 +2332,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): _('It can not be "backed up" by simply exporting these private keys.')) d = WindowModalDialog(self, _('Private keys')) - d.setMinimumSize(850, 300) + d.setMinimumSize(980, 300) vbox = QVBoxLayout(d) msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), diff --git a/gui/qt/util.py b/gui/qt/util.py index c0bdf62e8..7f0cb238f 100644 --- a/gui/qt/util.py +++ b/gui/qt/util.py @@ -254,7 +254,7 @@ def line_dialog(parent, title, label, ok_label, default=None): def text_dialog(parent, title, label, ok_label, default=None, allow_multi=False): from .qrtextedit import ScanQRTextEdit dialog = WindowModalDialog(parent, title) - dialog.setMinimumWidth(500) + dialog.setMinimumWidth(600) l = QVBoxLayout() dialog.setLayout(l) l.addWidget(QLabel(label)) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 843397559..65d50bbf0 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -508,9 +508,8 @@ def DecodeBase58Check(psz): return key - -# extended key export format for segwit - +# backwards compat +# extended WIF for segwit (used in 3.0.x; but still used internally) SCRIPT_TYPES = { 'p2pkh':0, 'p2wpkh':1, @@ -521,26 +520,43 @@ SCRIPT_TYPES = { } -def serialize_privkey(secret, compressed, txin_type): - prefix = bytes([(SCRIPT_TYPES[txin_type]+NetworkConstants.WIF_PREFIX)&255]) +def serialize_privkey(secret, compressed, txin_type, internal_use=False): + if internal_use: + prefix = bytes([(SCRIPT_TYPES[txin_type] + NetworkConstants.WIF_PREFIX) & 255]) + else: + prefix = bytes([NetworkConstants.WIF_PREFIX]) suffix = b'\01' if compressed else b'' vchIn = prefix + secret + suffix - return EncodeBase58Check(vchIn) + base58_wif = EncodeBase58Check(vchIn) + if internal_use: + return base58_wif + else: + return '{}:{}'.format(txin_type, base58_wif) def deserialize_privkey(key): - # whether the pubkey is compressed should be visible from the keystore - vch = DecodeBase58Check(key) if is_minikey(key): return 'p2pkh', minikey_to_private_key(key), True - elif vch: - txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - NetworkConstants.WIF_PREFIX] - assert len(vch) in [33, 34] - compressed = len(vch) == 34 - return txin_type, vch[1:33], compressed - else: + + txin_type = None + if ':' in key: + txin_type, key = key.split(sep=':', maxsplit=1) + assert txin_type in SCRIPT_TYPES + vch = DecodeBase58Check(key) + if not vch: raise BaseException("cannot deserialize", key) + if txin_type is None: + # keys exported in version 3.0.x encoded script type in first byte + txin_type = inv_dict(SCRIPT_TYPES)[vch[0] - NetworkConstants.WIF_PREFIX] + else: + assert vch[0] == NetworkConstants.WIF_PREFIX + + assert len(vch) in [33, 34] + compressed = len(vch) == 34 + return txin_type, vch[1:33], compressed + + def regenerate_key(pk): assert len(pk) == 32 return EC_KEY(pk) diff --git a/lib/keystore.py b/lib/keystore.py index e3579958a..011602ec4 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -139,7 +139,10 @@ class Imported_KeyStore(Software_KeyStore): def import_privkey(self, sec, password): txin_type, privkey, compressed = deserialize_privkey(sec) pubkey = public_key_from_private_key(privkey, compressed) - self.keypairs[pubkey] = pw_encode(sec, password) + # re-serialize the key so the internal storage format is consistent + serialized_privkey = serialize_privkey( + privkey, compressed, txin_type, internal_use=True) + self.keypairs[pubkey] = pw_encode(serialized_privkey, password) return txin_type, pubkey def delete_imported_key(self, key): diff --git a/lib/wallet.py b/lib/wallet.py index 90e47c436..ec7d720b6 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -388,23 +388,21 @@ class Abstract_Wallet(PrintError): def get_address_index(self, address): raise NotImplementedError() + def get_redeem_script(self, address): + return None + def export_private_key(self, address, password): - """ extended WIF format """ if self.is_watching_only(): return [] index = self.get_address_index(address) pk, compressed = self.keystore.get_private_key(index, password) - 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, compressed, self.txin_type), redeem_script - + txin_type = self.get_txin_type(address) + redeem_script = self.get_redeem_script(address) + serialized_privkey = bitcoin.serialize_privkey(pk, compressed, txin_type) + return serialized_privkey, redeem_script def get_public_keys(self, address): - sequence = self.get_address_index(address) - return self.get_pubkeys(*sequence) + return [self.get_public_key(address)] def add_unverified_tx(self, tx_hash, tx_height): if tx_height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT) \ @@ -1896,12 +1894,10 @@ class Imported_Wallet(Simple_Wallet): self.add_address(addr) return addr - def export_private_key(self, address, password): + def get_redeem_script(self, address): d = self.addresses[address] - pubkey = d['pubkey'] redeem_script = d['redeem_script'] - sec = pw_decode(self.keystore.keypairs[pubkey], password) - return sec, redeem_script + return redeem_script def get_txin_type(self, address): return self.addresses[address].get('type', 'address') @@ -2079,9 +2075,6 @@ class Simple_Deterministic_Wallet(Simple_Wallet, Deterministic_Wallet): def get_pubkey(self, c, i): return self.derive_pubkeys(c, i) - def get_public_keys(self, address): - return [self.get_public_key(address)] - def add_input_sig_info(self, txin, address): derivation = self.get_address_index(address) x_pubkey = self.keystore.get_xpubkey(*derivation) @@ -2119,6 +2112,10 @@ class Multisig_Wallet(Deterministic_Wallet): def get_pubkeys(self, c, i): return self.derive_pubkeys(c, i) + def get_public_keys(self, address): + sequence = self.get_address_index(address) + return self.get_pubkeys(*sequence) + def pubkeys_to_address(self, pubkeys): redeem_script = self.pubkeys_to_redeem_script(pubkeys) return bitcoin.redeem_script_to_address(self.txin_type, redeem_script) @@ -2126,6 +2123,11 @@ class Multisig_Wallet(Deterministic_Wallet): def pubkeys_to_redeem_script(self, pubkeys): return transaction.multisig_script(sorted(pubkeys), self.m) + def get_redeem_script(self, address): + pubkeys = self.get_public_keys(address) + redeem_script = self.pubkeys_to_redeem_script(pubkeys) + return redeem_script + def derive_pubkeys(self, c, i): return [k.derive_pubkey(c, i) for k in self.get_keystores()]