seed v6
authorThomasV <thomasv@gitorious>
Sat, 26 Oct 2013 09:54:11 +0000 (11:54 +0200)
committerThomasV <thomasv@gitorious>
Sat, 26 Oct 2013 14:40:05 +0000 (16:40 +0200)
electrum
gui/android.py
gui/gtk.py
gui/qt/installwizard.py
gui/qt/main_window.py
gui/qt/password_dialog.py
gui/qt/seed_dialog.py
lib/bitcoin.py
lib/commands.py
lib/version.py
lib/wallet.py

index e3e02e7..d9fc3fd 100755 (executable)
--- a/electrum
+++ b/electrum
@@ -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)
index f57b539..e254a46 100644 (file)
@@ -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
@@ -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
@@ -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:
index e9bf1dc..0eab4dd 100644 (file)
@@ -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
@@ -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
@@ -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:
index 4cbae91..342279a 100644 (file)
@@ -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))
@@ -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'))
@@ -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':
index 8394d30..94cc5fd 100644 (file)
@@ -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 = {}
index ff25eeb..dfd8e95 100644 (file)
@@ -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
@@ -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
index 52aa831..8817de0 100644 (file)
@@ -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)
         
index d5fdc9d..94d5075 100644 (file)
@@ -19,6 +19,7 @@
 
 
 import hashlib, base64, ecdsa, re
+import hmac
 from util import print_error
 
 def rev_hex(s):
@@ -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
 
index a88412f..b430bf1 100644 (file)
@@ -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:
index ee6ecad..7f60b5d 100644 (file)
@@ -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 
index e2d2c18..ca796ec 100644 (file)
@@ -64,7 +64,7 @@ def pw_decode(s, password):
 
 
 
-from version import ELECTRUM_VERSION, SEED_VERSION
+from version import *
 
 
 class WalletStorage:
@@ -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()
 
@@ -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:
@@ -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)
@@ -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)
@@ -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)
 
@@ -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():
@@ -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
@@ -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)
@@ -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)
@@ -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()