URI: 
       tmove private key methods from wallet to accounts - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c9fc6275ab47936f75f74d1a48aa4affabf6a2b7
   DIR parent 9b8ad42a662a62a77cf1504ec8a0814e68cdf21e
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Sun,  4 May 2014 19:43:15 +0200
       
       move private key methods from wallet to accounts
       
       Diffstat:
         M gui/qt/main_window.py               |       2 +-
         M gui/qt/password_dialog.py           |       2 ++
         M lib/account.py                      |      72 ++++++++++++++++++++++++++++---
         M lib/bitcoin.py                      |      28 ++++++++++++++++++++++++++++
         M lib/wallet.py                       |     209 +++++++++++--------------------
       
       5 files changed, 170 insertions(+), 143 deletions(-)
       ---
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1077,7 +1077,7 @@ class ElectrumWindow(QMainWindow):
                        menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
                        menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
                        #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
       -            if addr in self.wallet.imported_keys:
       +            if self.wallet.is_imported(addr):
                        menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
        
                if any(addr not in self.wallet.frozen_addresses for addr in addrs):
   DIR diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
       t@@ -124,6 +124,8 @@ class PasswordDialog(QDialog):
                try:
                    self.wallet.update_password(password, new_password)
                except:
       +            import traceback, sys
       +            traceback.print_exc(file=sys.stdout)
                    QMessageBox.warning(self.parent, _('Error'), _('Failed to update password'), _('OK'))
                    return
        
   DIR diff --git a/lib/account.py b/lib/account.py
       t@@ -16,11 +16,12 @@
        # You should have received a copy of the GNU General Public License
        # along with this program. If not, see <http://www.gnu.org/licenses/>.
        
       -
        from bitcoin import *
        from i18n import _
        from transaction import Transaction
        
       +
       +
        class Account(object):
            def __init__(self, v):
                self.addresses = v.get('0', [])
       t@@ -52,6 +53,12 @@ class Account(object):
            def get_name(self, k):
                return _('Main account')
        
       +    def get_keyID(self, *sequence):
       +        pass
       +
       +    def redeem_script(self, *sequence):
       +        pass
       +
        
        class PendingAccount(Account):
            def __init__(self, v):
       t@@ -70,18 +77,52 @@ class PendingAccount(Account):
        
        class ImportedAccount(Account):
            def __init__(self, d):
       -        self.addresses = d.keys()
       +        self.keypairs = d['imported']
        
            def get_addresses(self, for_change):
       -        return [] if for_change else sorted(self.addresses[:])
       +        return [] if for_change else sorted(self.keypairs.keys())
       +
       +    def get_pubkey(self, *sequence):
       +        for_change, i = sequence
       +        assert for_change == 0
       +        addr = self.get_addresses(0)[i]
       +        return self.keypairs[addr][i][0]
       +
       +    def get_private_key(self, sequence, wallet, password):
       +        from wallet import pw_decode
       +        for_change, i = sequence
       +        assert for_change == 0
       +        address = self.get_addresses(0)[i]
       +        pk = pw_decode(self.keypairs[address][1], password)
       +        # this checks the password
       +        assert address == address_from_private_key(pk)
       +        return [pk]
        
            def has_change(self):
                return False
        
       +    def add(self, address, pubkey, privkey, password):
       +        from wallet import pw_encode
       +        self.keypairs[address] = (pubkey, pw_encode(privkey, password ))
       +
       +    def remove(self, address):
       +        self.keypairs.pop(address)
       +
       +    def dump(self):
       +        return {'imported':self.keypairs}
       +
            def get_name(self, k):
                return _('Imported keys')
        
        
       +    def update_password(self, old_password, new_password):
       +        for k, v in self.keypairs.items():
       +            pubkey, a = v
       +            b = pw_decode(a, old_password)
       +            c = pw_encode(b, new_password)
       +            self.keypairs[k] = (pubkey, c)
       +
       +
        class OldAccount(Account):
            """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
        
       t@@ -132,10 +173,15 @@ class OldAccount(Account):
                compressed = False
                return SecretToASecret( pk, compressed )
                
       -    def get_private_key(self, seed, sequence):
       +
       +    def get_private_key(self, sequence, wallet, password):
       +        seed = wallet.get_seed(password)
       +        self.check_seed(seed)
                for_change, n = sequence
                secexp = self.stretch_key(seed)
       -        return self.get_private_key_from_stretched_exponent(for_change, n, secexp)
       +        pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
       +        return [pk]
       +
        
            def check_seed(self, seed):
                curve = SECP256k1
       t@@ -196,6 +242,22 @@ class BIP32_Account(Account):
            def get_pubkey(self, for_change, n):
                return self.get_pubkeys((for_change, n))[0]
        
       +
       +    def get_private_key(self, sequence, wallet, password):
       +        out = []
       +        xpubs = self.get_master_pubkeys()
       +        roots = [k for k, v in wallet.master_public_keys.iteritems() if v in xpubs]
       +        for root in roots:
       +            xpriv = wallet.get_master_private_key(root, password)
       +            if not xpriv:
       +                continue
       +            _, _, _, c, k = deserialize_xkey(xpriv)
       +            pk = bip32_private_key( sequence, k, c )
       +            out.append(pk)
       +                    
       +        return out
       +
       +
            def redeem_script(self, sequence):
                return None
        
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -20,8 +20,35 @@
        
        import hashlib, base64, ecdsa, re
        import hmac
       +import aes
        from util import print_error
        
       +# AES encryption
       +EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
       +DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
       +
       +def pw_encode(s, password):
       +    if password:
       +        secret = Hash(password)
       +        return EncodeAES(secret, s)
       +    else:
       +        return s
       +
       +def pw_decode(s, password):
       +    if password is not None:
       +        secret = Hash(password)
       +        try:
       +            d = DecodeAES(secret, s)
       +        except Exception:
       +            raise Exception('Invalid password')
       +        return d
       +    else:
       +        return s
       +
       +
       +
       +
       +
        def rev_hex(s):
            return s.decode('hex')[::-1].encode('hex')
        
       t@@ -274,6 +301,7 @@ def public_key_from_private_key(sec):
            pkey = regenerate_key(sec)
            assert pkey
            compressed = is_compressed(sec)
       +    print "is compressed", compressed
            public_key = GetPubKey(pkey.pubkey, compressed)
            return public_key.encode('hex')
        
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -45,30 +45,6 @@ DUST_THRESHOLD = 5430
        # internal ID for imported account
        IMPORTED_ACCOUNT = '/x'
        
       -# AES encryption
       -EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
       -DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
       -
       -def pw_encode(s, password):
       -    if password:
       -        secret = Hash(password)
       -        return EncodeAES(secret, s)
       -    else:
       -        return s
       -
       -def pw_decode(s, password):
       -    if password is not None:
       -        secret = Hash(password)
       -        try:
       -            d = DecodeAES(secret, s)
       -        except Exception:
       -            raise Exception('Invalid password')
       -        return d
       -    else:
       -        return s
       -
       -
       -
        
        
        from version import *
       t@@ -249,7 +225,26 @@ class Abstract_Wallet:
                self.accounts = {}
                self.imported_keys = self.storage.get('imported_keys',{})
                if self.imported_keys:
       -            self.accounts['/x'] = ImportedAccount(self.imported_keys)
       +            print_error("cannot load imported keys")
       +
       +        d = self.storage.get('accounts', {})
       +        for k, v in d.items():
       +            if k == 0:
       +                v['mpk'] = self.storage.get('master_public_key')
       +                self.accounts[k] = OldAccount(v)
       +            elif v.get('imported'):
       +                self.accounts[k] = ImportedAccount(v)
       +            elif v.get('xpub3'):
       +                self.accounts[k] = BIP32_Account_2of3(v)
       +            elif v.get('xpub2'):
       +                self.accounts[k] = BIP32_Account_2of2(v)
       +            elif v.get('xpub'):
       +                self.accounts[k] = BIP32_Account(v)
       +            elif v.get('pending'):
       +                self.accounts[k] = PendingAccount(v)
       +            else:
       +                print_error("cannot load account", v)
       +
        
            def synchronize(self):
                pass
       t@@ -257,14 +252,9 @@ class Abstract_Wallet:
            def can_create_accounts(self):
                return False
        
       -    def check_password(self, password):
       -        raise
       -
       -
            def set_up_to_date(self,b):
                with self.lock: self.up_to_date = b
        
       -
            def is_up_to_date(self):
                with self.lock: return self.up_to_date
        
       t@@ -274,21 +264,27 @@ class Abstract_Wallet:
                while not self.is_up_to_date(): 
                    time.sleep(0.1)
        
       +    def is_imported(self, addr):
       +        account = self.accounts.get(IMPORTED_ACCOUNT)
       +        if account: 
       +            return addr in account.get_addresses(0)
       +        else:
       +            return False
        
            def import_key(self, sec, password):
       -        self.check_password(password)
                try:
       -            address = address_from_private_key(sec)
       +            pubkey = public_key_from_private_key(sec)
       +            address = public_key_to_bc_address(pubkey.decode('hex'))
                except Exception:
                    raise Exception('Invalid private key')
        
                if self.is_mine(address):
                    raise Exception('Address already in wallet')
                
       -        # store the originally requested keypair into the imported keys table
       -        self.imported_keys[address] = pw_encode(sec, password )
       -        self.storage.put('imported_keys', self.imported_keys, True)
       -        self.accounts[IMPORTED_ACCOUNT] = ImportedAccount(self.imported_keys)
       +        if self.accounts.get(IMPORTED_ACCOUNT) is None:
       +            self.accounts[IMPORTED_ACCOUNT] = ImportedAccount({'imported':{}})
       +        self.accounts[IMPORTED_ACCOUNT].add(address, pubkey, sec, password)
       +        self.save_accounts()
                
                if self.synchronizer:
                    self.synchronizer.subscribe_to_addresses([address])
       t@@ -296,13 +292,11 @@ class Abstract_Wallet:
                
        
            def delete_imported_key(self, addr):
       -        if addr in self.imported_keys:
       -            self.imported_keys.pop(addr)
       -            self.storage.put('imported_keys', self.imported_keys, True)
       -            if self.imported_keys:
       -                self.accounts[IMPORTED_ACCOUNT] = ImportedAccount(self.imported_keys)
       -            else:
       -                self.accounts.pop(IMPORTED_ACCOUNT)
       +        account = self.accounts[IMPORTED_ACCOUNT]
       +        account.remove(addr)
       +        if not account.get_addresses(0):
       +            self.accounts.pop(IMPORTED_ACCOUNT)
       +        self.save_accounts()
        
        
            def set_label(self, name, text = None):
       t@@ -368,35 +362,15 @@ class Abstract_Wallet:
            def getpubkeys(self, addr):
                assert is_valid(addr) and self.is_mine(addr)
                account, sequence = self.get_address_index(addr)
       -        if account != IMPORTED_ACCOUNT:
       -            a = self.accounts[account]
       -            return a.get_pubkeys( sequence )
       -
       +        a = self.accounts[account]
       +        return a.get_pubkeys( sequence )
        
        
            def get_private_key(self, address, password):
                if self.is_watching_only():
                    return []
       -
       -        out = []
       -        if address in self.imported_keys.keys():
       -            self.check_password(password)
       -            out.append( pw_decode( self.imported_keys[address], password ) )
       -        else:
       -            seed = self.get_seed(password)
       -            account_id, sequence = self.get_address_index(address)
       -            account = self.accounts[account_id]
       -            xpubs = account.get_master_pubkeys()
       -            roots = [k for k, v in self.master_public_keys.iteritems() if v in xpubs]
       -            for root in roots:
       -                xpriv = self.get_master_private_key(root, password)
       -                if not xpriv:
       -                    continue
       -                _, _, _, c, k = deserialize_xkey(xpriv)
       -                pk = bip32_private_key( sequence, k, c )
       -                out.append(pk)
       -                    
       -        return out
       +        account_id, sequence = self.get_address_index(address)
       +        return self.accounts[account_id].get_private_key(sequence, self, password)
        
        
            def get_public_keys(self, address):
       t@@ -414,9 +388,6 @@ class Abstract_Wallet:
                        pubkey = public_key_from_private_key(sec)
                        keypairs[ pubkey ] = sec
        
       -                # this is needed because we don't store imported pubkeys
       -                if address in self.imported_keys.keys():
       -                    txin['redeemPubkey'] = pubkey
        
        
            def add_keypairs_from_KeyID(self, tx, keypairs, password):
       t@@ -891,8 +862,6 @@ class Abstract_Wallet:
        
            def add_input_info(self, txin):
                address = txin['address']
       -        if address in self.imported_keys.keys():
       -            return
                account_id, sequence = self.get_address_index(address)
                account = self.accounts[account_id]
                txin['KeyID'] = account.get_keyID(sequence)
       t@@ -941,12 +910,10 @@ class Abstract_Wallet:
                    self.seed = pw_encode( decoded, new_password)
                    self.storage.put('seed', self.seed, True)
        
       -        for k in self.imported_keys.keys():
       -            a = self.imported_keys[k]
       -            b = pw_decode(a, old_password)
       -            c = pw_encode(b, new_password)
       -            self.imported_keys[k] = c
       -        self.storage.put('imported_keys', self.imported_keys, True)
       +        imported_account = self.accounts.get(IMPORTED_ACCOUNT)
       +        if imported_account: 
       +            imported_account.update_password(old_password, new_password)
       +            self.save_accounts()
        
                for k, v in self.master_private_keys.items():
                    b = pw_decode(v, old_password)
       t@@ -1097,15 +1064,27 @@ class Abstract_Wallet:
            def get_accounts(self):
                return self.accounts
        
       +    def save_accounts(self):
       +        d = {}
       +        for k, v in self.accounts.items():
       +            d[k] = v.dump()
       +        self.storage.put('accounts', d, True)
       +
       +    
        
        class Imported_Wallet(Abstract_Wallet):
        
            def __init__(self, storage):
                Abstract_Wallet.__init__(self, storage)
       +        a = self.accounts.get(IMPORTED_ACCOUNT)
       +        if not a:
       +            self.accounts[IMPORTED_ACCOUNT] = ImportedAccount({'imported':{}})
       +
        
            def is_watching_only(self):
       -        n = self.imported_keys.values()
       -        return n == [''] * len(n)
       +        acc = self.accounts[IMPORTED_ACCOUNT]
       +        n = acc.keypairs.values()
       +        return n == [(None, None)] * len(n)
        
            def has_seed(self):
                return False
       t@@ -1114,12 +1093,7 @@ class Imported_Wallet(Abstract_Wallet):
                return False
        
            def check_password(self, password):
       -        if self.imported_keys:
       -            k, v = self.imported_keys.items()[0]
       -            sec = pw_decode(v, password)
       -            address = address_from_private_key(sec)
       -            assert address == k
       -
       +        self.accounts[IMPORTED_ACCOUNT].get_private_key((0,0), self, password)
        
        
        
       t@@ -1137,9 +1111,6 @@ class Deterministic_Wallet(Abstract_Wallet):
            def is_watching_only(self):
                return not self.has_seed()
        
       -    def check_password(self, password):
       -        self.get_seed(password)
       -
            def add_seed(self, seed, password):
                if self.seed: 
                    raise Exception("a seed exists")
       t@@ -1157,12 +1128,10 @@ class Deterministic_Wallet(Abstract_Wallet):
                self.create_master_keys(password)
        
            def get_seed(self, password):
       -        s = pw_decode(self.seed, password)
       -        seed = mnemonic_to_seed(s,'').encode('hex')
       -        return seed
       +        return pw_decode(self.seed, password)
        
            def get_mnemonic(self, password):
       -        return pw_decode(self.seed, password)
       +        return self.get_seed(password)
                
            def change_gap_limit(self, value):
                if value >= self.gap_limit:
       t@@ -1324,32 +1293,6 @@ class Deterministic_Wallet(Abstract_Wallet):
                self.save_accounts()
        
        
       -    def save_accounts(self):
       -        d = {}
       -        for k, v in self.accounts.items():
       -            d[k] = v.dump()
       -        self.storage.put('accounts', d, True)
       -
       -    
       -
       -    def load_accounts(self):
       -        Abstract_Wallet.load_accounts(self)
       -        d = self.storage.get('accounts', {})
       -        for k, v in d.items():
       -            if k == 0:
       -                v['mpk'] = self.storage.get('master_public_key')
       -                self.accounts[k] = OldAccount(v)
       -            elif v.get('xpub3'):
       -                self.accounts[k] = BIP32_Account_2of3(v)
       -            elif v.get('xpub2'):
       -                self.accounts[k] = BIP32_Account_2of2(v)
       -            elif v.get('xpub'):
       -                self.accounts[k] = BIP32_Account(v)
       -            elif v.get('pending'):
       -                self.accounts[k] = PendingAccount(v)
       -            else:
       -                print_error("cannot load account", v)
       -
        
            def account_is_pending(self, k):
                return type(self.accounts.get(k)) == PendingAccount
       t@@ -1393,6 +1336,10 @@ class NewWallet(Deterministic_Wallet):
                xpriv = pw_decode( k, password)
                return xpriv
        
       +    def check_password(self, password):
       +        xpriv = self.get_master_private_key( "m/", password )
       +        xpub = self.master_public_keys["m/"]
       +        assert deserialize_xkey(xpriv)[3] == deserialize_xkey(xpub)[3]
        
            def create_watching_only_wallet(self, xpub):
                self.storage.put('seed_version', self.seed_version, True)
       t@@ -1611,9 +1558,12 @@ class OldWallet(Deterministic_Wallet):
        
            def get_seed(self, password):
                seed = pw_decode(self.seed, password)
       -        self.accounts[0].check_seed(seed)
                return seed
        
       +    def check_password(self, password):
       +        seed = pw_decode(self.seed, password)
       +        self.accounts[0].check_seed(seed)
       +
            def get_mnemonic(self, password):
                import mnemonic
                s = pw_decode(self.seed, password)
       t@@ -1641,21 +1591,6 @@ class OldWallet(Deterministic_Wallet):
        
        
        
       -    def get_private_key(self, address, password):
       -        if self.is_watching_only():
       -            return []
       -
       -        out = []
       -        if address in self.imported_keys.keys():
       -            self.check_password(password)
       -            out.append( pw_decode( self.imported_keys[address], password ) )
       -        else:
       -            seed = self.get_seed(password)
       -            account_id, sequence = self.get_address_index(address)
       -            pk = self.accounts[0].get_private_key(seed, sequence)
       -            out.append(pk)
       -        return out
       -
            def check_pending_accounts(self):
                pass
        
       t@@ -1757,8 +1692,8 @@ class Wallet(object):
            def from_address(self, text, storage):
                w = Imported_Wallet(storage)
                for x in text.split():
       -            w.imported_keys[x] = ''
       -        w.storage.put('imported_keys', w.imported_keys, True)
       +            w.accounts[IMPORTED_ACCOUNT].add(x, None, None, None)
       +        w.save_accounts()
                return w
        
            @classmethod