electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit c6e09a60388325050410101e78fb72f43aaa525b
parent 8b194cd409a1a79e616578a5eaf91cba68b36dab
Author: Kacper Żuk <kacper.b.zuk@gmail.com>
Date:   Sun, 22 Jan 2017 15:58:37 +0100

Provide warnings about invalid BIP39 checksum in seed dialog

Diffstat:
Mgui/qt/seed_dialog.py | 9++++++---
Mlib/keystore.py | 32++++++++++++++++++++++++++++++--
Mlib/mnemonic.py | 26+++++++++++++++-----------
Mlib/tests/test_mnemonic.py | 9+++++++++
4 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py @@ -64,6 +64,7 @@ class SeedLayout(QVBoxLayout): def f(b): self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed self.on_edit() + self.is_bip39 = b if b: msg = ' '.join([ '<b>' + _('Warning') + ': BIP39 seeds are dangerous!' + '</b><br/><br/>', @@ -76,7 +77,6 @@ class SeedLayout(QVBoxLayout): else: msg = '' self.seed_warning.setText(msg) - cb_bip39 = QCheckBox(_('BIP39 seed')) cb_bip39.toggled.connect(f) cb_bip39.setChecked(self.is_bip39) @@ -130,9 +130,9 @@ class SeedLayout(QVBoxLayout): self.addLayout(hbox) self.addStretch(1) self.seed_warning = WWLabel('') - self.addWidget(self.seed_warning) if msg: self.seed_warning.setText(seed_warning_msg(seed)) + self.addWidget(self.seed_warning) def get_seed(self): text = unicode(self.seed_e.text()) @@ -146,7 +146,10 @@ class SeedLayout(QVBoxLayout): t = seed_type(s) label = _('Seed Type') + ': ' + t if t else '' else: - label = 'BIP39 (checksum disabled)' + from electrum.keystore import bip39_is_checksum_valid + is_checksum, is_wordlist = bip39_is_checksum_valid(s) + status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' + label = 'BIP39' + ' (%s)'%status self.seed_type_label.setText(label) self.parent.next_button.setEnabled(b) diff --git a/lib/keystore.py b/lib/keystore.py @@ -24,6 +24,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import struct from unicodedata import normalize @@ -35,7 +36,7 @@ from bitcoin import * from bitcoin import is_old_seed, is_new_seed, is_seed from util import PrintError, InvalidPassword -from mnemonic import Mnemonic +from mnemonic import Mnemonic, load_wordlist class KeyStore(PrintError): @@ -555,7 +556,34 @@ def bip39_to_seed(mnemonic, passphrase): iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64) - +# returns tuple (is_checksum_valid, is_wordlist_valid) +def bip39_is_checksum_valid(mnemonic): + words = [ normalize('NFKD', word) for word in mnemonic.split() ] + words_len = len(words) + wordlist = load_wordlist("english.txt") + n = len(wordlist) + checksum_length = 11*words_len//33 + entropy_length = 32*checksum_length + i = 0 + words.reverse() + while words: + w = words.pop() + try: + k = wordlist.index(w) + except ValueError: + return False, False + i = i*n + k + if words_len not in [12, 15, 18, 21, 24]: + return False, True + entropy = i >> checksum_length + checksum = i % 2**checksum_length + h = '{:x}'.format(entropy) + while len(h) < entropy_length/4: + h = '0'+h + b = bytearray.fromhex(h) + hashed = int(hashlib.sha256(b).digest().encode('hex'), 16) + calculated_checksum = hashed >> (256 - checksum_length) + return checksum == calculated_checksum, True # extended pubkeys diff --git a/lib/mnemonic.py b/lib/mnemonic.py @@ -91,6 +91,20 @@ def normalize_text(seed): seed = u''.join([seed[i] for i in range(len(seed)) if not (seed[i] in string.whitespace and is_CJK(seed[i-1]) and is_CJK(seed[i+1]))]) return seed +def load_wordlist(filename): + path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) + s = open(path,'r').read().strip() + s = unicodedata.normalize('NFKD', s.decode('utf8')) + lines = s.split('\n') + wordlist = [] + for line in lines: + line = line.split('#')[0] + line = line.strip(' \r') + assert ' ' not in line + if line: + wordlist.append(line) + return wordlist + filenames = { 'en':'english.txt', @@ -110,17 +124,7 @@ class Mnemonic(object): lang = lang or 'en' print_error('language', lang) filename = filenames.get(lang[0:2], 'english.txt') - path = os.path.join(os.path.dirname(__file__), 'wordlist', filename) - s = open(path,'r').read().strip() - s = unicodedata.normalize('NFKD', s.decode('utf8')) - lines = s.split('\n') - self.wordlist = [] - for line in lines: - line = line.split('#')[0] - line = line.strip(' \r') - assert ' ' not in line - if line: - self.wordlist.append(line) + self.wordlist = load_wordlist(filename) print_error("wordlist has %d words"%len(self.wordlist)) @classmethod diff --git a/lib/tests/test_mnemonic.py b/lib/tests/test_mnemonic.py @@ -1,4 +1,5 @@ import unittest +from lib import keystore from lib import mnemonic from lib import old_mnemonic @@ -27,3 +28,11 @@ class Test_OldMnemonic(unittest.TestCase): words = 'hardly point goal hallway patience key stone difference ready caught listen fact' self.assertEquals(result, words.split()) self.assertEquals(old_mnemonic.mn_decode(result), seed) + +class Test_BIP39Checksum(unittest.TestCase): + + def test(self): + mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog' + is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic) + self.assertTrue(is_wordlist_valid) + self.assertTrue(is_checksum_valid)