commit 31aaae8ed22d932a06b0ddeddbe0bc9932c0367b
parent 07bdd6c494004458e3799ef364a71d6fa5f476c8
Author: ThomasV <thomasv@gitorious>
Date: Sat, 26 Oct 2013 11:54:11 +0200
seed v6
Diffstat:
11 files changed, 110 insertions(+), 56 deletions(-)
diff --git 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)
diff --git a/gui/android.py b/gui/android.py
@@ -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:
diff --git a/gui/gtk.py b/gui/gtk.py
@@ -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:
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
@@ -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':
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -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 = {}
diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
@@ -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
diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
@@ -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)
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
@@ -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
diff --git a/lib/commands.py b/lib/commands.py
@@ -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:
diff --git a/lib/version.py b/lib/version.py
@@ -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
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -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()