tnew wallet structure: - store addresses instead of pubkeys - derive pubkeys only for serialization - fix #2024 - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit ff390688678bf31f36433feb04fd328173f60587 DIR parent b1f27d7e8439438978d713cc104a8be57a429c3e HTML Author: ThomasV <thomasv@electrum.org> Date: Wed, 22 Feb 2017 08:47:17 +0100 new wallet structure: - store addresses instead of pubkeys - derive pubkeys only for serialization - fix #2024 Diffstat: M gui/qt/main_window.py | 3 ++- M lib/keystore.py | 3 +++ M lib/transaction.py | 93 +++++++++++++++++++------------ M lib/wallet.py | 113 +++++++++++-------------------- 4 files changed, 103 insertions(+), 109 deletions(-) --- DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py t@@ -903,7 +903,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def clear_receive_tab(self): addr = self.wallet.get_receiving_address() - self.receive_address_e.setText(addr) + if addr: + self.receive_address_e.setText(addr) self.receive_message_e.setText('') self.receive_amount_e.setAmount(None) self.expires_label.hide() DIR diff --git a/lib/keystore.py b/lib/keystore.py t@@ -586,6 +586,9 @@ def xpubkey_to_address(x_pubkey): address = public_key_to_p2pkh(pubkey.decode('hex')) return pubkey, address +def xpubkey_to_pubkey(x_pubkey): + pubkey, address = xpubkey_to_address(x_pubkey) + return pubkey hw_keystores = {} DIR diff --git a/lib/transaction.py b/lib/transaction.py t@@ -42,7 +42,7 @@ import struct import struct import StringIO import random -from keystore import xpubkey_to_address +from keystore import xpubkey_to_address, xpubkey_to_pubkey NO_SIGNATURE = 'ff' t@@ -364,7 +364,7 @@ def parse_scriptSig(d, bytes): print_error("cannot find address in input script", bytes.encode('hex')) return x_pubkeys = map(lambda x: x[1].encode('hex'), dec2[1:-2]) - pubkeys = [xpubkey_to_address(x)[0] for x in x_pubkeys] + pubkeys = [xpubkey_to_pubkey(x) for x in x_pubkeys] redeemScript = multisig_script(pubkeys, m) # write result in d d['type'] = 'p2sh' t@@ -531,10 +531,23 @@ class Transaction: self.deserialize() return self._outputs + @classmethod + def get_sorted_pubkeys(self, txin): + # sort pubkeys and x_pubkeys, using the order of pubkeys + x_pubkeys = txin['x_pubkeys'] + pubkeys = txin.get('pubkeys') + if pubkeys is None: + pubkeys = [xpubkey_to_pubkey(x) for x in x_pubkeys] + pubkeys, x_pubkeys = zip(*sorted(zip(pubkeys, x_pubkeys))) + txin['pubkeys'] = pubkeys = list(pubkeys) + txin['x_pubkeys'] = x_pubkeys = list(x_pubkeys) + return pubkeys, x_pubkeys + def update_signatures(self, raw): """Add new signatures to a transaction""" d = deserialize(raw) for i, txin in enumerate(self.inputs()): + pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) sigs1 = txin.get('signatures') sigs2 = d['inputs'][i].get('signatures') for sig in sigs2: t@@ -545,7 +558,6 @@ class Transaction: order = ecdsa.ecdsa.generator_secp256k1.order() r, s = ecdsa.util.sigdecode_der(sig.decode('hex'), order) sig_string = ecdsa.util.sigencode_string(r, s, order) - pubkeys = txin.get('pubkeys') compressed = True for recid in range(4): public_key = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve = SECP256k1) t@@ -563,7 +575,8 @@ class Transaction: def deserialize(self): if self.raw is None: - self.raw = self.serialize() + return + #self.raw = self.serialize() if self._inputs is not None: return d = deserialize(self.raw) t@@ -595,20 +608,22 @@ class Transaction: # if we have enough signatures, we use the actual pubkeys # otherwise, use extended pubkeys (with bip32 derivation) num_sig = txin.get('num_sig', 1) - x_signatures = txin['signatures'] - signatures = filter(None, x_signatures) - is_complete = len(signatures) == num_sig if estimate_size: # we assume that signature will be 0x48 bytes long - pubkeys = txin['pubkeys'] + pk_list = [ "00" * 0x21 ] * num_sig sig_list = [ "00" * 0x48 ] * num_sig - elif is_complete: - pubkeys = txin['pubkeys'] - sig_list = [(sig + '01') for sig in signatures] else: - pubkeys = txin['x_pubkeys'] - sig_list = [(sig + '01') if sig else NO_SIGNATURE for sig in x_signatures] - return pubkeys, sig_list + pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) + x_signatures = txin['signatures'] + signatures = filter(None, x_signatures) + is_complete = len(signatures) == num_sig + if is_complete: + pk_list = pubkeys + sig_list = [(sig + '01') for sig in signatures] + else: + pk_list = x_pubkeys + sig_list = [(sig + '01') if sig else NO_SIGNATURE for sig in x_signatures] + return pk_list, sig_list @classmethod def serialize_witness(self, txin): t@@ -622,25 +637,37 @@ class Transaction: @classmethod def input_script(self, txin, estimate_size=False): - redeem_script = txin.get('redeemScript') - if self.is_segwit_input(txin): - return push_script(redeem_script) - if txin['type'] == 'p2pk': - sig = txin['signatures'][0] - return push_script(sig) + _type = txin['type'] pubkeys, sig_list = self.get_siglist(txin, estimate_size) script = ''.join(push_script(x) for x in sig_list) - if not pubkeys: + if _type == 'p2pk': pass - elif redeem_script: + elif _type == 'p2sh': # put op_0 before script script = '00' + script + redeem_script = multisig_script(pubkeys, txin['num_sig']) script += push_script(redeem_script) - else: + elif _type == 'p2pkh': script += push_script(pubkeys[0]) + elif _type == 'p2wpkh-p2sh': + redeem_script = segwit_script(pubkeys[0]) + return push_script(redeem_script) + else: + raise TypeError('Unknown txin type', _type) return script @classmethod + def get_preimage_script(self, txin): + # only for non-segwit + if txin['type'] == 'p2pkh': + return get_scriptPubKey(txin['address']) + elif txin['type'] == 'p2sh': + pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) + return multisig_script(pubkeys, txin['num_sig']) + else: + raise TypeError('Unknown txin type', _type) + + @classmethod def serialize_outpoint(self, txin): return txin['prevout_hash'].decode('hex')[::-1].encode('hex') + int_to_hex(txin['prevout_n'], 4) t@@ -693,8 +720,7 @@ class Transaction: nSequence = int_to_hex(txin.get('sequence', 0xffffffff), 4) preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType else: - txin_script = lambda txin: txin.get('redeemScript') or get_scriptPubKey(txin['address']) - txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, txin_script(txin) if i==k else '') for k, txin in enumerate(inputs)) + txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs)) txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) preimage = nVersion + txins + txouts + nLocktime + nHashType return preimage t@@ -787,24 +813,19 @@ class Transaction: def sign(self, keypairs): - for i, txin in enumerate(self.inputs()): + for i, txin in enumerate(self._inputs): num = txin['num_sig'] - for x_pubkey in txin['x_pubkeys']: + pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin) + for j, x_pubkey in enumerate(x_pubkeys): signatures = filter(None, txin['signatures']) if len(signatures) == num: # txin is complete break if x_pubkey in keypairs.keys(): print_error("adding signature for", x_pubkey) - # add pubkey to txin - txin = self._inputs[i] - x_pubkeys = txin['x_pubkeys'] - ii = x_pubkeys.index(x_pubkey) sec = keypairs[x_pubkey] pubkey = public_key_from_private_key(sec) - txin['x_pubkeys'][ii] = pubkey - txin['pubkeys'][ii] = pubkey - self._inputs[i] = txin + assert pubkey == pubkeys[j] # add signature pre_hash = Hash(self.serialize_preimage(i).decode('hex')) pkey = regenerate_key(sec) t@@ -813,12 +834,12 @@ class Transaction: public_key = private_key.get_verifying_key() sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der) assert public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der) - txin['signatures'][ii] = sig.encode('hex') + txin['signatures'][j] = sig.encode('hex') + txin['x_pubkeys'][j] = pubkey self._inputs[i] = txin print_error("is_complete", self.is_complete()) self.raw = self.serialize() - def get_outputs(self): """convert pubkeys to addresses""" o = [] DIR diff --git a/lib/wallet.py b/lib/wallet.py t@@ -209,15 +209,13 @@ class Abstract_Wallet(PrintError): def basename(self): return os.path.basename(self.storage.path) - def save_pubkeys(self): - self.storage.put('pubkeys', {'receiving':self.receiving_pubkeys, 'change':self.change_pubkeys}) + def save_addresses(self): + self.storage.put('addresses', {'receiving':self.receiving_addresses, 'change':self.change_addresses}) def load_addresses(self): - d = self.storage.get('pubkeys', {}) - self.receiving_pubkeys = d.get('receiving', []) - self.change_pubkeys = d.get('change', []) - self.receiving_addresses = map(self.pubkeys_to_address, self.receiving_pubkeys) - self.change_addresses = map(self.pubkeys_to_address, self.change_pubkeys) + d = self.storage.get('addresses', {}) + self.receiving_addresses = d.get('receiving', []) + self.change_addresses = d.get('change', []) def synchronize(self): pass t@@ -260,24 +258,15 @@ class Abstract_Wallet(PrintError): def get_address_index(self, address): if self.keystore.can_import(): - i = self.receiving_addresses.index(address) - return self.receiving_pubkeys[i] + 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) if address in self.change_addresses: return True, self.change_addresses.index(address) raise Exception("Address not found", address) - def get_pubkey_index(self, pubkey): - if self.keystore.can_import(): - assert pubkey in self.receiving_pubkeys - return pubkey - elif pubkey in self.receiving_pubkeys: - return False, self.receiving_pubkeys.index(pubkey) - if pubkey in self.change_pubkeys: - return True, self.change_pubkeys.index(pubkey) - raise Exception("Pubkey not found", pubkey) - def get_private_key(self, address, password): if self.is_watching_only(): return [] t@@ -287,8 +276,7 @@ class Abstract_Wallet(PrintError): def get_public_key(self, address): if self.keystore.can_import(): - i = self.receiving_addresses.index(address) - pubkey = self.receiving_pubkeys[i] + pubkey = self.get_address_index(address) else: sequence = self.get_address_index(address) pubkey = self.get_pubkey(*sequence) t@@ -1063,6 +1051,7 @@ class Abstract_Wallet(PrintError): return Transaction.from_io(inputs, outputs) def add_input_info(self, txin): + txin['type'] = self.txin_type # Add address for utxo that are in wallet if txin.get('scriptSig') == '': coins = self.get_spendable_coins() t@@ -1137,6 +1126,8 @@ class Abstract_Wallet(PrintError): def get_receiving_address(self): # always return an address domain = self.get_receiving_addresses() + if not domain: + return choice = domain[0] for addr in domain: if not self.history.get(addr): t@@ -1322,6 +1313,7 @@ class Imported_Wallet(Abstract_Wallet): # wallet made of imported addresses wallet_type = 'imported' + txin_type = 'unknown' def __init__(self, storage): Abstract_Wallet.__init__(self, storage) t@@ -1397,11 +1389,9 @@ class Imported_Wallet(Abstract_Wallet): def add_input_sig_info(self, txin, address): addrtype, hash160 = bc_address_to_hash_160(address) - xpubkey = 'fd' + (chr(addrtype) + hash160).encode('hex') - txin['x_pubkeys'] = [ xpubkey ] - txin['pubkeys'] = [ xpubkey ] + x_pubkey = 'fd' + (chr(addrtype) + hash160).encode('hex') + txin['x_pubkeys'] = [x_pubkey] txin['signatures'] = [None] - txin['type'] = 'unknown' t@@ -1440,11 +1430,10 @@ class Deterministic_Wallet(Abstract_Wallet): addresses = self.get_receiving_addresses() k = self.num_unused_trailing_addresses(addresses) n = len(addresses) - k + value - self.receiving_pubkeys = self.receiving_pubkeys[0:n] self.receiving_addresses = self.receiving_addresses[0:n] self.gap_limit = value self.storage.put('gap_limit', self.gap_limit) - self.save_pubkeys() + self.save_addresses() return True else: return False t@@ -1471,14 +1460,12 @@ class Deterministic_Wallet(Abstract_Wallet): return nmax + 1 def create_new_address(self, for_change): - pubkey_list = self.change_pubkeys if for_change else self.receiving_pubkeys - n = len(pubkey_list) - x = self.new_pubkeys(for_change, n) - pubkey_list.append(x) - self.save_pubkeys() - address = self.pubkeys_to_address(x) addr_list = self.change_addresses if for_change else self.receiving_addresses + n = len(addr_list) + x = self.derive_pubkeys(for_change, n) + address = self.pubkeys_to_address(x) addr_list.append(address) + self.save_addresses() self.add_address(address) return address t@@ -1500,10 +1487,10 @@ class Deterministic_Wallet(Abstract_Wallet): self.synchronize_sequence(False) self.synchronize_sequence(True) else: - if len(self.receiving_pubkeys) != len(self.keystore.keypairs): - self.receiving_pubkeys = self.keystore.keypairs.keys() - self.save_pubkeys() - self.receiving_addresses = map(self.pubkeys_to_address, self.receiving_pubkeys) + if len(self.receiving_addresses) != len(self.keystore.keypairs): + pubkeys = self.keystore.keypairs.keys() + self.receiving_addresses = map(self.pubkeys_to_address, pubkeys) + self.save_addresses() for addr in self.receiving_addresses: self.add_address(addr) t@@ -1536,35 +1523,22 @@ class Simple_Wallet(Abstract_Wallet): def load_keystore(self): self.keystore = load_keystore(self.storage, 'keystore') self.is_segwit = self.keystore.is_segwit() + self.txin_type = 'p2wpkh-p2sh' if self.is_segwit else 'p2pkh' def get_pubkey(self, c, i): - pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys - return pubkey_list[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): if not self.keystore.can_import(): - txin['derivation'] = derivation = self.get_address_index(address) + derivation = self.get_address_index(address) x_pubkey = self.keystore.get_xpubkey(*derivation) - pubkey = self.get_pubkey(*derivation) else: - pubkey = self.get_public_key(address) - assert pubkey is not None - x_pubkey = pubkey + x_pubkey = self.get_public_key(address) txin['x_pubkeys'] = [x_pubkey] - txin['pubkeys'] = [pubkey] txin['signatures'] = [None] - - addrtype, hash160 = bc_address_to_hash_160(address) - if addrtype == bitcoin.ADDRTYPE_P2SH: - txin['redeemScript'] = self.pubkeys_to_redeem_script(pubkey) - txin['type'] = 'p2wpkh-p2sh' - else: - txin['redeemPubkey'] = pubkey - txin['type'] = 'p2pkh' - txin['num_sig'] = 1 def sign_message(self, address, message, password): t@@ -1572,7 +1546,8 @@ class Simple_Wallet(Abstract_Wallet): return self.keystore.sign_message(index, message, password) def decrypt_message(self, pubkey, message, password): - index = self.get_pubkey_index(pubkey) + addr = self.pubkeys_to_address(pubkey) + index = self.get_address_index(addr) return self.keystore.decrypt_message(index, message, password) t@@ -1584,7 +1559,7 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet): def get_master_public_key(self): return self.keystore.get_master_public_key() - def new_pubkeys(self, c, i): + def derive_pubkeys(self, c, i): return self.keystore.derive_pubkey(c, i) def get_keystore(self): t@@ -1618,8 +1593,8 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet): pubkey = self.get_public_key(address) self.keystore.delete_imported_key(pubkey) self.save_keystore() - self.receiving_pubkeys.remove(pubkey) self.receiving_addresses.remove(address) + self.save_addresses() self.storage.write() def can_import_privkey(self): t@@ -1628,10 +1603,9 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet): def import_key(self, pk, pw): pubkey = self.keystore.import_key(pk, pw) self.save_keystore() - self.receiving_pubkeys.append(pubkey) - self.save_pubkeys() addr = self.pubkeys_to_address(pubkey) self.receiving_addresses.append(addr) + self.save_addresses() self.storage.write() self.add_address(addr) return addr t@@ -1670,6 +1644,7 @@ class Standard_Wallet(Simple_Deterministic_Wallet): class Multisig_Wallet(Deterministic_Wallet, P2SH): # generic m of n gap_limit = 20 + txin_type = 'p2sh' def __init__(self, storage): self.wallet_type = storage.get('wallet_type') t@@ -1677,8 +1652,7 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH): Deterministic_Wallet.__init__(self, storage) def get_pubkeys(self, c, i): - pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys - return pubkey_list[i] + return self.derive_pubkeys(c, i) def redeem_script(self, c, i): pubkeys = self.get_pubkeys(c, i) t@@ -1687,7 +1661,7 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH): def pubkeys_to_redeem_script(self, pubkeys): return transaction.multisig_script(sorted(pubkeys), self.m) - def new_pubkeys(self, c, i): + def derive_pubkeys(self, c, i): return [k.derive_pubkey(c, i) for k in self.get_keystores()] def load_keystore(self): t@@ -1736,16 +1710,11 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH): return ''.join(sorted(self.get_master_public_keys())) def add_input_sig_info(self, txin, address): - txin['derivation'] = derivation = self.get_address_index(address) - pubkeys = self.get_pubkeys(*derivation) - x_pubkeys = [k.get_xpubkey(*derivation) for k in self.get_keystores()] - # sort pubkeys and x_pubkeys, using the order of pubkeys - pubkeys, x_pubkeys = zip(*sorted(zip(pubkeys, x_pubkeys))) - txin['type'] = 'p2sh' - txin['pubkeys'] = list(pubkeys) - txin['x_pubkeys'] = list(x_pubkeys) - txin['signatures'] = [None] * len(pubkeys) - txin['redeemScript'] = self.redeem_script(*derivation) + derivation = self.get_address_index(address) + # extended pubkeys + txin['x_pubkeys'] = [k.get_xpubkey(*derivation) for k in self.get_keystores()] + # we need n place holders + txin['signatures'] = [None] * self.n txin['num_sig'] = self.m