Provide warnings about invalid BIP39 checksum in seed dialog
This commit is contained in:
parent
8b194cd409
commit
c6e09a6038
|
@ -64,6 +64,7 @@ class SeedLayout(QVBoxLayout):
|
||||||
def f(b):
|
def f(b):
|
||||||
self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed
|
self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed
|
||||||
self.on_edit()
|
self.on_edit()
|
||||||
|
self.is_bip39 = b
|
||||||
if b:
|
if b:
|
||||||
msg = ' '.join([
|
msg = ' '.join([
|
||||||
'<b>' + _('Warning') + ': BIP39 seeds are dangerous!' + '</b><br/><br/>',
|
'<b>' + _('Warning') + ': BIP39 seeds are dangerous!' + '</b><br/><br/>',
|
||||||
|
@ -76,7 +77,6 @@ class SeedLayout(QVBoxLayout):
|
||||||
else:
|
else:
|
||||||
msg = ''
|
msg = ''
|
||||||
self.seed_warning.setText(msg)
|
self.seed_warning.setText(msg)
|
||||||
|
|
||||||
cb_bip39 = QCheckBox(_('BIP39 seed'))
|
cb_bip39 = QCheckBox(_('BIP39 seed'))
|
||||||
cb_bip39.toggled.connect(f)
|
cb_bip39.toggled.connect(f)
|
||||||
cb_bip39.setChecked(self.is_bip39)
|
cb_bip39.setChecked(self.is_bip39)
|
||||||
|
@ -130,9 +130,9 @@ class SeedLayout(QVBoxLayout):
|
||||||
self.addLayout(hbox)
|
self.addLayout(hbox)
|
||||||
self.addStretch(1)
|
self.addStretch(1)
|
||||||
self.seed_warning = WWLabel('')
|
self.seed_warning = WWLabel('')
|
||||||
self.addWidget(self.seed_warning)
|
|
||||||
if msg:
|
if msg:
|
||||||
self.seed_warning.setText(seed_warning_msg(seed))
|
self.seed_warning.setText(seed_warning_msg(seed))
|
||||||
|
self.addWidget(self.seed_warning)
|
||||||
|
|
||||||
def get_seed(self):
|
def get_seed(self):
|
||||||
text = unicode(self.seed_e.text())
|
text = unicode(self.seed_e.text())
|
||||||
|
@ -146,7 +146,10 @@ class SeedLayout(QVBoxLayout):
|
||||||
t = seed_type(s)
|
t = seed_type(s)
|
||||||
label = _('Seed Type') + ': ' + t if t else ''
|
label = _('Seed Type') + ': ' + t if t else ''
|
||||||
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.seed_type_label.setText(label)
|
||||||
self.parent.next_button.setEnabled(b)
|
self.parent.next_button.setEnabled(b)
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
from unicodedata import normalize
|
from unicodedata import normalize
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ from bitcoin import *
|
||||||
|
|
||||||
from bitcoin import is_old_seed, is_new_seed, is_seed
|
from bitcoin import is_old_seed, is_new_seed, is_seed
|
||||||
from util import PrintError, InvalidPassword
|
from util import PrintError, InvalidPassword
|
||||||
from mnemonic import Mnemonic
|
from mnemonic import Mnemonic, load_wordlist
|
||||||
|
|
||||||
|
|
||||||
class KeyStore(PrintError):
|
class KeyStore(PrintError):
|
||||||
|
@ -555,7 +556,34 @@ def bip39_to_seed(mnemonic, passphrase):
|
||||||
iterations = PBKDF2_ROUNDS, macmodule = hmac,
|
iterations = PBKDF2_ROUNDS, macmodule = hmac,
|
||||||
digestmodule = hashlib.sha512).read(64)
|
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
|
# extended pubkeys
|
||||||
|
|
||||||
|
|
|
@ -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]))])
|
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
|
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 = {
|
filenames = {
|
||||||
'en':'english.txt',
|
'en':'english.txt',
|
||||||
|
@ -110,17 +124,7 @@ class Mnemonic(object):
|
||||||
lang = lang or 'en'
|
lang = lang or 'en'
|
||||||
print_error('language', lang)
|
print_error('language', lang)
|
||||||
filename = filenames.get(lang[0:2], 'english.txt')
|
filename = filenames.get(lang[0:2], 'english.txt')
|
||||||
path = os.path.join(os.path.dirname(__file__), 'wordlist', filename)
|
self.wordlist = load_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)
|
|
||||||
print_error("wordlist has %d words"%len(self.wordlist))
|
print_error("wordlist has %d words"%len(self.wordlist))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
from lib import keystore
|
||||||
from lib import mnemonic
|
from lib import mnemonic
|
||||||
from lib import old_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'
|
words = 'hardly point goal hallway patience key stone difference ready caught listen fact'
|
||||||
self.assertEquals(result, words.split())
|
self.assertEquals(result, words.split())
|
||||||
self.assertEquals(old_mnemonic.mn_decode(result), seed)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue