electrum-bitcoinprivate/lib/mnemonic.py

124 lines
3.8 KiB
Python
Raw Normal View History

2011-11-08 04:56:35 -08:00
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2014 Thomas Voegtlin
2011-11-08 04:56:35 -08:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import hmac
import math
import hashlib
import unicodedata
import string
2011-11-08 04:56:35 -08:00
import ecdsa
import pbkdf2
2011-11-08 04:56:35 -08:00
2014-09-13 00:58:11 -07:00
import util
from util import print_error
from bitcoin import is_old_seed, is_new_seed
2014-09-09 02:51:45 -07:00
import version
2011-11-08 04:56:35 -08:00
filenames = {
'en':'english.txt',
'es':'spanish.txt',
'ja':'japanese.txt',
'pt':'portuguese.txt',
}
class Mnemonic(object):
# Seed derivation no longer follows BIP39
# Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum
2011-11-08 04:56:35 -08:00
def __init__(self, lang='en'):
filename = filenames.get(lang[0:2], 'english.txt')
2014-09-13 00:58:11 -07:00
path = os.path.join(util.appdata_dir(), '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))
2011-11-08 04:56:35 -08:00
@classmethod
def mnemonic_to_seed(self, mnemonic, passphrase):
PBKDF2_ROUNDS = 2048
2014-09-11 07:28:03 -07:00
mnemonic = self.prepare_seed(mnemonic)
return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, iterations = PBKDF2_ROUNDS, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
2011-11-08 04:56:35 -08:00
@classmethod
def prepare_seed(self, seed):
# normalize
seed = unicodedata.normalize('NFKD', unicode(seed))
2014-09-12 07:48:24 -07:00
# lower
seed = seed.lower()
# remove accents and whitespaces
seed = u''.join([c for c in seed if not unicodedata.combining(c) and not c in string.whitespace])
return seed
2011-11-08 04:56:35 -08:00
def mnemonic_encode(self, i):
n = len(self.wordlist)
words = []
while i:
x = i%n
i = i/n
words.append(self.wordlist[x])
return ' '.join(words)
2011-11-08 04:56:35 -08:00
def mnemonic_decode(self, seed):
n = len(self.wordlist)
words = seed.split()
i = 0
while words:
w = words.pop()
k = self.wordlist.index(w)
i = i*n + k
return i
2011-11-08 04:56:35 -08:00
def check_seed(self, seed, custom_entropy):
assert is_new_seed(seed)
i = self.mnemonic_decode(seed)
return i % custom_entropy == 0
2014-09-09 02:51:45 -07:00
def make_seed(self, num_bits=128, prefix=version.SEED_BIP44, custom_entropy=1):
n = int(math.ceil(math.log(custom_entropy,2)))
2014-09-09 02:51:45 -07:00
# bits of entropy used by the prefix
k = len(prefix)*4
# we add at least 16 bits
2014-09-09 02:51:45 -07:00
n_added = max(16, k + num_bits - n)
print_error("make_seed", prefix, "adding %d bits"%n_added)
my_entropy = ecdsa.util.randrange( pow(2, n_added) )
nonce = 0
while True:
nonce += 1
i = custom_entropy * (my_entropy + nonce)
seed = self.mnemonic_encode(i)
assert i == self.mnemonic_decode(seed)
if is_old_seed(seed):
continue
if is_new_seed(seed, prefix):
break
print_error('%d words'%len(seed.split()))
return seed
2011-11-08 04:56:35 -08:00