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()