URI: 
       tseed v6 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 31aaae8ed22d932a06b0ddeddbe0bc9932c0367b
   DIR parent 07bdd6c494004458e3799ef364a71d6fa5f476c8
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Sat, 26 Oct 2013 11:54:11 +0200
       
       seed v6
       
       Diffstat:
         M electrum                            |       2 +-
         M gui/android.py                      |       6 +++---
         M gui/gtk.py                          |       6 +++---
         M gui/qt/installwizard.py             |      20 ++++++++------------
         M gui/qt/main_window.py               |       4 ++--
         M gui/qt/password_dialog.py           |       4 ++--
         M gui/qt/seed_dialog.py               |       5 ++---
         M lib/bitcoin.py                      |       3 +++
         M lib/commands.py                     |       6 +++---
         M lib/version.py                      |       3 ++-
         M lib/wallet.py                       |     107 +++++++++++++++++++++++--------
       
       11 files changed, 110 insertions(+), 56 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -262,7 +262,7 @@ if __name__ == '__main__':
                        exit(1)
                    # check password
                    try:
       -                seed = wallet.decode_seed(password)
       +                seed = wallet.get_seed(password)
                    except:
                        print_msg("Error: This password does not decode this wallet.")
                        exit(1)
   DIR diff --git a/gui/android.py b/gui/android.py
       t@@ -717,7 +717,7 @@ def show_seed():
                password = None
            
            try:
       -        seed = wallet.decode_seed(password)
       +        seed = wallet.get_seed(password)
            except:
                modal_dialog('error','incorrect password')
                return
       t@@ -733,7 +733,7 @@ def change_password_dialog():
                password = None
        
            try:
       -        seed = wallet.decode_seed(password)
       +        wallet.get_seed(password)
            except:
                modal_dialog('error','incorrect password')
                return
       t@@ -748,7 +748,7 @@ def change_password_dialog():
                    modal_dialog('error','passwords do not match')
                    return
        
       -    wallet.update_password(seed, password, new_password)
       +    wallet.update_password(password, new_password)
            if new_password:
                modal_dialog('Password updated','your wallet is encrypted')
            else:
   DIR diff --git a/gui/gtk.py b/gui/gtk.py
       t@@ -69,7 +69,7 @@ def show_seed_dialog(wallet, password, parent):
                show_message("No seed")
                return
            try:
       -        seed = wallet.decode_seed(password)
       +        seed = wallet.get_seed(password)
            except:
                show_message("Incorrect password")
                return
       t@@ -435,7 +435,7 @@ def change_password_dialog(wallet, parent, icon):
                return
        
            try:
       -        seed = wallet.decode_seed(password)
       +        wallet.get_seed(password)
            except:
                show_message("Incorrect password")
                return
       t@@ -444,7 +444,7 @@ def change_password_dialog(wallet, parent, icon):
                show_message("passwords do not match")
                return
        
       -    wallet.update_password(seed, password, new_password)
       +    wallet.update_password(password, new_password)
        
            if icon:
                if wallet.use_encryption:
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -89,10 +89,9 @@ class InstallWizard(QDialog):
                vbox = QVBoxLayout(self)
                if is_restore:
                    msg = _("Please enter your wallet seed.") + "\n"
       -            msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string.")+ ' \n'
                else:
                    msg = _("Your seed is important!") \
       -                  + "\n" + _("To make sure that you have properly saved your seed, please retype it here.") + ' '
       +                + "\n" + _("To make sure that you have properly saved your seed, please retype it here.")
                
                logo = QLabel()
                logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
       t@@ -119,15 +118,7 @@ class InstallWizard(QDialog):
                if not self.exec_():
                    return
        
       -        try:
       -            seed = str(seed_e.toPlainText())
       -            seed.decode('hex')
       -        except:
       -            try:
       -                seed = mnemonic.mn_decode( seed.split() )
       -            except:
       -                QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
       -                return
       +        seed = unicode(seed_e.toPlainText())
        
                if not seed:
                    QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
       t@@ -288,7 +279,12 @@ class InstallWizard(QDialog):
                    seed = self.seed_dialog()
                    if not seed:
                        return
       -            wallet.init_seed(str(seed))
       +            try:
       +                wallet.init_seed(seed)
       +            except:
       +                QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
       +                return
       +
                    wallet.save_seed()
        
                elif action == 'watching':
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1544,12 +1544,12 @@ class ElectrumWindow(QMainWindow):
        
                if self.wallet.seed:
                    try:
       -                seed = self.wallet.decode_seed(password)
       +                mnemonic = self.wallet.get_mnemonic(password)
                    except:
                        QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
                        return
                    from seed_dialog import SeedDialog
       -            d = SeedDialog(self, seed, self.wallet.imported_keys)
       +            d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
                    d.exec_()
                else:
                    l = {}
   DIR diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
       t@@ -84,7 +84,7 @@ def run_password_dialog(self, wallet, parent):
            new_password2 = unicode(self.conf_pw.text())
        
            try:
       -        seed = wallet.decode_seed(password)
       +        wallet.get_seed(password)
            except:
                QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
                return
       t@@ -96,7 +96,7 @@ def run_password_dialog(self, wallet, parent):
                return
        
            try:
       -        wallet.update_password(seed, password, new_password)
       +        wallet.update_password(password, new_password)
            except:
                QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
                return
   DIR diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
       t@@ -57,12 +57,11 @@ class PrivateKeysDialog(QDialog):
        
        def make_seed_dialog(seed, imported_keys):
        
       -        words = mnemonic.mn_encode(seed)
       -        brainwallet = ' '.join(words)
       +        words = seed.split()
        
                label1 = QLabel(_("Your wallet generation seed is")+ ":")
        
       -        seed_text = QTextEdit(brainwallet)
       +        seed_text = QTextEdit(seed)
                seed_text.setReadOnly(True)
                seed_text.setMaximumHeight(130)
                
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -19,6 +19,7 @@
        
        
        import hashlib, base64, ecdsa, re
       +import hmac
        from util import print_error
        
        def rev_hex(s):
       t@@ -56,6 +57,8 @@ Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest()
        hash_encode = lambda x: x[::-1].encode('hex')
        hash_decode = lambda x: x.decode('hex')[::-1]
        
       +hmac_sha_512 = lambda x,y: hmac.new(x, y, hashlib.sha512).digest()
       +mnemonic_hash = lambda x: hmac_sha_512("Bitcoin mnemonic", x).encode('hex')
        
        # pywallet openssl private key implementation
        
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -215,9 +215,9 @@ class Commands:
                return self.network.get_servers()
        
            def getseed(self):
       -        import mnemonic
       -        seed = self.wallet.decode_seed(self.password)
       -        return { "hex":seed, "mnemonic": ' '.join(mnemonic.mn_encode(seed)) }
       +        mnemonic = self.wallet.get_mnemonic(self.password)
       +        seed = self.wallet.get_seed(self.password)
       +        return { 'mnemonic':mnemonic, 'seed':seed, 'version':self.wallet.seed_version }
        
            def importprivkey(self, sec):
                try:
   DIR diff --git a/lib/version.py b/lib/version.py
       t@@ -1,4 +1,5 @@
        ELECTRUM_VERSION = "1.9"    # version of the client package
        PROTOCOL_VERSION = '0.6'    # protocol version requested
       -SEED_VERSION     = 5        # bump this every time the seed generation is modified
       +SEED_VERSION     = 6        # bump this every time the seed generation is modified
       +SEED_PREFIX      = '100'    # the hash of a valid mnemonic seed must begin with this (12 bits)
        TRANSLATION_ID   = 4127     # version of the wiki page 
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -64,7 +64,7 @@ def pw_decode(s, password):
        
        
        
       -from version import ELECTRUM_VERSION, SEED_VERSION
       +from version import *
        
        
        class WalletStorage:
       t@@ -176,8 +176,10 @@ class Wallet:
        
                self.next_addresses = storage.get('next_addresses',{})
        
       -        if self.seed_version < 4:
       -            raise ValueError("This wallet seed is deprecated.")
       +        if self.seed_version not in [4, 6]:
       +            msg = "This wallet seed is not supported."
       +            if self.seed_version in [5]: msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%self.seed_version
       +            raise ValueError(msg)
        
                self.load_accounts()
        
       t@@ -247,7 +249,7 @@ class Wallet:
        
            def import_key(self, sec, password):
                # check password
       -        seed = self.decode_seed(password)
       +        seed = self.get_seed(password)
                try:
                    address = address_from_private_key(sec)
                except:
       t@@ -269,12 +271,55 @@ class Wallet:
                    self.storage.put('imported_keys', self.imported_keys, True)
        
        
       +    def make_seed(self):
       +        import mnemonic, ecdsa
       +        entropy = ecdsa.util.randrange( pow(2,160) )
       +        nonce = 0
       +        while True:
       +            ss = "%040x"%(entropy+nonce)
       +            s = hashlib.sha256(ss.decode('hex')).digest().encode('hex')
       +            # we keep only 13 words, that's approximately 139 bits of entropy
       +            words = mnemonic.mn_encode(s)[0:13] 
       +            seed = ' '.join(words)
       +            if mnemonic_hash(seed)[0:3] == SEED_PREFIX: 
       +                break  # this removes 12 bits of entropy 
       +            nonce += 1
       +
       +        return seed
       +
       +
            def init_seed(self, seed):
       -        if self.seed: raise BaseException("a seed exists")
       -        if not seed: 
       -            seed = random_seed(128)
       -        self.seed = seed
       +        if self.seed: 
       +            raise BaseException("a seed exists")
        
       +        if not seed:
       +            self.seed = self.make_seed()
       +            self.seed_version = SEED_VERSION
       +            return
       +
       +        # find out what kind of wallet we are
       +        try:
       +            seed.decode('hex')
       +            self.seed_version = 4
       +            return
       +        except:
       +            pass
       +
       +        words = seed.split()
       +        try:
       +            mnemonic.mn_decode(words)
       +            uses_electrum_words = True
       +        except:
       +            uses_electrum_words = False
       +
       +        if uses_electrum_words and len(words) != 13:
       +            self.seed_version = 4
       +            self.seed = mnemonic.mn_encode(seed)
       +        else:
       +            assert mnemonic_hash(seed)[0:3] == SEED_PREFIX
       +            self.seed_version = SEED_VERSION
       +            self.seed = seed
       +            
        
            def save_seed(self):
                self.storage.put('seed', self.seed, True)
       t@@ -291,12 +336,12 @@ class Wallet:
        
            def create_accounts(self): 
                # create default account
       -        self.create_master_keys('1', self.seed)
       +        self.create_master_keys('1')
                self.create_account('1','Main account')
        
        
       -    def create_master_keys(self, account_type, seed):
       -        master_k, master_c, master_K, master_cK = bip32_init(self.seed)
       +    def create_master_keys(self, account_type):
       +        master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
                if account_type == '1':
                    k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
                    self.master_public_keys["m/0'/"] = (c0, K0, cK0)
       t@@ -339,7 +384,7 @@ class Wallet:
        
            def deseed_root(self, seed, password):
                # for safety, we ask the user to enter their seed
       -        assert seed == self.decode_seed(password)
       +        assert seed == self.get_seed(password)
                self.seed = ''
                self.storage.put('seed', '', True)
        
       t@@ -600,12 +645,26 @@ class Wallet:
        
        
        
       -    def decode_seed(self, password):
       -        seed = pw_decode(self.seed, password)
       +    def get_seed(self, password):
       +        s = pw_decode(self.seed, password)
       +        if self.seed_version == 4:
       +            seed = s
       +        else:
       +            seed = mnemonic_hash(s)
                #todo:  #self.sequences[0].check_seed(seed)
                return seed
                
        
       +    def get_mnemonic(self, password):
       +        import mnemonic
       +        s = pw_decode(self.seed, password)
       +        if self.seed_version == 4:
       +            return ' '.join(mnemonic.mn_encode(s))
       +        else:
       +            return s
       +
       +        
       +
            def get_private_key(self, address, password):
                out = []
                if address in self.imported_keys.keys():
       t@@ -613,7 +672,7 @@ class Wallet:
                else:
                    account, sequence = self.get_address_index(address)
                    if account == 0:
       -                seed = self.decode_seed(password)
       +                seed = self.get_seed(password)
                        pk = self.accounts[account].get_private_key(seed, sequence)
                        out.append(pk)
                        return out
       t@@ -673,7 +732,7 @@ class Wallet:
            def signrawtransaction(self, tx, input_info, private_keys, password):
        
                # check that the password is correct
       -        seed = self.decode_seed(password)
       +        seed = self.get_seed(password)
        
                # add input info
                tx.add_input_info(input_info)
       t@@ -1291,10 +1350,11 @@ class Wallet:
        
        
        
       -    def update_password(self, seed, old_password, new_password):
       +    def update_password(self, old_password, new_password):
                if new_password == '': new_password = None
                # this will throw an exception if unicode cannot be converted
       -        self.seed = pw_encode( seed, new_password)
       +        decoded = pw_decode(self.seed, old_password)
       +        self.seed = pw_encode( decoded, new_password)
                self.storage.put('seed', self.seed, True)
                self.use_encryption = (new_password != None)
                self.storage.put('use_encryption', self.use_encryption,True)
       t@@ -1485,17 +1545,12 @@ class Wallet:
                # wait until we are connected, because the user might have selected another server
                wait_for_network()
        
       -        # try to restore old account
       -        self.create_old_account()
       -        wait_for_wallet()
        
       -        if self.is_found():
       -            self.seed_version = 4
       -            self.storage.put('seed_version', self.seed_version, True)
       +        if self.seed_version == 4:
       +            self.create_old_account()
                else:
       -            self.accounts.pop(0)
                    self.create_accounts()
       -            wait_for_wallet()
       +        wait_for_wallet()